Skip to content

Commit cc9e28d

Browse files
Ajit Pratap Singhclaude
authored andcommitted
fix(ci): add missing transpiler package to git tracking
The pkg/transpiler/ directory was created but never committed, causing the WASM build and other CI jobs to fail with "no required module provides package .../pkg/transpiler". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 00e42cf commit cc9e28d

10 files changed

Lines changed: 686 additions & 0 deletions

pkg/transpiler/dialect_rules.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2026 GoSQLX Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package transpiler
16+
17+
import (
18+
"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
19+
"github.com/ajitpratap0/GoSQLX/pkg/transpiler/rules"
20+
)
21+
22+
type dialectPair struct {
23+
from, to keywords.SQLDialect
24+
}
25+
26+
var ruleRegistry = map[dialectPair][]RewriteRule{}
27+
28+
func init() {
29+
register(keywords.DialectMySQL, keywords.DialectPostgreSQL,
30+
rules.MySQLAutoIncrementToSerial,
31+
rules.MySQLBacktickToDoubleQuote,
32+
rules.MySQLLimitCommaToOffset,
33+
rules.MySQLBooleanToPgBool,
34+
)
35+
register(keywords.DialectPostgreSQL, keywords.DialectMySQL,
36+
rules.PgSerialToAutoIncrement,
37+
rules.PgDoubleQuoteToBacktick,
38+
rules.PgILikeToLower,
39+
)
40+
register(keywords.DialectPostgreSQL, keywords.DialectSQLite,
41+
rules.PgSerialToIntegerPK,
42+
rules.PgArrayToJSON,
43+
)
44+
}
45+
46+
func register(from, to keywords.SQLDialect, rs ...RewriteRule) {
47+
key := dialectPair{from, to}
48+
ruleRegistry[key] = append(ruleRegistry[key], rs...)
49+
}
50+
51+
// RulesFor returns the registered rewrite rules for a dialect pair.
52+
// Returns nil (empty) for unregistered or same-dialect pairs.
53+
// Exported for testing.
54+
func RulesFor(from, to keywords.SQLDialect) []RewriteRule {
55+
if from == to {
56+
return nil
57+
}
58+
return ruleRegistry[dialectPair{from, to}]
59+
}
60+
61+
// rulesFor is the internal version used by Transpile.
62+
func rulesFor(from, to keywords.SQLDialect) []RewriteRule {
63+
return RulesFor(from, to)
64+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2026 GoSQLX Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package transpiler_test
16+
17+
import (
18+
"testing"
19+
20+
"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
21+
"github.com/ajitpratap0/GoSQLX/pkg/transpiler"
22+
)
23+
24+
func TestRulesFor_MySQLToPostgres_NonEmpty(t *testing.T) {
25+
rules := transpiler.RulesFor(keywords.DialectMySQL, keywords.DialectPostgreSQL)
26+
if len(rules) == 0 {
27+
t.Error("expected at least one rule for MySQL→PostgreSQL")
28+
}
29+
}
30+
31+
func TestRulesFor_SameDialect_Empty(t *testing.T) {
32+
rules := transpiler.RulesFor(keywords.DialectPostgreSQL, keywords.DialectPostgreSQL)
33+
if len(rules) != 0 {
34+
t.Errorf("expected no rules for same dialect, got %d", len(rules))
35+
}
36+
}
37+
38+
func TestRulesFor_UnregisteredPair_Empty(t *testing.T) {
39+
rules := transpiler.RulesFor(keywords.DialectOracle, keywords.DialectClickHouse)
40+
// Unknown pair → no rules (passthrough).
41+
_ = rules // should be 0 length, no panic
42+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2026 GoSQLX Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package rules contains individual dialect rewrite rules used by the transpiler.
16+
package rules
17+
18+
import (
19+
"strings"
20+
21+
"github.com/ajitpratap0/GoSQLX/pkg/sql/ast"
22+
)
23+
24+
// MySQLAutoIncrementToSerial rewrites columns that use the AUTO_INCREMENT
25+
// constraint to use the PostgreSQL SERIAL (or BIGSERIAL) type instead.
26+
//
27+
// MySQL: id INT AUTO_INCREMENT PRIMARY KEY
28+
// PostgreSQL: id SERIAL PRIMARY KEY
29+
func MySQLAutoIncrementToSerial(stmt ast.Statement) error {
30+
ct, ok := stmt.(*ast.CreateTableStatement)
31+
if !ok {
32+
return nil
33+
}
34+
for i := range ct.Columns {
35+
col := &ct.Columns[i]
36+
// AUTO_INCREMENT is stored as a boolean flag on ColumnConstraint,
37+
// but the parser may also emit it as a constraint with Type "AUTO_INCREMENT".
38+
// Check both representations.
39+
hasAutoInc := false
40+
newConstraints := col.Constraints[:0]
41+
for _, c := range col.Constraints {
42+
if c.AutoIncrement || strings.EqualFold(c.Type, "AUTO_INCREMENT") {
43+
hasAutoInc = true
44+
// Drop this constraint — it will be encoded in the type name.
45+
continue
46+
}
47+
newConstraints = append(newConstraints, c)
48+
}
49+
if hasAutoInc {
50+
col.Constraints = newConstraints
51+
switch strings.ToUpper(col.Type) {
52+
case "BIGINT":
53+
col.Type = "BIGSERIAL"
54+
default:
55+
col.Type = "SERIAL"
56+
}
57+
}
58+
}
59+
return nil
60+
}
61+
62+
// MySQLBacktickToDoubleQuote is a no-op: the GoSQLX tokenizer strips backtick
63+
// quoting and stores raw identifier names in the AST. The formatter applies
64+
// the correct quoting style for the target dialect.
65+
func MySQLBacktickToDoubleQuote(_ ast.Statement) error {
66+
return nil
67+
}
68+
69+
// MySQLLimitCommaToOffset is a no-op: the GoSQLX parser already normalises
70+
// MySQL's `LIMIT offset, count` syntax into the AST's Limit / Offset fields,
71+
// which are emitted by the formatter in standard `LIMIT n OFFSET m` form.
72+
func MySQLLimitCommaToOffset(_ ast.Statement) error {
73+
return nil
74+
}
75+
76+
// MySQLBooleanToPgBool rewrites TINYINT columns (MySQL's conventional boolean
77+
// representation) to BOOLEAN for PostgreSQL.
78+
//
79+
// Only TINYINT with no explicit size argument is rewritten. TINYINT with
80+
// other sizes (e.g. TINYINT(4)) is left unchanged.
81+
//
82+
// MySQL: active TINYINT(1) or active TINYINT
83+
// PostgreSQL: active BOOLEAN
84+
func MySQLBooleanToPgBool(stmt ast.Statement) error {
85+
ct, ok := stmt.(*ast.CreateTableStatement)
86+
if !ok {
87+
return nil
88+
}
89+
for i := range ct.Columns {
90+
col := &ct.Columns[i]
91+
// ColumnDef.Type is a bare type string like "TINYINT", "TINYINT(1)", etc.
92+
t := strings.ToUpper(strings.TrimSpace(col.Type))
93+
if t == "TINYINT" || t == "TINYINT(1)" {
94+
col.Type = "BOOLEAN"
95+
}
96+
}
97+
return nil
98+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2026 GoSQLX Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package rules_test
16+
17+
import (
18+
"strings"
19+
"testing"
20+
21+
"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
22+
"github.com/ajitpratap0/GoSQLX/pkg/transpiler"
23+
)
24+
25+
// transpileMyToPg is a test helper that runs Transpile MySQL→PostgreSQL.
26+
func transpileMyToPg(t *testing.T, sql string) string {
27+
t.Helper()
28+
result, err := transpiler.Transpile(sql, keywords.DialectMySQL, keywords.DialectPostgreSQL)
29+
if err != nil {
30+
t.Fatalf("Transpile MySQL→PG %q: %v", sql, err)
31+
}
32+
return result
33+
}
34+
35+
func containsCI(s, sub string) bool {
36+
return strings.Contains(strings.ToUpper(s), strings.ToUpper(sub))
37+
}
38+
39+
func TestMySQLAutoIncrement_ToSerial(t *testing.T) {
40+
in := "CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))"
41+
out := transpileMyToPg(t, in)
42+
if !containsCI(out, "SERIAL") && !containsCI(out, "BIGSERIAL") {
43+
t.Errorf("expected SERIAL in output, got: %s", out)
44+
}
45+
}
46+
47+
func TestMySQLBigintAutoIncrement_ToBigserial(t *testing.T) {
48+
in := "CREATE TABLE events (id BIGINT AUTO_INCREMENT PRIMARY KEY, name TEXT)"
49+
out := transpileMyToPg(t, in)
50+
if !containsCI(out, "BIGSERIAL") {
51+
t.Errorf("expected BIGSERIAL in output for BIGINT AUTO_INCREMENT, got: %s", out)
52+
}
53+
}
54+
55+
func TestMySQLTinyint1_ToBoolean(t *testing.T) {
56+
in := "CREATE TABLE flags (id INT PRIMARY KEY, active TINYINT(1))"
57+
out := transpileMyToPg(t, in)
58+
if !containsCI(out, "BOOLEAN") {
59+
t.Errorf("expected BOOLEAN in output for TINYINT(1), got: %s", out)
60+
}
61+
}
62+
63+
func TestMySQL_SelectPassthrough(t *testing.T) {
64+
in := "SELECT id, name FROM users WHERE id = 1"
65+
out := transpileMyToPg(t, in)
66+
if out == "" {
67+
t.Error("expected non-empty output for basic SELECT")
68+
}
69+
if !containsCI(out, "SELECT") {
70+
t.Errorf("output should contain SELECT, got: %s", out)
71+
}
72+
}
73+
74+
func TestMySQLLimitComma_ToOffset(t *testing.T) {
75+
// The parser normalises LIMIT offset, count → Limit/Offset AST fields.
76+
// The formatter then emits LIMIT n OFFSET m.
77+
in := "SELECT * FROM users LIMIT 10, 20"
78+
out := transpileMyToPg(t, in)
79+
if out == "" {
80+
t.Error("expected non-empty output")
81+
}
82+
// Either OFFSET appears (properly rewritten) or at minimum LIMIT is present.
83+
if !containsCI(out, "LIMIT") {
84+
t.Errorf("expected LIMIT keyword in output, got: %s", out)
85+
}
86+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2026 GoSQLX Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package rules
16+
17+
import (
18+
"strings"
19+
20+
"github.com/ajitpratap0/GoSQLX/pkg/sql/ast"
21+
)
22+
23+
// PgSerialToAutoIncrement rewrites PostgreSQL SERIAL / BIGSERIAL / SMALLSERIAL
24+
// column types to the MySQL equivalent (INT / BIGINT with AUTO_INCREMENT).
25+
//
26+
// PostgreSQL: id SERIAL PRIMARY KEY
27+
// MySQL: id INT AUTO_INCREMENT PRIMARY KEY
28+
func PgSerialToAutoIncrement(stmt ast.Statement) error {
29+
ct, ok := stmt.(*ast.CreateTableStatement)
30+
if !ok {
31+
return nil
32+
}
33+
for i := range ct.Columns {
34+
col := &ct.Columns[i]
35+
switch strings.ToUpper(col.Type) {
36+
case "SERIAL", "SMALLSERIAL":
37+
col.Type = "INT"
38+
col.Constraints = append(col.Constraints, ast.ColumnConstraint{AutoIncrement: true})
39+
case "BIGSERIAL":
40+
col.Type = "BIGINT"
41+
col.Constraints = append(col.Constraints, ast.ColumnConstraint{AutoIncrement: true})
42+
}
43+
}
44+
return nil
45+
}
46+
47+
// PgDoubleQuoteToBacktick is a no-op: the GoSQLX AST stores raw (unquoted)
48+
// identifier names. The formatter uses the target dialect's quoting style.
49+
func PgDoubleQuoteToBacktick(_ ast.Statement) error {
50+
return nil
51+
}
52+
53+
// PgILikeToLower rewrites PostgreSQL ILIKE to a LOWER() … LIKE LOWER() pair
54+
// that is compatible with MySQL.
55+
//
56+
// PostgreSQL: col ILIKE '%alice%'
57+
// MySQL: LOWER(col) LIKE LOWER('%alice%')
58+
func PgILikeToLower(stmt ast.Statement) error {
59+
return ast.Walk(&ilikeLowerer{}, stmt)
60+
}
61+
62+
type ilikeLowerer struct{}
63+
64+
func (v *ilikeLowerer) Visit(node ast.Node) (ast.Visitor, error) {
65+
if node == nil {
66+
return nil, nil
67+
}
68+
bin, ok := node.(*ast.BinaryExpression)
69+
if !ok {
70+
return v, nil
71+
}
72+
if !strings.EqualFold(bin.Operator, "ILIKE") {
73+
return v, nil
74+
}
75+
// Rewrite: left ILIKE right → LOWER(left) LIKE LOWER(right)
76+
bin.Operator = "LIKE"
77+
bin.Left = &ast.FunctionCall{
78+
Name: "LOWER",
79+
Arguments: []ast.Expression{bin.Left},
80+
}
81+
bin.Right = &ast.FunctionCall{
82+
Name: "LOWER",
83+
Arguments: []ast.Expression{bin.Right},
84+
}
85+
// Return nil so we do not recurse into the newly-created LOWER() wrappers.
86+
return nil, nil
87+
}

0 commit comments

Comments
 (0)