Skip to content

Commit e09be65

Browse files
Ajit Pratap Singhclaude
authored andcommitted
feat(formatter): add render handlers for Sequence DDL, SHOW, and DESCRIBE statements (#455)
Add dedicated FormatStatement case arms and render functions for CreateSequenceStatement, AlterSequenceStatement, DropSequenceStatement, ShowStatement, and DescribeStatement so they produce proper SQL output instead of falling back to stmtSQL/TokenLiteral. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7110956 commit e09be65

3 files changed

Lines changed: 382 additions & 0 deletions

File tree

pkg/formatter/render.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ func FormatStatement(s ast.Statement, opts ast.FormatOptions) string {
154154
return renderTruncate(v, opts)
155155
case *ast.MergeStatement:
156156
return renderMerge(v, opts)
157+
case *ast.CreateSequenceStatement:
158+
return renderCreateSequence(v, opts)
159+
case *ast.AlterSequenceStatement:
160+
return renderAlterSequence(v, opts)
161+
case *ast.DropSequenceStatement:
162+
return renderDropSequence(v, opts)
163+
case *ast.ShowStatement:
164+
return renderShow(v, opts)
165+
case *ast.DescribeStatement:
166+
return renderDescribe(v, opts)
157167
default:
158168
// Fallback to SQL() for unrecognized statement types
159169
return stmtSQL(s)

pkg/formatter/render_ddl.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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+
// render_ddl.go - formatter render handlers for DDL statements that have
16+
// dedicated AST nodes but previously fell through to the stmtSQL() fallback.
17+
// Covered: CREATE/ALTER/DROP SEQUENCE, SHOW, DESCRIBE.
18+
19+
package formatter
20+
21+
import (
22+
"strings"
23+
24+
"github.com/ajitpratap0/GoSQLX/pkg/sql/ast"
25+
)
26+
27+
// renderCreateSequence renders a CREATE [OR REPLACE] SEQUENCE [IF NOT EXISTS] statement.
28+
func renderCreateSequence(s *ast.CreateSequenceStatement, opts ast.FormatOptions) string {
29+
f := newNodeFormatter(opts)
30+
sb := f.sb
31+
32+
sb.WriteString(f.kw("CREATE"))
33+
if s.OrReplace {
34+
sb.WriteString(" ")
35+
sb.WriteString(f.kw("OR REPLACE"))
36+
}
37+
sb.WriteString(" ")
38+
sb.WriteString(f.kw("SEQUENCE"))
39+
if s.IfNotExists {
40+
sb.WriteString(" ")
41+
sb.WriteString(f.kw("IF NOT EXISTS"))
42+
}
43+
if s.Name != nil && s.Name.Name != "" {
44+
sb.WriteString(" ")
45+
sb.WriteString(s.Name.Name)
46+
}
47+
writeSequenceOptionsFormatted(sb, s.Options, f)
48+
return sb.String()
49+
}
50+
51+
// renderAlterSequence renders an ALTER SEQUENCE [IF EXISTS] statement.
52+
func renderAlterSequence(s *ast.AlterSequenceStatement, opts ast.FormatOptions) string {
53+
f := newNodeFormatter(opts)
54+
sb := f.sb
55+
56+
sb.WriteString(f.kw("ALTER SEQUENCE"))
57+
if s.IfExists {
58+
sb.WriteString(" ")
59+
sb.WriteString(f.kw("IF EXISTS"))
60+
}
61+
if s.Name != nil && s.Name.Name != "" {
62+
sb.WriteString(" ")
63+
sb.WriteString(s.Name.Name)
64+
}
65+
writeSequenceOptionsFormatted(sb, s.Options, f)
66+
return sb.String()
67+
}
68+
69+
// renderDropSequence renders a DROP SEQUENCE [IF EXISTS] statement.
70+
func renderDropSequence(s *ast.DropSequenceStatement, opts ast.FormatOptions) string {
71+
f := newNodeFormatter(opts)
72+
sb := f.sb
73+
74+
sb.WriteString(f.kw("DROP SEQUENCE"))
75+
if s.IfExists {
76+
sb.WriteString(" ")
77+
sb.WriteString(f.kw("IF EXISTS"))
78+
}
79+
if s.Name != nil && s.Name.Name != "" {
80+
sb.WriteString(" ")
81+
sb.WriteString(s.Name.Name)
82+
}
83+
return sb.String()
84+
}
85+
86+
// renderShow renders a SHOW statement (e.g., SHOW TABLES, SHOW DATABASES, SHOW CREATE TABLE x).
87+
func renderShow(s *ast.ShowStatement, opts ast.FormatOptions) string {
88+
f := newNodeFormatter(opts)
89+
sb := f.sb
90+
91+
sb.WriteString(f.kw("SHOW"))
92+
if s.ShowType != "" {
93+
sb.WriteString(" ")
94+
sb.WriteString(f.kw(strings.ToUpper(s.ShowType)))
95+
}
96+
if s.ObjectName != "" {
97+
sb.WriteString(" ")
98+
sb.WriteString(s.ObjectName)
99+
}
100+
if s.From != "" {
101+
sb.WriteString(" ")
102+
sb.WriteString(f.kw("FROM"))
103+
sb.WriteString(" ")
104+
sb.WriteString(s.From)
105+
}
106+
return sb.String()
107+
}
108+
109+
// renderDescribe renders a DESCRIBE/DESC statement.
110+
func renderDescribe(s *ast.DescribeStatement, opts ast.FormatOptions) string {
111+
f := newNodeFormatter(opts)
112+
sb := f.sb
113+
114+
sb.WriteString(f.kw("DESCRIBE"))
115+
if s.TableName != "" {
116+
sb.WriteString(" ")
117+
sb.WriteString(s.TableName)
118+
}
119+
return sb.String()
120+
}
121+
122+
// writeSequenceOptionsFormatted appends formatted sequence options to the builder.
123+
// It mirrors the logic in ast/sql.go writeSequenceOptions but uses the nodeFormatter
124+
// for keyword casing.
125+
func writeSequenceOptionsFormatted(sb *strings.Builder, opts ast.SequenceOptions, f *nodeFormatter) {
126+
if opts.StartWith != nil {
127+
sb.WriteString(" ")
128+
sb.WriteString(f.kw("START WITH"))
129+
sb.WriteString(" ")
130+
sb.WriteString(opts.StartWith.TokenLiteral())
131+
}
132+
if opts.IncrementBy != nil {
133+
sb.WriteString(" ")
134+
sb.WriteString(f.kw("INCREMENT BY"))
135+
sb.WriteString(" ")
136+
sb.WriteString(opts.IncrementBy.TokenLiteral())
137+
}
138+
if opts.MinValue != nil {
139+
sb.WriteString(" ")
140+
sb.WriteString(f.kw("MINVALUE"))
141+
sb.WriteString(" ")
142+
sb.WriteString(opts.MinValue.TokenLiteral())
143+
}
144+
if opts.MaxValue != nil {
145+
sb.WriteString(" ")
146+
sb.WriteString(f.kw("MAXVALUE"))
147+
sb.WriteString(" ")
148+
sb.WriteString(opts.MaxValue.TokenLiteral())
149+
}
150+
if opts.Cache != nil {
151+
sb.WriteString(" ")
152+
sb.WriteString(f.kw("CACHE"))
153+
sb.WriteString(" ")
154+
sb.WriteString(opts.Cache.TokenLiteral())
155+
} else if opts.NoCache {
156+
sb.WriteString(" ")
157+
sb.WriteString(f.kw("NOCACHE"))
158+
}
159+
switch opts.CycleMode {
160+
case ast.CycleBehavior:
161+
sb.WriteString(" ")
162+
sb.WriteString(f.kw("CYCLE"))
163+
case ast.NoCycleBehavior:
164+
sb.WriteString(" ")
165+
sb.WriteString(f.kw("NOCYCLE"))
166+
}
167+
if opts.RestartWith != nil {
168+
sb.WriteString(" ")
169+
sb.WriteString(f.kw("RESTART WITH"))
170+
sb.WriteString(" ")
171+
sb.WriteString(opts.RestartWith.TokenLiteral())
172+
} else if opts.Restart {
173+
sb.WriteString(" ")
174+
sb.WriteString(f.kw("RESTART"))
175+
}
176+
}

pkg/formatter/render_ddl_test.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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 formatter_test
16+
17+
import (
18+
"strings"
19+
"testing"
20+
21+
"github.com/ajitpratap0/GoSQLX/pkg/formatter"
22+
"github.com/ajitpratap0/GoSQLX/pkg/gosqlx"
23+
"github.com/ajitpratap0/GoSQLX/pkg/sql/ast"
24+
"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
25+
)
26+
27+
// TestFormat_CreateSequence verifies that CREATE SEQUENCE statements are rendered
28+
// by the dedicated formatter arm rather than falling back to stmtSQL/TokenLiteral.
29+
func TestFormat_CreateSequence(t *testing.T) {
30+
sql := "CREATE SEQUENCE user_id_seq START WITH 1 INCREMENT BY 1"
31+
tree, err := gosqlx.ParseWithDialect(sql, keywords.DialectMariaDB)
32+
if err != nil {
33+
t.Fatalf("Parse error: %v", err)
34+
}
35+
if len(tree.Statements) == 0 {
36+
t.Fatal("expected at least one statement")
37+
}
38+
stmt, ok := tree.Statements[0].(*ast.CreateSequenceStatement)
39+
if !ok {
40+
t.Fatalf("expected *ast.CreateSequenceStatement, got %T", tree.Statements[0])
41+
}
42+
43+
opts := ast.FormatOptions{}
44+
result := formatter.FormatStatement(stmt, opts)
45+
upper := strings.ToUpper(result)
46+
if !strings.Contains(upper, "CREATE") {
47+
t.Errorf("expected CREATE in output, got: %q", result)
48+
}
49+
if !strings.Contains(upper, "SEQUENCE") {
50+
t.Errorf("expected SEQUENCE in output, got: %q", result)
51+
}
52+
if !strings.Contains(result, "user_id_seq") {
53+
t.Errorf("expected sequence name in output, got: %q", result)
54+
}
55+
if !strings.Contains(upper, "START WITH") {
56+
t.Errorf("expected START WITH in output, got: %q", result)
57+
}
58+
}
59+
60+
// TestFormat_CreateSequence_IfNotExists verifies IF NOT EXISTS is rendered.
61+
func TestFormat_CreateSequence_IfNotExists(t *testing.T) {
62+
sql := "CREATE SEQUENCE IF NOT EXISTS s2 START WITH 10"
63+
tree, err := gosqlx.ParseWithDialect(sql, keywords.DialectMariaDB)
64+
if err != nil {
65+
t.Fatalf("Parse error: %v", err)
66+
}
67+
stmt, ok := tree.Statements[0].(*ast.CreateSequenceStatement)
68+
if !ok {
69+
t.Fatalf("expected *ast.CreateSequenceStatement, got %T", tree.Statements[0])
70+
}
71+
opts := ast.FormatOptions{}
72+
result := formatter.FormatStatement(stmt, opts)
73+
upper := strings.ToUpper(result)
74+
if !strings.Contains(upper, "IF NOT EXISTS") {
75+
t.Errorf("expected IF NOT EXISTS in output, got: %q", result)
76+
}
77+
}
78+
79+
// TestFormat_AlterSequence verifies ALTER SEQUENCE statements are formatted correctly.
80+
func TestFormat_AlterSequence(t *testing.T) {
81+
sql := "ALTER SEQUENCE user_id_seq RESTART WITH 100"
82+
tree, err := gosqlx.ParseWithDialect(sql, keywords.DialectMariaDB)
83+
if err != nil {
84+
t.Fatalf("Parse error: %v", err)
85+
}
86+
stmt, ok := tree.Statements[0].(*ast.AlterSequenceStatement)
87+
if !ok {
88+
t.Fatalf("expected *ast.AlterSequenceStatement, got %T", tree.Statements[0])
89+
}
90+
opts := ast.FormatOptions{}
91+
result := formatter.FormatStatement(stmt, opts)
92+
upper := strings.ToUpper(result)
93+
if !strings.Contains(upper, "ALTER") {
94+
t.Errorf("expected ALTER in output, got: %q", result)
95+
}
96+
if !strings.Contains(upper, "SEQUENCE") {
97+
t.Errorf("expected SEQUENCE in output, got: %q", result)
98+
}
99+
if !strings.Contains(result, "user_id_seq") {
100+
t.Errorf("expected sequence name in output, got: %q", result)
101+
}
102+
}
103+
104+
// TestFormat_DropSequence verifies DROP SEQUENCE statements are formatted correctly.
105+
func TestFormat_DropSequence(t *testing.T) {
106+
sql := "DROP SEQUENCE IF EXISTS user_id_seq"
107+
tree, err := gosqlx.ParseWithDialect(sql, keywords.DialectMariaDB)
108+
if err != nil {
109+
t.Fatalf("Parse error: %v", err)
110+
}
111+
stmt, ok := tree.Statements[0].(*ast.DropSequenceStatement)
112+
if !ok {
113+
t.Fatalf("expected *ast.DropSequenceStatement, got %T", tree.Statements[0])
114+
}
115+
opts := ast.FormatOptions{}
116+
result := formatter.FormatStatement(stmt, opts)
117+
upper := strings.ToUpper(result)
118+
if !strings.Contains(upper, "DROP") {
119+
t.Errorf("expected DROP in output, got: %q", result)
120+
}
121+
if !strings.Contains(upper, "SEQUENCE") {
122+
t.Errorf("expected SEQUENCE in output, got: %q", result)
123+
}
124+
if !strings.Contains(upper, "IF EXISTS") {
125+
t.Errorf("expected IF EXISTS in output, got: %q", result)
126+
}
127+
if !strings.Contains(result, "user_id_seq") {
128+
t.Errorf("expected sequence name in output, got: %q", result)
129+
}
130+
}
131+
132+
// TestFormat_ShowStatement verifies SHOW statements are formatted correctly.
133+
func TestFormat_ShowStatement(t *testing.T) {
134+
sql := "SHOW TABLES"
135+
tree, err := gosqlx.Parse(sql)
136+
if err != nil {
137+
t.Fatalf("Parse error: %v", err)
138+
}
139+
stmt, ok := tree.Statements[0].(*ast.ShowStatement)
140+
if !ok {
141+
t.Fatalf("expected *ast.ShowStatement, got %T", tree.Statements[0])
142+
}
143+
opts := ast.FormatOptions{}
144+
result := formatter.FormatStatement(stmt, opts)
145+
upper := strings.ToUpper(result)
146+
if !strings.Contains(upper, "SHOW") {
147+
t.Errorf("expected SHOW in output, got: %q", result)
148+
}
149+
if !strings.Contains(upper, "TABLES") {
150+
t.Errorf("expected TABLES in output, got: %q", result)
151+
}
152+
}
153+
154+
// TestFormat_DescribeStatement verifies DESCRIBE statements are formatted correctly.
155+
func TestFormat_DescribeStatement(t *testing.T) {
156+
sql := "DESCRIBE users"
157+
tree, err := gosqlx.Parse(sql)
158+
if err != nil {
159+
t.Fatalf("Parse error: %v", err)
160+
}
161+
stmt, ok := tree.Statements[0].(*ast.DescribeStatement)
162+
if !ok {
163+
t.Fatalf("expected *ast.DescribeStatement, got %T", tree.Statements[0])
164+
}
165+
opts := ast.FormatOptions{}
166+
result := formatter.FormatStatement(stmt, opts)
167+
upper := strings.ToUpper(result)
168+
if !strings.Contains(upper, "DESCRIBE") {
169+
t.Errorf("expected DESCRIBE in output, got: %q", result)
170+
}
171+
if !strings.Contains(result, "users") {
172+
t.Errorf("expected table name 'users' in output, got: %q", result)
173+
}
174+
}
175+
176+
// TestFormat_DDL_KeywordCase verifies that DDL formatter respects keyword casing options.
177+
func TestFormat_DDL_KeywordCase(t *testing.T) {
178+
sql := "DROP SEQUENCE myseq"
179+
tree, err := gosqlx.ParseWithDialect(sql, keywords.DialectMariaDB)
180+
if err != nil {
181+
t.Fatalf("Parse error: %v", err)
182+
}
183+
stmt := tree.Statements[0].(*ast.DropSequenceStatement)
184+
185+
upperOpts := ast.FormatOptions{KeywordCase: ast.KeywordUpper}
186+
result := formatter.FormatStatement(stmt, upperOpts)
187+
if !strings.Contains(result, "DROP SEQUENCE") {
188+
t.Errorf("expected uppercase keywords, got: %q", result)
189+
}
190+
191+
lowerOpts := ast.FormatOptions{KeywordCase: ast.KeywordLower}
192+
result = formatter.FormatStatement(stmt, lowerOpts)
193+
if !strings.Contains(result, "drop sequence") {
194+
t.Errorf("expected lowercase keywords, got: %q", result)
195+
}
196+
}

0 commit comments

Comments
 (0)