Skip to content

Commit 9d6b46c

Browse files
committed
fix(mysql): Convert boolean literals (true/false) to bool type
Previously, MySQL boolean literals like `SELECT true` were incorrectly converted to int32 type, while table columns with BOOL/BOOLEAN/TINYINT(1) types were correctly converted to bool. The TiDB parser represents boolean literals as mysql.TypeLonglong with value 1 (true) or 0 (false), which was indistinguishable from integer literals like `SELECT 1`. This fix: - Detects boolean literals by examining the original SQL text - Converts `true`/`false` (case-insensitive) to bool type - Preserves integer literals like `SELECT 1` as int32 - Supports all case variations: TRUE, True, FALSE, False, etc. Test cases: - SELECT true/false → bool - SELECT TRUE/FALSE → bool - SELECT true AS col → bool - SELECT 1/0 → int32 (unchanged)
1 parent 2e0435c commit 9d6b46c

File tree

7 files changed

+163
-1
lines changed

7 files changed

+163
-1
lines changed

internal/endtoend/testdata/select_true_literal/mysql/db/db.go

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/select_true_literal/mysql/db/models.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/select_true_literal/mysql/db/query.sql.go

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- name: SelectTrue :one
2+
SELECT true;
3+
4+
-- name: SelectFalse :one
5+
SELECT false;
6+
7+
-- name: SelectTrueWithAlias :one
8+
SELECT true AS is_active;
9+
10+
-- name: SelectMultipleBooleans :one
11+
SELECT true AS col_a, false AS col_b, true AS col_c;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"version": "1",
3+
"packages": [
4+
{
5+
"path": "db",
6+
"engine": "mysql",
7+
"queries": "query.sql"
8+
}
9+
]
10+
}

internal/engine/dolphin/convert.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
type cc struct {
1919
paramCount int
20+
sql string // Original SQL text for this statement
2021
}
2122

2223
func todo(n pcast.Node) *ast.TODO {
@@ -672,6 +673,41 @@ func (c *cc) convertUpdateStmt(n *pcast.UpdateStmt) *ast.UpdateStmt {
672673
return stmt
673674
}
674675

676+
// isBooleanLiteral checks if the ValueExpr represents a boolean literal (true/false)
677+
// by examining the original SQL text
678+
func (c *cc) isBooleanLiteral(n *driver.ValueExpr) bool {
679+
if c.sql == "" {
680+
return false
681+
}
682+
683+
pos := n.OriginTextPosition()
684+
if pos < 0 || pos >= len(c.sql) {
685+
return false
686+
}
687+
688+
// Extract the token from the SQL text
689+
remaining := strings.ToLower(c.sql[pos:])
690+
691+
// Check if it starts with "true" or "false" and is followed by a non-identifier character
692+
if strings.HasPrefix(remaining, "true") {
693+
if len(remaining) == 4 || !isIdentifierChar(remaining[4]) {
694+
return true
695+
}
696+
}
697+
if strings.HasPrefix(remaining, "false") {
698+
if len(remaining) == 5 || !isIdentifierChar(remaining[5]) {
699+
return true
700+
}
701+
}
702+
703+
return false
704+
}
705+
706+
// isIdentifierChar returns true if the character can be part of an identifier
707+
func isIdentifierChar(c byte) bool {
708+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'
709+
}
710+
675711
func (c *cc) convertValueExpr(n *driver.ValueExpr) *ast.A_Const {
676712
switch n.TexprNode.Type.GetType() {
677713
case mysql.TypeBit:
@@ -691,6 +727,15 @@ func (c *cc) convertValueExpr(n *driver.ValueExpr) *ast.A_Const {
691727
mysql.TypeYear,
692728
mysql.TypeLong,
693729
mysql.TypeLonglong:
730+
// Check if this is a boolean literal (true/false)
731+
if c.isBooleanLiteral(n) {
732+
return &ast.A_Const{
733+
Val: &ast.Boolean{
734+
Boolval: n.Datum.GetInt64() != 0,
735+
},
736+
Location: n.OriginTextPosition(),
737+
}
738+
}
694739
return &ast.A_Const{
695740
Val: &ast.Integer{
696741
Ival: n.Datum.GetInt64(),

internal/engine/dolphin/parse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
5959
}
6060
var stmts []ast.Statement
6161
for i := range stmtNodes {
62-
converter := &cc{}
62+
converter := &cc{sql: string(blob)}
6363
out := converter.convert(stmtNodes[i])
6464
if _, ok := out.(*ast.TODO); ok {
6565
continue

0 commit comments

Comments
 (0)