From 9ea4ce36057388dbc4bdcf34ab68697261807bff Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Tue, 10 Dec 2024 14:10:47 +0700 Subject: [PATCH 01/10] feat: adding nullable text if selected column is json operator and casted as text --- go.mod | 1 - go.sum | 20 +---- internal/compiler/output_columns.go | 7 +- .../issue.md | 1 + .../postgresql/pgx/v5/exec.json | 3 + .../postgresql/pgx/v5/go/db.go | 32 +++++++ .../postgresql/pgx/v5/go/models.go | 10 +++ .../postgresql/pgx/v5/go/query.sql.go | 74 ++++++++++++++++ .../postgresql/pgx/v5/query.sql | 7 ++ .../postgresql/pgx/v5/schema.sql | 8 ++ .../postgresql/pgx/v5/sqlc.yaml | 11 +++ .../postgresql/pgx/v5/stmttreejson.txt | 85 +++++++++++++++++++ .../postgresql/pgx/v5/stmttreeordinary.txt | 30 +++++++ internal/sql/lang/operator.go | 12 +++ 14 files changed, 280 insertions(+), 21 deletions(-) create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/issue.md create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/exec.json create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/db.go create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/models.go create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/schema.sql create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/sqlc.yaml create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreejson.txt create mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreeordinary.txt diff --git a/go.mod b/go.mod index f3b3b65742..d268983aad 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,6 @@ require ( require ( cel.dev/expr v0.18.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect - github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect diff --git a/go.sum b/go.sum index df09635cf6..0084f1c4bc 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= -github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso= -github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -130,19 +128,12 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm github.com/pganalyze/pg_query_go/v5 v5.1.0 h1:MlxQqHZnvA3cbRQYyIrjxEjzo560P6MyTgtlaf3pmXg= github.com/pganalyze/pg_query_go/v5 v5.1.0/go.mod h1:FsglvxidZsVN+Ltw3Ai6nTgPVcK2BPukH3jCDEqc1Ug= github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 h1:+FZIDR/D97YOPik4N4lPDaUcLDF/EQPogxtlHB2ZZRM= -github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb h1:3pSi4EDG6hg0orE1ndHkXvX6Qdq2cZn8gAPir8ymKZk= github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= -github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c h1:CgbKAHto5CQgWM9fSBIvaxsJHuGP0uM74HXtv3MyyGQ= -github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew= github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 h1:tdMsjOqUR7YXHoBitzdebTvOjs/swniBTOLy5XiMtuE= github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86/go.mod h1:exzhVYca3WRtd6gclGNErRWb1qEgff3LYta0LvRmON4= github.com/pingcap/log v1.1.0 h1:ELiPxACz7vdo1qAvvaWJg1NrYFoY6gqAh/+Uo6aXdD8= github.com/pingcap/log v1.1.0/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= -github.com/pingcap/tidb/pkg/parser v0.0.0-20231103154709-4f00ece106b1 h1:SwGY3zMnK4wO85vvRIqrR3Yh6VpIC9pydG0QNOUPHCY= -github.com/pingcap/tidb/pkg/parser v0.0.0-20231103154709-4f00ece106b1/go.mod h1:yRkiqLFwIqibYg2P7h4bclHjHcJiIFRLKhGRyBcKYus= github.com/pingcap/tidb/pkg/parser v0.0.0-20241203170126-9812d85d0d25 h1:sAHMshrilTiR9ue2SktI/tVVT2gB4kNaQaY5pbs0YQQ= github.com/pingcap/tidb/pkg/parser v0.0.0-20241203170126-9812d85d0d25/go.mod h1:Hju1TEWZvrctQKbztTRwXH7rd41Yq0Pgmq4PrEKcq7o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -162,7 +153,6 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -206,8 +196,8 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -220,8 +210,6 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -274,8 +262,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -307,7 +293,6 @@ google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8i google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -316,7 +301,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index dbdbe252b3..d7c81bcbe6 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -256,7 +256,6 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er case *ast.ColumnRef: if hasStarRef(n) { - // add a column with a reference to an embedded table if embed, ok := qc.embeds.Find(n); ok { cols = append(cols, &Column{ @@ -366,8 +365,12 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er col.NotNull = false } } + if expr, ok := n.Arg.(*ast.A_Expr); ok { + if op := astutils.Join(expr.Name, ""); lang.IsJSONOperator(op) { + col.NotNull = false + } + } cols = append(cols, col) - case *ast.SelectStmt: subcols, err := c.outputColumns(qc, n) if err != nil { diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/issue.md b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/issue.md new file mode 100644 index 0000000000..eb8c6145f3 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/issue.md @@ -0,0 +1 @@ +https://github.com/sqlc-dev/sqlc/issues/3710 \ No newline at end of file diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/exec.json b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/exec.json new file mode 100644 index 0000000000..ee1b7ecd9e --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["managed-db"] +} diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/db.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/db.go new file mode 100644 index 0000000000..69ef001548 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 + +package querytest + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/models.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/models.go new file mode 100644 index 0000000000..c0841a0eb9 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/models.go @@ -0,0 +1,10 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 + +package querytest + +type Mytable struct { + ID int64 `json:"id"` + Myjson []byte `json:"myjson"` +} diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go new file mode 100644 index 0000000000..a8efa1ecc2 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go @@ -0,0 +1,74 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: query.sql + +package querytest + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const myGet = `-- name: MyGet :many +SELECT id, myjson, (mt.myjson->'thing1'->'thing2')::text, mt.myjson->'thing1' +FROM "mytable" mt +` + +type MyGetRow struct { + ID int64 `json:"id"` + Myjson []byte `json:"myjson"` + Column3 pgtype.Text `json:"column_3"` + Column4 interface{} `json:"column_4"` +} + +func (q *Queries) MyGet(ctx context.Context) ([]MyGetRow, error) { + rows, err := q.db.Query(ctx, myGet) + if err != nil { + return nil, err + } + defer rows.Close() + var items []MyGetRow + for rows.Next() { + var i MyGetRow + if err := rows.Scan( + &i.ID, + &i.Myjson, + &i.Column3, + &i.Column4, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const myGet2 = `-- name: MyGet2 :many +SELECT id::text +FROM "mytable" mt +` + +func (q *Queries) MyGet2(ctx context.Context) ([]string, error) { + rows, err := q.db.Query(ctx, myGet2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []string + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql new file mode 100644 index 0000000000..76ab90dba7 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql @@ -0,0 +1,7 @@ +-- name: MyGet :many +SELECT *, (mt.myjson->'thing1'->'thing2')::text, mt.myjson->'thing1' +FROM "mytable" mt; + +-- name: MyGet2 :many +SELECT id::text +FROM "mytable" mt; \ No newline at end of file diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/schema.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/schema.sql new file mode 100644 index 0000000000..81cad60b92 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE "mytable" ( + id BIGSERIAL NOT NULL PRIMARY KEY, + myjson JSONB NOT NULL +); + +insert into mytable (myjson) values + ('{}'), + ('{"thing1": {"thing2": "thing3"}}'); \ No newline at end of file diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/sqlc.yaml b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/sqlc.yaml new file mode 100644 index 0000000000..3065f606c0 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/sqlc.yaml @@ -0,0 +1,11 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "query.sql" + schema: "schema.sql" + gen: + go: + package: "querytest" + out: "go" + sql_package: "pgx/v5" + emit_json_tags: true \ No newline at end of file diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreejson.txt b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreejson.txt new file mode 100644 index 0000000000..c9b71ef03a --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreejson.txt @@ -0,0 +1,85 @@ +raw tree.Stmt: stmt:{ + select_stmt:{ + target_list:{ + res_target:{ + val:{column_ref:{fields:{a_star:{}} location:28}} + location:28 + } + } + target_list:{ + res_target:{ + val:{ + type_cast:{ + arg:{ + a_expr:{ + kind:AEXPR_OP + name:{string:{sval:"->"}} + lexpr:{ + a_expr:{ + kind:AEXPR_OP + name:{string:{sval:"->"}} + lexpr:{ + column_ref:{ + fields:{string:{sval:"mt"}} + fields:{string:{sval:"myjson"}} + location:32 + } + } + rexpr:{ + a_const:{ + sval:{sval:"thing1"} location:43 + } + } + location:41 + } + } + rexpr:{ + a_const:{sval:{sval:"thing2"} location:53} + } + location:51 + } + } + type_name:{ + names:{string:{sval:"text"}} typemod:-1 location:64 + } + location:62 + } + } + location:31 + } + } + target_list:{ + res_target:{ + val:{ + a_expr:{ + kind:AEXPR_OP + name:{string:{sval:"->"}} + lexpr:{ + column_ref:{ + fields:{string:{sval:"mt"}} + fields:{string:{sval:"myjson"}} + location:70 + } + } + rexpr:{ + a_const:{sval:{sval:"thing1"} location:81} + } + location:79 + } + } + location:70 + } + } + from_clause:{ + range_var:{ + relname:"mytable" + inh:true + relpersistence:"p" + alias:{aliasname:"mt"} + location:95 + } + } + limit_option:LIMIT_OPTION_DEFAULT op:SETOP_NONE + } +} +stmt_len:107 \ No newline at end of file diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreeordinary.txt b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreeordinary.txt new file mode 100644 index 0000000000..3d7f814127 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreeordinary.txt @@ -0,0 +1,30 @@ +stmt:{ + select_stmt:{ + target_list:{ + res_target:{ + val:{ + type_cast:{ + arg:{ + column_ref:{ + fields:{string:{sval:"id"}} location:120 + } + } + type_name:{ + names:{string:{sval:"text"}} typemod:-1 location:124 + } + location:122 + } + } + location:120 + } + } + from_clause:{ + range_var:{ + relname:"mytable" inh:true relpersistence:"p" alias:{aliasname:"mt"} location:134 + } + } + limit_option:LIMIT_OPTION_DEFAULT op:SETOP_NONE + } +} +stmt_location:89 +stmt_len:57 \ No newline at end of file diff --git a/internal/sql/lang/operator.go b/internal/sql/lang/operator.go index cd5ef50e38..99ec892c13 100644 --- a/internal/sql/lang/operator.go +++ b/internal/sql/lang/operator.go @@ -41,3 +41,15 @@ func IsMathematicalOperator(s string) bool { } return true } + +func IsJSONOperator(s string) bool { + switch s { + case "->": + case "->>": + case "#>": + case "#>>": + default: + return false + } + return true +} From 25274e88cbe21328480a13749b241a5528851abe Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Tue, 10 Dec 2024 15:09:17 +0700 Subject: [PATCH 02/10] test: fix fmt --- .../postgresql/pgx/v5/go/query.sql.go | 29 +------ .../postgresql/pgx/v5/query.sql | 8 +- .../postgresql/pgx/v5/stmttreejson.txt | 85 ------------------- .../postgresql/pgx/v5/stmttreeordinary.txt | 30 ------- 4 files changed, 4 insertions(+), 148 deletions(-) delete mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreejson.txt delete mode 100644 internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreeordinary.txt diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go index a8efa1ecc2..aeb7a170a0 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go @@ -12,8 +12,8 @@ import ( ) const myGet = `-- name: MyGet :many -SELECT id, myjson, (mt.myjson->'thing1'->'thing2')::text, mt.myjson->'thing1' -FROM "mytable" mt +SELECT id, myjson,(mt.myjson -> 'thing1' -> 'thing2')::text,mt.myjson -> 'thing1' +FROM mytable mt ` type MyGetRow struct { @@ -47,28 +47,3 @@ func (q *Queries) MyGet(ctx context.Context) ([]MyGetRow, error) { } return items, nil } - -const myGet2 = `-- name: MyGet2 :many -SELECT id::text -FROM "mytable" mt -` - -func (q *Queries) MyGet2(ctx context.Context) ([]string, error) { - rows, err := q.db.Query(ctx, myGet2) - if err != nil { - return nil, err - } - defer rows.Close() - var items []string - for rows.Next() { - var id string - if err := rows.Scan(&id); err != nil { - return nil, err - } - items = append(items, id) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql index 76ab90dba7..e58c548841 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql @@ -1,7 +1,3 @@ -- name: MyGet :many -SELECT *, (mt.myjson->'thing1'->'thing2')::text, mt.myjson->'thing1' -FROM "mytable" mt; - --- name: MyGet2 :many -SELECT id::text -FROM "mytable" mt; \ No newline at end of file +SELECT *,(mt.myjson -> 'thing1' -> 'thing2')::text,mt.myjson -> 'thing1' +FROM mytable mt; diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreejson.txt b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreejson.txt deleted file mode 100644 index c9b71ef03a..0000000000 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreejson.txt +++ /dev/null @@ -1,85 +0,0 @@ -raw tree.Stmt: stmt:{ - select_stmt:{ - target_list:{ - res_target:{ - val:{column_ref:{fields:{a_star:{}} location:28}} - location:28 - } - } - target_list:{ - res_target:{ - val:{ - type_cast:{ - arg:{ - a_expr:{ - kind:AEXPR_OP - name:{string:{sval:"->"}} - lexpr:{ - a_expr:{ - kind:AEXPR_OP - name:{string:{sval:"->"}} - lexpr:{ - column_ref:{ - fields:{string:{sval:"mt"}} - fields:{string:{sval:"myjson"}} - location:32 - } - } - rexpr:{ - a_const:{ - sval:{sval:"thing1"} location:43 - } - } - location:41 - } - } - rexpr:{ - a_const:{sval:{sval:"thing2"} location:53} - } - location:51 - } - } - type_name:{ - names:{string:{sval:"text"}} typemod:-1 location:64 - } - location:62 - } - } - location:31 - } - } - target_list:{ - res_target:{ - val:{ - a_expr:{ - kind:AEXPR_OP - name:{string:{sval:"->"}} - lexpr:{ - column_ref:{ - fields:{string:{sval:"mt"}} - fields:{string:{sval:"myjson"}} - location:70 - } - } - rexpr:{ - a_const:{sval:{sval:"thing1"} location:81} - } - location:79 - } - } - location:70 - } - } - from_clause:{ - range_var:{ - relname:"mytable" - inh:true - relpersistence:"p" - alias:{aliasname:"mt"} - location:95 - } - } - limit_option:LIMIT_OPTION_DEFAULT op:SETOP_NONE - } -} -stmt_len:107 \ No newline at end of file diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreeordinary.txt b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreeordinary.txt deleted file mode 100644 index 3d7f814127..0000000000 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/stmttreeordinary.txt +++ /dev/null @@ -1,30 +0,0 @@ -stmt:{ - select_stmt:{ - target_list:{ - res_target:{ - val:{ - type_cast:{ - arg:{ - column_ref:{ - fields:{string:{sval:"id"}} location:120 - } - } - type_name:{ - names:{string:{sval:"text"}} typemod:-1 location:124 - } - location:122 - } - } - location:120 - } - } - from_clause:{ - range_var:{ - relname:"mytable" inh:true relpersistence:"p" alias:{aliasname:"mt"} location:134 - } - } - limit_option:LIMIT_OPTION_DEFAULT op:SETOP_NONE - } -} -stmt_location:89 -stmt_len:57 \ No newline at end of file From f61bfcc6721e6c71786bbb6f64ad039c74a2ccb9 Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Tue, 10 Dec 2024 15:36:57 +0700 Subject: [PATCH 03/10] test: delete fourth column --- .../postgresql/pgx/v5/go/query.sql.go | 10 ++-------- .../postgresql/pgx/v5/query.sql | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go index aeb7a170a0..ad07dc0e23 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go @@ -12,7 +12,7 @@ import ( ) const myGet = `-- name: MyGet :many -SELECT id, myjson,(mt.myjson -> 'thing1' -> 'thing2')::text,mt.myjson -> 'thing1' +SELECT id, myjson, (mt.myjson->'thing1'->'thing2')::text FROM mytable mt ` @@ -20,7 +20,6 @@ type MyGetRow struct { ID int64 `json:"id"` Myjson []byte `json:"myjson"` Column3 pgtype.Text `json:"column_3"` - Column4 interface{} `json:"column_4"` } func (q *Queries) MyGet(ctx context.Context) ([]MyGetRow, error) { @@ -32,12 +31,7 @@ func (q *Queries) MyGet(ctx context.Context) ([]MyGetRow, error) { var items []MyGetRow for rows.Next() { var i MyGetRow - if err := rows.Scan( - &i.ID, - &i.Myjson, - &i.Column3, - &i.Column4, - ); err != nil { + if err := rows.Scan(&i.ID, &i.Myjson, &i.Column3); err != nil { return nil, err } items = append(items, i) diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql index e58c548841..db68a8c371 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql @@ -1,3 +1,3 @@ -- name: MyGet :many -SELECT *,(mt.myjson -> 'thing1' -> 'thing2')::text,mt.myjson -> 'thing1' +SELECT *, (mt.myjson->'thing1'->'thing2')::text FROM mytable mt; From 049fb49304b7a7b5fda2448469fbf9b7e210ed13 Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Tue, 10 Dec 2024 15:48:57 +0700 Subject: [PATCH 04/10] test: avoid test format because the reversed query from raw statements printed selected columns without parentheses, it will generate different type on from query.sql --- .../postgresql/pgx/{v5 => }/exec.json | 0 .../postgresql/pgx/{v5 => }/go/db.go | 0 .../postgresql/pgx/{v5 => }/go/models.go | 0 .../postgresql/pgx/{v5 => }/go/query.sql.go | 0 .../postgresql/pgx/{v5 => }/query.sql | 0 .../postgresql/pgx/{v5 => }/schema.sql | 0 .../postgresql/pgx/{v5 => }/sqlc.yaml | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/{v5 => }/exec.json (100%) rename internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/{v5 => }/go/db.go (100%) rename internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/{v5 => }/go/models.go (100%) rename internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/{v5 => }/go/query.sql.go (100%) rename internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/{v5 => }/query.sql (100%) rename internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/{v5 => }/schema.sql (100%) rename internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/{v5 => }/sqlc.yaml (100%) diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/exec.json b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/exec.json similarity index 100% rename from internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/exec.json rename to internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/exec.json diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/db.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/db.go similarity index 100% rename from internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/db.go rename to internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/db.go diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/models.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/models.go similarity index 100% rename from internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/models.go rename to internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/models.go diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go similarity index 100% rename from internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/go/query.sql.go rename to internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql similarity index 100% rename from internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/query.sql rename to internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/schema.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/schema.sql similarity index 100% rename from internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/schema.sql rename to internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/schema.sql diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/sqlc.yaml b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/sqlc.yaml similarity index 100% rename from internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/v5/sqlc.yaml rename to internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/sqlc.yaml From ebc8a772cff26624f985b77196371803887bdfda Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Thu, 12 Dec 2024 14:53:42 +0700 Subject: [PATCH 05/10] feat: case expr handling and json operator handling --- internal/compiler/output_columns.go | 158 +++++--- .../postgresql/pgx/go/query.sql.go | 348 +++++++++++++++++- .../postgresql/pgx/query.sql | 82 ++++- internal/sql/lang/operator.go | 10 + 4 files changed, 533 insertions(+), 65 deletions(-) diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index d7c81bcbe6..8c2c39ddd6 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -132,33 +132,14 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er if res.Name != nil { name = *res.Name } - switch n.Val.(type) { - case *ast.String: - cols = append(cols, &Column{Name: name, DataType: "text", NotNull: true}) - case *ast.Integer: - cols = append(cols, &Column{Name: name, DataType: "int", NotNull: true}) - case *ast.Float: - cols = append(cols, &Column{Name: name, DataType: "float", NotNull: true}) - case *ast.Boolean: - cols = append(cols, &Column{Name: name, DataType: "bool", NotNull: true}) - default: - cols = append(cols, &Column{Name: name, DataType: "any", NotNull: false}) - } + cols = append(cols, convertAConstToColumn(n, name)) case *ast.A_Expr: name := "" if res.Name != nil { name = *res.Name } - switch op := astutils.Join(n.Name, ""); { - case lang.IsComparisonOperator(op): - // TODO: Generate a name for these operations - cols = append(cols, &Column{Name: name, DataType: "bool", NotNull: true}) - case lang.IsMathematicalOperator(op): - cols = append(cols, &Column{Name: name, DataType: "int", NotNull: true}) - default: - cols = append(cols, &Column{Name: name, DataType: "any", NotNull: false}) - } + cols = append(cols, convertAExprToColumn(n, name)) case *ast.BoolExpr: name := "" @@ -187,40 +168,49 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er if res.Name != nil { name = *res.Name } - // TODO: The TypeCase and A_Const code has been copied from below. Instead, we - // need a recurse function to get the type of a node. - if tc, ok := n.Defresult.(*ast.TypeCast); ok { - if tc.TypeName == nil { - return nil, errors.New("no type name type cast") + + typePrecedence := map[string]int{ + "any": 0, + "bool": 1, + "int": 2, + "pg_catalog.int4": 2, + "float": 3, + "pg_catalog.float8": 3, + "text": 4, + } + + chosenType := "any" + chosenNullable := false + for _, i := range n.Args.Items { + cw := i.(*ast.CaseWhen) + col, err := convertCaseExprCondToColumn(cw.Result, res.Name) + if err != nil { + return nil, err } - name := "" - if ref, ok := tc.Arg.(*ast.ColumnRef); ok { - name = astutils.Join(ref.Fields, "_") + if typePrecedence[col.DataType] > typePrecedence[chosenType] { + chosenType = col.DataType } - if res.Name != nil { - name = *res.Name + if !col.NotNull { + chosenNullable = true } - // TODO Validate column names - col := toColumn(tc.TypeName) - col.Name = name - cols = append(cols, col) - } else if aconst, ok := n.Defresult.(*ast.A_Const); ok { - switch aconst.Val.(type) { - case *ast.String: - cols = append(cols, &Column{Name: name, DataType: "text", NotNull: true}) - case *ast.Integer: - cols = append(cols, &Column{Name: name, DataType: "int", NotNull: true}) - case *ast.Float: - cols = append(cols, &Column{Name: name, DataType: "float", NotNull: true}) - case *ast.Boolean: - cols = append(cols, &Column{Name: name, DataType: "bool", NotNull: true}) - default: - cols = append(cols, &Column{Name: name, DataType: "any", NotNull: false}) + } + + if n.Defresult != nil { + defaultCol, err := convertCaseExprCondToColumn(n.Defresult, res.Name) + if err != nil { + return nil, err + } + if typePrecedence[defaultCol.DataType] > typePrecedence[chosenType] { + chosenType = defaultCol.DataType + } + if !defaultCol.NotNull { + chosenNullable = true } - } else { - cols = append(cols, &Column{Name: name, DataType: "any", NotNull: false}) } + chosenColumn := &Column{Name: name, DataType: chosenType, NotNull: !chosenNullable} + cols = append(cols, chosenColumn) + case *ast.CoalesceExpr: name := "coalesce" if res.Name != nil { @@ -371,6 +361,7 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er } } cols = append(cols, col) + case *ast.SelectStmt: subcols, err := c.outputColumns(qc, n) if err != nil { @@ -767,3 +758,72 @@ func findColumnForRef(ref *ast.ColumnRef, tables []*Table, targetList *ast.List) return nil } + +func convertCaseExprCondToColumn(n ast.Node, resTargetName *string) (*Column, error) { + var col *Column + name := "" + if resTargetName != nil { + name = *resTargetName + } + + if tc, ok := n.(*ast.TypeCast); ok { + if tc.TypeName == nil { + return nil, errors.New("no type name type cast") + } + if ref, ok := tc.Arg.(*ast.ColumnRef); ok { + name = astutils.Join(ref.Fields, "_") + } + // TODO Validate column names + col = toColumn(tc.TypeName) + + if x, ok := tc.Arg.(*ast.A_Const); ok { + if _, ok := x.Val.(*ast.Null); ok { + col.NotNull = false + } + } + col.Name = name + + } else if aconst, ok := n.(*ast.A_Const); ok { + col = convertAConstToColumn(aconst, name) + } else if aexpr, ok := n.(*ast.A_Expr); ok { + col = convertAExprToColumn(aexpr, name) + } else { + col = &Column{Name: name, DataType: "any", NotNull: false} + } + + return col, nil +} + +func convertAExprToColumn(aexpr *ast.A_Expr, name string) *Column { + var col *Column + switch op := astutils.Join(aexpr.Name, ""); { + case lang.IsComparisonOperator(op): + // TODO: Generate a name for these operations + col = &Column{Name: name, DataType: "bool", NotNull: true} + case lang.IsMathematicalOperator(op): + col = &Column{Name: name, DataType: "int", NotNull: true} + case lang.IsJSONOperator(op) && lang.IsJSONResultAsText(op): + col = &Column{Name: name, DataType: "text", NotNull: false} + default: + col = &Column{Name: name, DataType: "any", NotNull: false} + } + + return col +} + +func convertAConstToColumn(aconst *ast.A_Const, name string) *Column { + var col *Column + switch aconst.Val.(type) { + case *ast.String: + col = &Column{Name: name, DataType: "text", NotNull: true} + case *ast.Integer: + col = &Column{Name: name, DataType: "int", NotNull: true} + case *ast.Float: + col = &Column{Name: name, DataType: "float", NotNull: true} + case *ast.Boolean: + col = &Column{Name: name, DataType: "bool", NotNull: true} + default: + col = &Column{Name: name, DataType: "any", NotNull: false} + } + return col +} diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go index ad07dc0e23..6e1a22a41e 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go @@ -11,30 +11,352 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) -const myGet = `-- name: MyGet :many -SELECT id, myjson, (mt.myjson->'thing1'->'thing2')::text -FROM mytable mt +const getNullable1 = `-- name: GetNullable1 :many +SELECT null::text +FROM "mytable" ` -type MyGetRow struct { - ID int64 `json:"id"` - Myjson []byte `json:"myjson"` - Column3 pgtype.Text `json:"column_3"` +func (q *Queries) GetNullable1(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable1) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable2 = `-- name: GetNullable2 :many +SELECT CASE + WHEN id = 1 THEN id::int + WHEN id = 2 THEN null + WHEN id = 3 THEN 8.5 + WHEN id = 4 THEN 7 + ELSE '2' +END +FROM "mytable" +` + +func (q *Queries) GetNullable2(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable2) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable2A = `-- name: GetNullable2A :many +SELECT CASE + WHEN id = 1 THEN id::int + WHEN id = 2 THEN null + WHEN id = 3 THEN 8.5 + ELSE 7 +END +FROM "mytable" +` + +func (q *Queries) GetNullable2A(ctx context.Context) ([]pgtype.Float8, error) { + rows, err := q.db.Query(ctx, getNullable2A) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Float8 + for rows.Next() { + var column_1 pgtype.Float8 + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable2B = `-- name: GetNullable2B :many +SELECT CASE + WHEN id = 1 THEN id::float + WHEN id = 2 THEN null + ELSE 7 +END +FROM "mytable" +` + +func (q *Queries) GetNullable2B(ctx context.Context) ([]pgtype.Float8, error) { + rows, err := q.db.Query(ctx, getNullable2B) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Float8 + for rows.Next() { + var column_1 pgtype.Float8 + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable2C = `-- name: GetNullable2C :many +SELECT CASE + WHEN id = 1 THEN true + ELSE null + END +FROM "mytable" +` + +func (q *Queries) GetNullable2C(ctx context.Context) ([]pgtype.Bool, error) { + rows, err := q.db.Query(ctx, getNullable2C) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Bool + for rows.Next() { + var column_1 pgtype.Bool + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable2D = `-- name: GetNullable2D :many +SELECT CASE + WHEN id = 2 THEN mt.myjson->'thing1'->>'thing2' + ELSE null + END +FROM "mytable" mt +` + +func (q *Queries) GetNullable2D(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable2D) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable2E = `-- name: GetNullable2E :many +SELECT CASE + WHEN id = 2 THEN mt.myjson->'thing1'->'thing2' + WHEN id = 3 THEN mt.myjson->'thing1' + ELSE null + END +FROM "mytable" mt +` + +func (q *Queries) GetNullable2E(ctx context.Context) ([]interface{}, error) { + rows, err := q.db.Query(ctx, getNullable2E) + if err != nil { + return nil, err + } + defer rows.Close() + var items []interface{} + for rows.Next() { + var column_1 interface{} + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable2F = `-- name: GetNullable2F :many +SELECT CASE + WHEN id = 2 THEN null + ELSE 7 - id +END +FROM "mytable" +` + +func (q *Queries) GetNullable2F(ctx context.Context) ([]pgtype.Int4, error) { + rows, err := q.db.Query(ctx, getNullable2F) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Int4 + for rows.Next() { + var column_1 pgtype.Int4 + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable3 = `-- name: GetNullable3 :many +SELECT CASE WHEN true THEN 'hello'::text ELSE null END +FROM "mytable" +` + +func (q *Queries) GetNullable3(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable3) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil } -func (q *Queries) MyGet(ctx context.Context) ([]MyGetRow, error) { - rows, err := q.db.Query(ctx, myGet) +const getNullable4 = `-- name: GetNullable4 :many +SELECT CASE WHEN true THEN 'hello'::text END +FROM "mytable" +` + +func (q *Queries) GetNullable4(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable4) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable5 = `-- name: GetNullable5 :many +SELECT (mt.myjson->'thing1'->'thing2')::text +FROM "mytable" mt +` + +func (q *Queries) GetNullable5(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable5) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable6 = `-- name: GetNullable6 :many +SELECT mt.myjson->'thing1'->>'thing2' +FROM "mytable" mt +` + +func (q *Queries) GetNullable6(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable6) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getNullable7 = `-- name: GetNullable7 :many +SELECT mt.myjson->'thing1'->'thing2' +FROM "mytable" mt +` + +func (q *Queries) GetNullable7(ctx context.Context) ([]interface{}, error) { + rows, err := q.db.Query(ctx, getNullable7) if err != nil { return nil, err } defer rows.Close() - var items []MyGetRow + var items []interface{} for rows.Next() { - var i MyGetRow - if err := rows.Scan(&i.ID, &i.Myjson, &i.Column3); err != nil { + var column_1 interface{} + if err := rows.Scan(&column_1); err != nil { return nil, err } - items = append(items, i) + items = append(items, column_1) } if err := rows.Err(); err != nil { return nil, err diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql index db68a8c371..ea1e520b0f 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql @@ -1,3 +1,79 @@ --- name: MyGet :many -SELECT *, (mt.myjson->'thing1'->'thing2')::text -FROM mytable mt; +-- name: GetNullable1 :many +SELECT null::text +FROM "mytable"; + +-- name: GetNullable2 :many +SELECT CASE + WHEN id = 1 THEN id::int + WHEN id = 2 THEN null + WHEN id = 3 THEN 8.5 + WHEN id = 4 THEN 7 + ELSE '2' +END +FROM "mytable"; + +-- name: GetNullable3 :many +SELECT CASE WHEN true THEN 'hello'::text ELSE null END +FROM "mytable"; + +-- name: GetNullable4 :many +SELECT CASE WHEN true THEN 'hello'::text END +FROM "mytable"; + +-- name: GetNullable5 :many +SELECT (mt.myjson->'thing1'->'thing2')::text +FROM "mytable" mt; + +-- name: GetNullable6 :many +SELECT mt.myjson->'thing1'->>'thing2' +FROM "mytable" mt; + +-- name: GetNullable7 :many +SELECT mt.myjson->'thing1'->'thing2' +FROM "mytable" mt; + +-- name: GetNullable2A :many +SELECT CASE + WHEN id = 1 THEN id::int + WHEN id = 2 THEN null + WHEN id = 3 THEN 8.5 + ELSE 7 +END +FROM "mytable"; + +-- name: GetNullable2B :many +SELECT CASE + WHEN id = 1 THEN id::float + WHEN id = 2 THEN null + ELSE 7 +END +FROM "mytable"; + +-- name: GetNullable2C :many +SELECT CASE + WHEN id = 1 THEN true + ELSE null + END +FROM "mytable"; + +-- name: GetNullable2D :many +SELECT CASE + WHEN id = 2 THEN mt.myjson->'thing1'->>'thing2' + ELSE null + END +FROM "mytable" mt; + +-- name: GetNullable2E :many +SELECT CASE + WHEN id = 2 THEN mt.myjson->'thing1'->'thing2' + WHEN id = 3 THEN mt.myjson->'thing1' + ELSE null + END +FROM "mytable" mt; + +-- name: GetNullable2F :many +SELECT CASE + WHEN id = 2 THEN null + ELSE 7 - id +END +FROM "mytable"; \ No newline at end of file diff --git a/internal/sql/lang/operator.go b/internal/sql/lang/operator.go index 99ec892c13..12cc661c31 100644 --- a/internal/sql/lang/operator.go +++ b/internal/sql/lang/operator.go @@ -53,3 +53,13 @@ func IsJSONOperator(s string) bool { } return true } + +func IsJSONResultAsText(s string) bool { + switch s { + case "->>": + case "#>>": + default: + return false + } + return true +} From f154a2fbf450bfa673e71fafda93086234ee90e2 Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Thu, 12 Dec 2024 15:18:05 +0700 Subject: [PATCH 06/10] fix: change managed-db to base --- .../postgresql/pgx/exec.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/exec.json b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/exec.json index ee1b7ecd9e..2e996ca79d 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/exec.json +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/exec.json @@ -1,3 +1,3 @@ { - "contexts": ["managed-db"] + "contexts": ["base"] } From 822e73fb30eaca38b41f8b585da4deaee0522ad5 Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Fri, 13 Dec 2024 07:16:09 +0700 Subject: [PATCH 07/10] test: add null::text as default case expr --- .../postgresql/pgx/go/query.sql.go | 25 +++++++++++++++++++ .../postgresql/pgx/query.sql | 4 +++ 2 files changed, 29 insertions(+) diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go index 6e1a22a41e..c1a952c3cf 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go @@ -264,6 +264,31 @@ func (q *Queries) GetNullable3(ctx context.Context) ([]pgtype.Text, error) { return items, nil } +const getNullable3B = `-- name: GetNullable3B :many +SELECT CASE WHEN true THEN 'hello'::text ELSE null::text END +FROM "mytable" +` + +func (q *Queries) GetNullable3B(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable3B) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getNullable4 = `-- name: GetNullable4 :many SELECT CASE WHEN true THEN 'hello'::text END FROM "mytable" diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql index ea1e520b0f..8a4802ddc1 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql @@ -16,6 +16,10 @@ FROM "mytable"; SELECT CASE WHEN true THEN 'hello'::text ELSE null END FROM "mytable"; +-- name: GetNullable3B :many +SELECT CASE WHEN true THEN 'hello'::text ELSE null::text END +FROM "mytable"; + -- name: GetNullable4 :many SELECT CASE WHEN true THEN 'hello'::text END FROM "mytable"; From e7ddabf111cb76ac7999fb01f1faaf8c4f404000 Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Fri, 13 Dec 2024 07:36:29 +0700 Subject: [PATCH 08/10] test: changing author email From 409d2fbdf81ebb483ed9ba58cd935b3d4e4119f1 Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Thu, 19 Dec 2024 16:47:55 +0700 Subject: [PATCH 09/10] fix: change type precedence auto-selection to data type any, to allow user more freedom to choose query column type --- internal/compiler/output_columns.go | 65 +++++++++++++------ .../postgresql/pgx/go/query.sql.go | 46 ++++++++++--- .../postgresql/pgx/query.sql | 7 ++ 3 files changed, 88 insertions(+), 30 deletions(-) diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index 8c2c39ddd6..f123fe8fe7 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -132,7 +132,11 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er if res.Name != nil { name = *res.Name } - cols = append(cols, convertAConstToColumn(n, name)) + col := convertAConstToColumn(n, name) + if col.DataType == "null" { + col.DataType = "any" + } + cols = append(cols, col) case *ast.A_Expr: name := "" @@ -164,50 +168,67 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er cols = append(cols, &Column{Name: name, DataType: "bool", NotNull: notNull}) case *ast.CaseExpr: - name := "" + var name string if res.Name != nil { name = *res.Name } - typePrecedence := map[string]int{ - "any": 0, - "bool": 1, - "int": 2, - "pg_catalog.int4": 2, - "float": 3, - "pg_catalog.float8": 3, - "text": 4, - } - - chosenType := "any" + chosenType := "" chosenNullable := false + for _, i := range n.Args.Items { cw := i.(*ast.CaseWhen) - col, err := convertCaseExprCondToColumn(cw.Result, res.Name) + col, err := convertCaseExprCondToColumn(cw.Result, &name) if err != nil { return nil, err } - if typePrecedence[col.DataType] > typePrecedence[chosenType] { - chosenType = col.DataType + if col.DataType == "null" { + // we don't choose type from this column if its value is null, only choose nullability + chosenNullable = true + continue + } + if col.DataType != chosenType { + if chosenType == "" { + chosenType = col.DataType + } else { + chosenType = "any" + } } if !col.NotNull { chosenNullable = true } } - if n.Defresult != nil { - defaultCol, err := convertCaseExprCondToColumn(n.Defresult, res.Name) + var defaultCol *Column + if n.Defresult.Pos() != 0 { + defaultCol, err = convertCaseExprCondToColumn(n.Defresult, &name) if err != nil { return nil, err } - if typePrecedence[defaultCol.DataType] > typePrecedence[chosenType] { - chosenType = defaultCol.DataType + } else { + defaultCol = &Column{Name: name, DataType: "null", NotNull: false} + } + + if defaultCol.DataType == "null" { + // we don't choose type from this column if its value is null, only choose nullability + chosenNullable = true + } else { + if defaultCol.DataType != chosenType { + if chosenType == "" { + chosenType = defaultCol.DataType + } else { + chosenType = "any" + } } if !defaultCol.NotNull { chosenNullable = true } } + if chosenType == "" { + chosenType = "any" + } + chosenColumn := &Column{Name: name, DataType: chosenType, NotNull: !chosenNullable} cols = append(cols, chosenColumn) @@ -811,7 +832,7 @@ func convertAExprToColumn(aexpr *ast.A_Expr, name string) *Column { return col } -func convertAConstToColumn(aconst *ast.A_Const, name string) *Column { +func convertAConstToColumn(aconst *ast.A_Const, name string) (*Column) { var col *Column switch aconst.Val.(type) { case *ast.String: @@ -822,6 +843,8 @@ func convertAConstToColumn(aconst *ast.A_Const, name string) *Column { col = &Column{Name: name, DataType: "float", NotNull: true} case *ast.Boolean: col = &Column{Name: name, DataType: "bool", NotNull: true} + case *ast.Null: + col = &Column{Name: name, DataType: "null", NotNull: false} default: col = &Column{Name: name, DataType: "any", NotNull: false} } diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go index c1a952c3cf..58f3a86a1c 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go @@ -47,15 +47,15 @@ END FROM "mytable" ` -func (q *Queries) GetNullable2(ctx context.Context) ([]pgtype.Text, error) { +func (q *Queries) GetNullable2(ctx context.Context) ([]interface{}, error) { rows, err := q.db.Query(ctx, getNullable2) if err != nil { return nil, err } defer rows.Close() - var items []pgtype.Text + var items []interface{} for rows.Next() { - var column_1 pgtype.Text + var column_1 interface{} if err := rows.Scan(&column_1); err != nil { return nil, err } @@ -77,15 +77,15 @@ END FROM "mytable" ` -func (q *Queries) GetNullable2A(ctx context.Context) ([]pgtype.Float8, error) { +func (q *Queries) GetNullable2A(ctx context.Context) ([]interface{}, error) { rows, err := q.db.Query(ctx, getNullable2A) if err != nil { return nil, err } defer rows.Close() - var items []pgtype.Float8 + var items []interface{} for rows.Next() { - var column_1 pgtype.Float8 + var column_1 interface{} if err := rows.Scan(&column_1); err != nil { return nil, err } @@ -106,15 +106,15 @@ END FROM "mytable" ` -func (q *Queries) GetNullable2B(ctx context.Context) ([]pgtype.Float8, error) { +func (q *Queries) GetNullable2B(ctx context.Context) ([]interface{}, error) { rows, err := q.db.Query(ctx, getNullable2B) if err != nil { return nil, err } defer rows.Close() - var items []pgtype.Float8 + var items []interface{} for rows.Next() { - var column_1 pgtype.Float8 + var column_1 interface{} if err := rows.Scan(&column_1); err != nil { return nil, err } @@ -239,6 +239,34 @@ func (q *Queries) GetNullable2F(ctx context.Context) ([]pgtype.Int4, error) { return items, nil } +const getNullable2G = `-- name: GetNullable2G :many +SELECT CASE + WHEN id = 2 THEN null::text + ELSE null +END +FROM "mytable" +` + +func (q *Queries) GetNullable2G(ctx context.Context) ([]pgtype.Text, error) { + rows, err := q.db.Query(ctx, getNullable2G) + if err != nil { + return nil, err + } + defer rows.Close() + var items []pgtype.Text + for rows.Next() { + var column_1 pgtype.Text + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getNullable3 = `-- name: GetNullable3 :many SELECT CASE WHEN true THEN 'hello'::text ELSE null END FROM "mytable" diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql index 8a4802ddc1..c5dccf6312 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql @@ -80,4 +80,11 @@ SELECT CASE WHEN id = 2 THEN null ELSE 7 - id END +FROM "mytable"; + +-- name: GetNullable2G :many +SELECT CASE + WHEN id = 2 THEN null + ELSE null +END FROM "mytable"; \ No newline at end of file From 19ab64230cc3c03f6e29eb96ecbf8cada50df75d Mon Sep 17 00:00:00 2001 From: Pulung Ragil Date: Thu, 19 Dec 2024 17:02:18 +0700 Subject: [PATCH 10/10] test: uncommitted test case for GetNullable2G --- .../postgresql/pgx/go/query.sql.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go index 58f3a86a1c..be9edd4c42 100644 --- a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go @@ -241,21 +241,21 @@ func (q *Queries) GetNullable2F(ctx context.Context) ([]pgtype.Int4, error) { const getNullable2G = `-- name: GetNullable2G :many SELECT CASE - WHEN id = 2 THEN null::text + WHEN id = 2 THEN null ELSE null END FROM "mytable" ` -func (q *Queries) GetNullable2G(ctx context.Context) ([]pgtype.Text, error) { +func (q *Queries) GetNullable2G(ctx context.Context) ([]interface{}, error) { rows, err := q.db.Query(ctx, getNullable2G) if err != nil { return nil, err } defer rows.Close() - var items []pgtype.Text + var items []interface{} for rows.Next() { - var column_1 pgtype.Text + var column_1 interface{} if err := rows.Scan(&column_1); err != nil { return nil, err }