diff --git a/internal/endtoend/testdata/select_true_literal/mysql/db/db.go b/internal/endtoend/testdata/select_true_literal/mysql/db/db.go new file mode 100644 index 0000000000..cd5bbb8e08 --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/mysql/db/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/select_true_literal/mysql/db/models.go b/internal/endtoend/testdata/select_true_literal/mysql/db/models.go new file mode 100644 index 0000000000..32099017df --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/mysql/db/models.go @@ -0,0 +1,5 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db diff --git a/internal/endtoend/testdata/select_true_literal/mysql/db/query.sql.go b/internal/endtoend/testdata/select_true_literal/mysql/db/query.sql.go new file mode 100644 index 0000000000..f90928b2a0 --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/mysql/db/query.sql.go @@ -0,0 +1,60 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: query.sql + +package db + +import ( + "context" +) + +const selectFalse = `-- name: SelectFalse :one +SELECT false +` + +func (q *Queries) SelectFalse(ctx context.Context) (bool, error) { + row := q.db.QueryRowContext(ctx, selectFalse) + var column_1 bool + err := row.Scan(&column_1) + return column_1, err +} + +const selectMultipleBooleans = `-- name: SelectMultipleBooleans :one +SELECT true AS col_a, false AS col_b, true AS col_c +` + +type SelectMultipleBooleansRow struct { + ColA bool + ColB bool + ColC bool +} + +func (q *Queries) SelectMultipleBooleans(ctx context.Context) (SelectMultipleBooleansRow, error) { + row := q.db.QueryRowContext(ctx, selectMultipleBooleans) + var i SelectMultipleBooleansRow + err := row.Scan(&i.ColA, &i.ColB, &i.ColC) + return i, err +} + +const selectTrue = `-- name: SelectTrue :one +SELECT true +` + +func (q *Queries) SelectTrue(ctx context.Context) (bool, error) { + row := q.db.QueryRowContext(ctx, selectTrue) + var column_1 bool + err := row.Scan(&column_1) + return column_1, err +} + +const selectTrueWithAlias = `-- name: SelectTrueWithAlias :one +SELECT true AS is_active +` + +func (q *Queries) SelectTrueWithAlias(ctx context.Context) (bool, error) { + row := q.db.QueryRowContext(ctx, selectTrueWithAlias) + var is_active bool + err := row.Scan(&is_active) + return is_active, err +} diff --git a/internal/endtoend/testdata/select_true_literal/mysql/query.sql b/internal/endtoend/testdata/select_true_literal/mysql/query.sql new file mode 100644 index 0000000000..5c95a13d5d --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/mysql/query.sql @@ -0,0 +1,11 @@ +-- name: SelectTrue :one +SELECT true; + +-- name: SelectFalse :one +SELECT false; + +-- name: SelectTrueWithAlias :one +SELECT true AS is_active; + +-- name: SelectMultipleBooleans :one +SELECT true AS col_a, false AS col_b, true AS col_c; diff --git a/internal/endtoend/testdata/select_true_literal/mysql/sqlc.json b/internal/endtoend/testdata/select_true_literal/mysql/sqlc.json new file mode 100644 index 0000000000..bb491bcc61 --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/mysql/sqlc.json @@ -0,0 +1,10 @@ +{ + "version": "1", + "packages": [ + { + "path": "db", + "engine": "mysql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/select_true_literal/sqlite/db/db.go b/internal/endtoend/testdata/select_true_literal/sqlite/db/db.go new file mode 100644 index 0000000000..cd5bbb8e08 --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/sqlite/db/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/select_true_literal/sqlite/db/models.go b/internal/endtoend/testdata/select_true_literal/sqlite/db/models.go new file mode 100644 index 0000000000..32099017df --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/sqlite/db/models.go @@ -0,0 +1,5 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db diff --git a/internal/endtoend/testdata/select_true_literal/sqlite/db/query.sql.go b/internal/endtoend/testdata/select_true_literal/sqlite/db/query.sql.go new file mode 100644 index 0000000000..e2c27b5403 --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/sqlite/db/query.sql.go @@ -0,0 +1,71 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: query.sql + +package db + +import ( + "context" +) + +const selectFalse = `-- name: SelectFalse :one +SELECT false +` + +func (q *Queries) SelectFalse(ctx context.Context) (bool, error) { + row := q.db.QueryRowContext(ctx, selectFalse) + var column_1 bool + err := row.Scan(&column_1) + return column_1, err +} + +const selectMultipleBooleans = `-- name: SelectMultipleBooleans :one +SELECT true AS col_a, false AS col_b, true AS col_c +` + +type SelectMultipleBooleansRow struct { + ColA bool + ColB bool + ColC bool +} + +func (q *Queries) SelectMultipleBooleans(ctx context.Context) (SelectMultipleBooleansRow, error) { + row := q.db.QueryRowContext(ctx, selectMultipleBooleans) + var i SelectMultipleBooleansRow + err := row.Scan(&i.ColA, &i.ColB, &i.ColC) + return i, err +} + +const selectOne = `-- name: SelectOne :one +SELECT 1 +` + +func (q *Queries) SelectOne(ctx context.Context) (int64, error) { + row := q.db.QueryRowContext(ctx, selectOne) + var column_1 int64 + err := row.Scan(&column_1) + return column_1, err +} + +const selectTrue = `-- name: SelectTrue :one +SELECT true +` + +func (q *Queries) SelectTrue(ctx context.Context) (bool, error) { + row := q.db.QueryRowContext(ctx, selectTrue) + var column_1 bool + err := row.Scan(&column_1) + return column_1, err +} + +const selectTrueWithAlias = `-- name: SelectTrueWithAlias :one +SELECT true AS is_active +` + +func (q *Queries) SelectTrueWithAlias(ctx context.Context) (bool, error) { + row := q.db.QueryRowContext(ctx, selectTrueWithAlias) + var is_active bool + err := row.Scan(&is_active) + return is_active, err +} diff --git a/internal/endtoend/testdata/select_true_literal/sqlite/query.sql b/internal/endtoend/testdata/select_true_literal/sqlite/query.sql new file mode 100644 index 0000000000..b667538f7c --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/sqlite/query.sql @@ -0,0 +1,14 @@ +-- name: SelectTrue :one +SELECT true; + +-- name: SelectFalse :one +SELECT false; + +-- name: SelectTrueWithAlias :one +SELECT true AS is_active; + +-- name: SelectMultipleBooleans :one +SELECT true AS col_a, false AS col_b, true AS col_c; + +-- name: SelectOne :one +SELECT 1; diff --git a/internal/endtoend/testdata/select_true_literal/sqlite/sqlc.json b/internal/endtoend/testdata/select_true_literal/sqlite/sqlc.json new file mode 100644 index 0000000000..b00ce26c02 --- /dev/null +++ b/internal/endtoend/testdata/select_true_literal/sqlite/sqlc.json @@ -0,0 +1,10 @@ +{ + "version": "1", + "packages": [ + { + "path": "db", + "engine": "sqlite", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/selectstatic/mysql/go/query.sql.go b/internal/endtoend/testdata/selectstatic/mysql/go/query.sql.go index c6cfed9b63..f269f37f22 100644 --- a/internal/endtoend/testdata/selectstatic/mysql/go/query.sql.go +++ b/internal/endtoend/testdata/selectstatic/mysql/go/query.sql.go @@ -17,7 +17,7 @@ type SelectStaticRow struct { Column1 string B string Num int32 - Truefield int32 + Truefield bool Floater float64 } diff --git a/internal/engine/dolphin/convert.go b/internal/engine/dolphin/convert.go index 1f68358ce4..03424e45e0 100644 --- a/internal/engine/dolphin/convert.go +++ b/internal/engine/dolphin/convert.go @@ -17,6 +17,7 @@ import ( type cc struct { paramCount int + sql string // Original SQL text for this statement } func todo(n pcast.Node) *ast.TODO { @@ -672,6 +673,41 @@ func (c *cc) convertUpdateStmt(n *pcast.UpdateStmt) *ast.UpdateStmt { return stmt } +// isBooleanLiteral checks if the ValueExpr represents a boolean literal (true/false) +// by examining the original SQL text +func (c *cc) isBooleanLiteral(n *driver.ValueExpr) bool { + if c.sql == "" { + return false + } + + pos := n.OriginTextPosition() + if pos < 0 || pos >= len(c.sql) { + return false + } + + // Extract the token from the SQL text + remaining := strings.ToLower(c.sql[pos:]) + + // Check if it starts with "true" or "false" and is followed by a non-identifier character + if strings.HasPrefix(remaining, "true") { + if len(remaining) == 4 || !isIdentifierChar(remaining[4]) { + return true + } + } + if strings.HasPrefix(remaining, "false") { + if len(remaining) == 5 || !isIdentifierChar(remaining[5]) { + return true + } + } + + return false +} + +// isIdentifierChar returns true if the character can be part of an identifier +func isIdentifierChar(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' +} + func (c *cc) convertValueExpr(n *driver.ValueExpr) *ast.A_Const { switch n.TexprNode.Type.GetType() { case mysql.TypeBit: @@ -691,6 +727,15 @@ func (c *cc) convertValueExpr(n *driver.ValueExpr) *ast.A_Const { mysql.TypeYear, mysql.TypeLong, mysql.TypeLonglong: + // Check if this is a boolean literal (true/false) + if c.isBooleanLiteral(n) { + return &ast.A_Const{ + Val: &ast.Boolean{ + Boolval: n.Datum.GetInt64() != 0, + }, + Location: n.OriginTextPosition(), + } + } return &ast.A_Const{ Val: &ast.Integer{ Ival: n.Datum.GetInt64(), diff --git a/internal/engine/dolphin/parse.go b/internal/engine/dolphin/parse.go index 537f7ad64f..f4f8a81d63 100644 --- a/internal/engine/dolphin/parse.go +++ b/internal/engine/dolphin/parse.go @@ -59,7 +59,7 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) { } var stmts []ast.Statement for i := range stmtNodes { - converter := &cc{} + converter := &cc{sql: string(blob)} out := converter.convert(stmtNodes[i]) if _, ok := out.(*ast.TODO); ok { continue diff --git a/internal/engine/sqlite/convert.go b/internal/engine/sqlite/convert.go index e9868f5be6..a95bec9e4c 100644 --- a/internal/engine/sqlite/convert.go +++ b/internal/engine/sqlite/convert.go @@ -752,13 +752,8 @@ func (c *cc) convertLiteral(n *parser.Expr_literalContext) ast.Node { } if literal.TRUE_() != nil || literal.FALSE_() != nil { - var i int64 - if literal.TRUE_() != nil { - i = 1 - } - return &ast.A_Const{ - Val: &ast.Integer{Ival: i}, + Val: &ast.Boolean{Boolval: literal.TRUE_() != nil}, Location: n.GetStart().GetStart(), } }