diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index dbdbe252b3..f123fe8fe7 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -132,33 +132,18 @@ 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}) + col := convertAConstToColumn(n, name) + if col.DataType == "null" { + col.DataType = "any" } + cols = append(cols, col) 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 := "" @@ -183,44 +168,70 @@ 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 } - // 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") + + chosenType := "" + chosenNullable := false + + for _, i := range n.Args.Items { + cw := i.(*ast.CaseWhen) + col, err := convertCaseExprCondToColumn(cw.Result, &name) + if err != nil { + return nil, err + } + if col.DataType == "null" { + // we don't choose type from this column if its value is null, only choose nullability + chosenNullable = true + continue } - name := "" - if ref, ok := tc.Arg.(*ast.ColumnRef); ok { - name = astutils.Join(ref.Fields, "_") + if col.DataType != chosenType { + if chosenType == "" { + chosenType = col.DataType + } else { + chosenType = "any" + } } - 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}) + } + + var defaultCol *Column + if n.Defresult.Pos() != 0 { + defaultCol, err = convertCaseExprCondToColumn(n.Defresult, &name) + if err != nil { + return nil, err } } else { - cols = append(cols, &Column{Name: name, DataType: "any", NotNull: false}) + 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) + case *ast.CoalesceExpr: name := "coalesce" if res.Name != nil { @@ -256,7 +267,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,6 +376,11 @@ 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: @@ -764,3 +779,74 @@ 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} + case *ast.Null: + col = &Column{Name: name, DataType: "null", NotNull: false} + 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/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/exec.json b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/db.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/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/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/go/models.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/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/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/go/query.sql.go b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go new file mode 100644 index 0000000000..be9edd4c42 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/go/query.sql.go @@ -0,0 +1,418 @@ +// 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 getNullable1 = `-- name: GetNullable1 :many +SELECT null::text +FROM "mytable" +` + +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) ([]interface{}, error) { + rows, err := q.db.Query(ctx, getNullable2) + 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 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) ([]interface{}, error) { + rows, err := q.db.Query(ctx, getNullable2A) + 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 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) ([]interface{}, error) { + rows, err := q.db.Query(ctx, getNullable2B) + 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 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 getNullable2G = `-- name: GetNullable2G :many +SELECT CASE + WHEN id = 2 THEN null + ELSE null +END +FROM "mytable" +` + +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 []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 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 +} + +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" +` + +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 []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 +} 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 new file mode 100644 index 0000000000..c5dccf6312 --- /dev/null +++ b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/query.sql @@ -0,0 +1,90 @@ +-- 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: 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"; + +-- 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"; + +-- name: GetNullable2G :many +SELECT CASE + WHEN id = 2 THEN null + ELSE null +END +FROM "mytable"; \ No newline at end of file diff --git a/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/schema.sql b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/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/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/sqlc.yaml b/internal/endtoend/testdata/json_traversal_as_select_expr_with_type_cast/postgresql/pgx/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/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/sql/lang/operator.go b/internal/sql/lang/operator.go index cd5ef50e38..12cc661c31 100644 --- a/internal/sql/lang/operator.go +++ b/internal/sql/lang/operator.go @@ -41,3 +41,25 @@ func IsMathematicalOperator(s string) bool { } return true } + +func IsJSONOperator(s string) bool { + switch s { + case "->": + case "->>": + case "#>": + case "#>>": + default: + return false + } + return true +} + +func IsJSONResultAsText(s string) bool { + switch s { + case "->>": + case "#>>": + default: + return false + } + return true +}