From b84d9a56f88daaeb2b5dd14c17ce7110c3a9797b Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:31:39 +0900 Subject: [PATCH 01/18] WIP pretty printing --- ast/sql.go | 155 +++++++++++++++++++++++++++++++++++--------- tools/parse/main.go | 11 +++- 2 files changed, 133 insertions(+), 33 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index eedd404a..9eff0352 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -7,6 +7,86 @@ import ( "github.com/cloudspannerecosystem/memefish/token" ) +type FormatOption struct { + Newline bool + Indent int +} + +var emptyFormatContext = &FormatContext{ + Option: FormatOption{ + Newline: false, + Indent: 0, + }, + Current: 0, +} + +type FormatContext struct { + Option FormatOption + Current int +} + +func (fc *FormatContext) SQL(node Node) string { + if nodeFormat, ok := node.(NodeFormat); fc != nil && ok { + return nodeFormat.SQLContext(fc) + } else { + return node.SQL() + } +} + +func (fc *FormatContext) Newline() string { + if fc != nil && fc.Option.Newline { + return "\n" + strings.Repeat(" ", fc.Current) + } + return " " +} + +func (fc *FormatContext) NewlineOrEmpty() string { + if fc != nil && fc.Option.Newline { + return "\n" + strings.Repeat(" ", fc.Current) + } + return "" +} + +func (fc *FormatContext) WithIndent(f func(fc *FormatContext) string) string { + var newFc FormatContext + if fc != nil { + newFc = *fc + newFc.Current += newFc.Option.Indent + return f(&newFc) + } else { + return f(emptyFormatContext) + } +} + +func sqlOptCtx[T interface { + Node + comparable +}](fc *FormatContext, left string, node T, right string) string { + var zero T + if node == zero { + return "" + } + return left + fc.SQL(node) + right +} + +// sqlJoin outputs joined string of SQL() of all elems by sep. +// This function corresponds to sqlJoin in ast.go +func sqlJoinCtx[T Node](fc *FormatContext, elems []T, sep string) string { + var b strings.Builder + for i, r := range elems { + if i > 0 { + b.WriteString(sep) + } + b.WriteString(fc.SQL(r)) + } + return b.String() +} + +type NodeFormat interface { + Node + SQLContext(fmtCtx *FormatContext) string +} + // ================================================================================ // // Helper functions for SQL() @@ -134,16 +214,14 @@ func paren(p prec, e Expr) string { // // ================================================================================ +func (q *QueryStatement) SQLContext(fc *FormatContext) string { + return sqlOptCtx(fc, "", q.Hint, fc.Newline()) + + sqlOptCtx(fc, "", q.With, fc.Newline()) + + fc.SQL(q.Query) +} + func (q *QueryStatement) SQL() string { - var sql string - if q.Hint != nil { - sql += q.Hint.SQL() + " " - } - if q.With != nil { - sql += q.With.SQL() + " " - } - sql += q.Query.SQL() - return sql + return q.SQLContext(nil) } func (h *Hint) SQL() string { @@ -171,17 +249,23 @@ func (c *CTE) SQL() string { return c.Name.SQL() + " AS (" + c.QueryExpr.SQL() + ")" } -func (s *Select) SQL() string { +func (s *Select) SQLContext(fc *FormatContext) string { return "SELECT " + strOpt(s.Distinct, "DISTINCT ") + - sqlOpt("", s.As, " ") + - sqlJoin(s.Results, ", ") + - sqlOpt(" ", s.From, "") + - sqlOpt(" ", s.Where, "") + - sqlOpt(" ", s.GroupBy, "") + - sqlOpt(" ", s.Having, "") + - sqlOpt(" ", s.OrderBy, "") + - sqlOpt(" ", s.Limit, "") + sqlOptCtx(fc, "", s.As, " ") + + fc.WithIndent(func(fc *FormatContext) string { + return sqlJoinCtx(fc, s.Results, ","+fc.Newline()) + }) + + sqlOptCtx(fc, fc.Newline(), s.From, "") + + sqlOptCtx(fc, fc.Newline(), s.Where, "") + + sqlOptCtx(fc, fc.Newline(), s.GroupBy, "") + + sqlOptCtx(fc, fc.Newline(), s.Having, "") + + sqlOptCtx(fc, fc.Newline(), s.OrderBy, "") + + sqlOptCtx(fc, fc.Newline(), s.Limit, "") +} + +func (s *Select) SQL() string { + return s.SQLContext(nil) } func (a *AsStruct) SQL() string { return "AS STRUCT" } @@ -242,8 +326,11 @@ func (e *ExprSelectItem) SQL() string { return e.Expr.SQL() } +func (f *From) SQLContext(fc *FormatContext) string { + return "FROM " + fc.SQL(f.Source) +} func (f *From) SQL() string { - return "FROM " + f.Source.SQL() + return f.SQLContext(nil) } func (w *Where) SQL() string { @@ -341,23 +428,27 @@ func (e *PathTableExpr) SQL() string { sqlOpt(" ", e.Sample, "") } +func (s *SubQueryTableExpr) SQLContext(fc *FormatContext) string { + return "(" + fc.WithIndent( + func(fc *FormatContext) string { + return fc.NewlineOrEmpty() + fc.SQL(s.Query) + }) + fc.Newline() + ")" + + sqlOptCtx(fc, " ", s.As, "") + + sqlOptCtx(fc, " ", s.Sample, "") +} + func (s *SubQueryTableExpr) SQL() string { - sql := "(" + s.Query.SQL() + ")" - if s.As != nil { - sql += " " + s.As.SQL() - } - if s.Sample != nil { - sql += " " + s.Sample.SQL() - } - return sql + return s.SQLContext(nil) +} + +func (p *ParenTableExpr) SQLContext(fc *FormatContext) string { + return "(" + fc.WithIndent(func(fc *FormatContext) string { + return fc.NewlineOrEmpty() + p.Source.SQL() + }) + fc.NewlineOrEmpty() + ")" + sqlOpt(" ", p.Sample, "") } func (p *ParenTableExpr) SQL() string { - sql := "(" + p.Source.SQL() + ")" - if p.Sample != nil { - sql += " " + p.Sample.SQL() - } - return sql + return p.SQLContext(nil) } func (j *Join) SQL() string { diff --git a/tools/parse/main.go b/tools/parse/main.go index 17d4a6ac..642ac184 100644 --- a/tools/parse/main.go +++ b/tools/parse/main.go @@ -11,11 +11,12 @@ import ( "unicode" "github.com/MakeNowJust/heredoc/v2" + "github.com/k0kubun/pp" + "github.com/cloudspannerecosystem/memefish" "github.com/cloudspannerecosystem/memefish/ast" "github.com/cloudspannerecosystem/memefish/token" "github.com/cloudspannerecosystem/memefish/tools/util/poslang" - "github.com/k0kubun/pp" ) var usage = heredoc.Doc(` @@ -118,6 +119,14 @@ func main() { fmt.Println() fmt.Println("--- SQL") fmt.Println(node.SQL()) + fmt.Println("--- SQL with indentation") + fmt.Println((&ast.FormatContext{ + Option: ast.FormatOption{ + Newline: true, + Indent: 2, + }, + Current: 0, + }).SQL(node)) if *pos != "" { fmt.Println("--- POS") From ae37ab7b023c0dde17365cbb0f6d7fa2115feaba Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:36:06 +0900 Subject: [PATCH 02/18] Update --- ast/sql.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 9eff0352..2cf6ff13 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -250,11 +250,11 @@ func (c *CTE) SQL() string { } func (s *Select) SQLContext(fc *FormatContext) string { - return "SELECT " + - strOpt(s.Distinct, "DISTINCT ") + - sqlOptCtx(fc, "", s.As, " ") + + return "SELECT" + + strOpt(s.Distinct, " DISTINCT ") + + sqlOptCtx(fc, " ", s.As, "") + fc.WithIndent(func(fc *FormatContext) string { - return sqlJoinCtx(fc, s.Results, ","+fc.Newline()) + return fc.Newline() + sqlJoinCtx(fc, s.Results, ","+fc.Newline()) }) + sqlOptCtx(fc, fc.Newline(), s.From, "") + sqlOptCtx(fc, fc.Newline(), s.Where, "") + From 0177d8f59d19d6da6455a25a8d3205f7648a7787 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 17 Dec 2024 21:58:37 +0900 Subject: [PATCH 03/18] Update --- ast/sql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ast/sql.go b/ast/sql.go index 2cf6ff13..b0da5498 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -432,7 +432,7 @@ func (s *SubQueryTableExpr) SQLContext(fc *FormatContext) string { return "(" + fc.WithIndent( func(fc *FormatContext) string { return fc.NewlineOrEmpty() + fc.SQL(s.Query) - }) + fc.Newline() + ")" + + }) + fc.NewlineOrEmpty() + ")" + sqlOptCtx(fc, " ", s.As, "") + sqlOptCtx(fc, " ", s.Sample, "") } From 02483cf6960c41a8ec7a13f7e42698201f2e95e8 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:01:12 +0900 Subject: [PATCH 04/18] Update --- ast/sql.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ast/sql.go b/ast/sql.go index b0da5498..fc03d44e 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -254,6 +254,9 @@ func (s *Select) SQLContext(fc *FormatContext) string { strOpt(s.Distinct, " DISTINCT ") + sqlOptCtx(fc, " ", s.As, "") + fc.WithIndent(func(fc *FormatContext) string { + if len(s.Results) == 1 { + return " " + fc.SQL(s.Results[0]) + } return fc.Newline() + sqlJoinCtx(fc, s.Results, ","+fc.Newline()) }) + sqlOptCtx(fc, fc.Newline(), s.From, "") + From db8162dbc836ae081f9d55274a673832115a7b3e Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 17 Dec 2024 22:10:44 +0900 Subject: [PATCH 05/18] Update --- ast/sql.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index fc03d44e..e468cd48 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -930,18 +930,25 @@ func (a *AlterProtoBundleDelete) SQL() string { return "DELETE " + a.Types.SQL() func (d *DropProtoBundle) SQL() string { return "DROP PROTO BUNDLE" } -func (c *CreateTable) SQL() string { +func (c *CreateTable) SQLContext(fc *FormatContext) string { return "CREATE TABLE " + strOpt(c.IfNotExists, "IF NOT EXISTS ") + - c.Name.SQL() + " (" + - sqlJoin(c.Columns, ", ") + strOpt(len(c.Columns) > 0 && (len(c.TableConstraints) > 0 || len(c.Synonyms) > 0), ", ") + - sqlJoin(c.TableConstraints, ", ") + strOpt(len(c.TableConstraints) > 0 && len(c.Synonyms) > 0, ", ") + - sqlJoin(c.Synonyms, ", ") + - ") PRIMARY KEY (" + sqlJoin(c.PrimaryKeys, ", ") + ")" + - sqlOpt("", c.Cluster, "") + - sqlOpt("", c.RowDeletionPolicy, "") + fc.SQL(c.Name) + " (" + + fc.WithIndent(func(fc *FormatContext) string { + return fc.NewlineOrEmpty() + sqlJoinCtx(fc, c.Columns, ","+fc.Newline()) + + strOpt(len(c.Columns) > 0 && (len(c.TableConstraints) > 0 || len(c.Synonyms) > 0), ","+fc.Newline()) + + sqlJoinCtx(fc, c.TableConstraints, ","+fc.Newline()) + + strOpt(len(c.TableConstraints) > 0 && len(c.Synonyms) > 0, ","+fc.Newline()) + + sqlJoinCtx(fc, c.Synonyms, ","+fc.Newline()) + }) + fc.NewlineOrEmpty() + + ") PRIMARY KEY (" + sqlJoinCtx(fc, c.PrimaryKeys, ", ") + ")" + + sqlOptCtx(fc, "", c.Cluster, "") + + sqlOptCtx(fc, "", c.RowDeletionPolicy, "") } +func (c *CreateTable) SQL() string { + return c.SQLContext(nil) +} func (s *Synonym) SQL() string { return "SYNONYM (" + s.Name.SQL() + ")" } func (c *CreateSequence) SQL() string { From fd452eec87e24f57e94280903ec55d939ab08bf0 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:14:05 +0900 Subject: [PATCH 06/18] Unexport some functions/methods --- ast/sql.go | 95 +++++++++++++++++++++++++-------------------- tools/parse/main.go | 6 ++- 2 files changed, 56 insertions(+), 45 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index e468cd48..17a89481 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -27,27 +27,27 @@ type FormatContext struct { func (fc *FormatContext) SQL(node Node) string { if nodeFormat, ok := node.(NodeFormat); fc != nil && ok { - return nodeFormat.SQLContext(fc) + return nodeFormat.sqlContext(fc) } else { return node.SQL() } } -func (fc *FormatContext) Newline() string { +func (fc *FormatContext) newlineOrSpace() string { if fc != nil && fc.Option.Newline { return "\n" + strings.Repeat(" ", fc.Current) } return " " } -func (fc *FormatContext) NewlineOrEmpty() string { +func (fc *FormatContext) newlineOrEmpty() string { if fc != nil && fc.Option.Newline { return "\n" + strings.Repeat(" ", fc.Current) } return "" } -func (fc *FormatContext) WithIndent(f func(fc *FormatContext) string) string { +func (fc *FormatContext) indentScope(f func(fc *FormatContext) string) string { var newFc FormatContext if fc != nil { newFc = *fc @@ -84,7 +84,8 @@ func sqlJoinCtx[T Node](fc *FormatContext, elems []T, sep string) string { type NodeFormat interface { Node - SQLContext(fmtCtx *FormatContext) string + // sqlContext will be Node.SQL() finally. + sqlContext(fmtCtx *FormatContext) string } // ================================================================================ @@ -214,14 +215,14 @@ func paren(p prec, e Expr) string { // // ================================================================================ -func (q *QueryStatement) SQLContext(fc *FormatContext) string { - return sqlOptCtx(fc, "", q.Hint, fc.Newline()) + - sqlOptCtx(fc, "", q.With, fc.Newline()) + +func (q *QueryStatement) sqlContext(fc *FormatContext) string { + return sqlOptCtx(fc, "", q.Hint, fc.newlineOrSpace()) + + sqlOptCtx(fc, "", q.With, fc.newlineOrSpace()) + fc.SQL(q.Query) } func (q *QueryStatement) SQL() string { - return q.SQLContext(nil) + return q.sqlContext(nil) } func (h *Hint) SQL() string { @@ -249,26 +250,26 @@ func (c *CTE) SQL() string { return c.Name.SQL() + " AS (" + c.QueryExpr.SQL() + ")" } -func (s *Select) SQLContext(fc *FormatContext) string { +func (s *Select) sqlContext(fc *FormatContext) string { return "SELECT" + strOpt(s.Distinct, " DISTINCT ") + sqlOptCtx(fc, " ", s.As, "") + - fc.WithIndent(func(fc *FormatContext) string { + fc.indentScope(func(fc *FormatContext) string { if len(s.Results) == 1 { return " " + fc.SQL(s.Results[0]) } - return fc.Newline() + sqlJoinCtx(fc, s.Results, ","+fc.Newline()) + return fc.newlineOrSpace() + sqlJoinCtx(fc, s.Results, ","+fc.newlineOrSpace()) }) + - sqlOptCtx(fc, fc.Newline(), s.From, "") + - sqlOptCtx(fc, fc.Newline(), s.Where, "") + - sqlOptCtx(fc, fc.Newline(), s.GroupBy, "") + - sqlOptCtx(fc, fc.Newline(), s.Having, "") + - sqlOptCtx(fc, fc.Newline(), s.OrderBy, "") + - sqlOptCtx(fc, fc.Newline(), s.Limit, "") + sqlOptCtx(fc, fc.newlineOrSpace(), s.From, "") + + sqlOptCtx(fc, fc.newlineOrSpace(), s.Where, "") + + sqlOptCtx(fc, fc.newlineOrSpace(), s.GroupBy, "") + + sqlOptCtx(fc, fc.newlineOrSpace(), s.Having, "") + + sqlOptCtx(fc, fc.newlineOrSpace(), s.OrderBy, "") + + sqlOptCtx(fc, fc.newlineOrSpace(), s.Limit, "") } func (s *Select) SQL() string { - return s.SQLContext(nil) + return s.sqlContext(nil) } func (a *AsStruct) SQL() string { return "AS STRUCT" } @@ -329,11 +330,11 @@ func (e *ExprSelectItem) SQL() string { return e.Expr.SQL() } -func (f *From) SQLContext(fc *FormatContext) string { +func (f *From) sqlContext(fc *FormatContext) string { return "FROM " + fc.SQL(f.Source) } func (f *From) SQL() string { - return f.SQLContext(nil) + return f.sqlContext(nil) } func (w *Where) SQL() string { @@ -431,27 +432,31 @@ func (e *PathTableExpr) SQL() string { sqlOpt(" ", e.Sample, "") } -func (s *SubQueryTableExpr) SQLContext(fc *FormatContext) string { - return "(" + fc.WithIndent( - func(fc *FormatContext) string { - return fc.NewlineOrEmpty() + fc.SQL(s.Query) - }) + fc.NewlineOrEmpty() + ")" + +func (s *SubQueryTableExpr) sqlContext(fc *FormatContext) string { + return "(" + + fc.indentScope(func(fc *FormatContext) string { + return fc.newlineOrEmpty() + fc.SQL(s.Query) + }) + + fc.newlineOrEmpty() + ")" + sqlOptCtx(fc, " ", s.As, "") + sqlOptCtx(fc, " ", s.Sample, "") } func (s *SubQueryTableExpr) SQL() string { - return s.SQLContext(nil) + return s.sqlContext(nil) } -func (p *ParenTableExpr) SQLContext(fc *FormatContext) string { - return "(" + fc.WithIndent(func(fc *FormatContext) string { - return fc.NewlineOrEmpty() + p.Source.SQL() - }) + fc.NewlineOrEmpty() + ")" + sqlOpt(" ", p.Sample, "") +func (p *ParenTableExpr) sqlContext(fc *FormatContext) string { + return "(" + + fc.indentScope(func(fc *FormatContext) string { + return fc.newlineOrEmpty() + p.Source.SQL() + }) + + fc.newlineOrEmpty() + ")" + + sqlOpt(" ", p.Sample, "") } func (p *ParenTableExpr) SQL() string { - return p.SQLContext(nil) + return p.sqlContext(nil) } func (j *Join) SQL() string { @@ -930,24 +935,28 @@ func (a *AlterProtoBundleDelete) SQL() string { return "DELETE " + a.Types.SQL() func (d *DropProtoBundle) SQL() string { return "DROP PROTO BUNDLE" } -func (c *CreateTable) SQLContext(fc *FormatContext) string { +func (c *CreateTable) sqlContext(fc *FormatContext) string { return "CREATE TABLE " + strOpt(c.IfNotExists, "IF NOT EXISTS ") + - fc.SQL(c.Name) + " (" + - fc.WithIndent(func(fc *FormatContext) string { - return fc.NewlineOrEmpty() + sqlJoinCtx(fc, c.Columns, ","+fc.Newline()) + - strOpt(len(c.Columns) > 0 && (len(c.TableConstraints) > 0 || len(c.Synonyms) > 0), ","+fc.Newline()) + - sqlJoinCtx(fc, c.TableConstraints, ","+fc.Newline()) + - strOpt(len(c.TableConstraints) > 0 && len(c.Synonyms) > 0, ","+fc.Newline()) + - sqlJoinCtx(fc, c.Synonyms, ","+fc.Newline()) - }) + fc.NewlineOrEmpty() + - ") PRIMARY KEY (" + sqlJoinCtx(fc, c.PrimaryKeys, ", ") + ")" + + fc.SQL(c.Name) + + " (" + + fc.indentScope(func(fc *FormatContext) string { + return fc.newlineOrEmpty() + sqlJoinCtx(fc, c.Columns, ","+fc.newlineOrSpace()) + + strOpt(len(c.Columns) > 0 && (len(c.TableConstraints) > 0 || len(c.Synonyms) > 0), ","+fc.newlineOrSpace()) + + sqlJoinCtx(fc, c.TableConstraints, ","+fc.newlineOrSpace()) + + strOpt(len(c.TableConstraints) > 0 && len(c.Synonyms) > 0, ","+fc.newlineOrSpace()) + + sqlJoinCtx(fc, c.Synonyms, ","+fc.newlineOrSpace()) + }) + + fc.newlineOrEmpty() + + ") PRIMARY KEY (" + + sqlJoinCtx(fc, c.PrimaryKeys, ", ") + + ")" + sqlOptCtx(fc, "", c.Cluster, "") + sqlOptCtx(fc, "", c.RowDeletionPolicy, "") } func (c *CreateTable) SQL() string { - return c.SQLContext(nil) + return c.sqlContext(nil) } func (s *Synonym) SQL() string { return "SYNONYM (" + s.Name.SQL() + ")" } diff --git a/tools/parse/main.go b/tools/parse/main.go index 642ac184..d7cbd845 100644 --- a/tools/parse/main.go +++ b/tools/parse/main.go @@ -119,14 +119,16 @@ func main() { fmt.Println() fmt.Println("--- SQL") fmt.Println(node.SQL()) + fmt.Println("--- SQL with indentation") - fmt.Println((&ast.FormatContext{ + fc := &ast.FormatContext{ Option: ast.FormatOption{ Newline: true, Indent: 2, }, Current: 0, - }).SQL(node)) + } + fmt.Println(fc.SQL(node)) if *pos != "" { fmt.Println("--- POS") From 1ceefc8e4eae0bce9056512aefca7c7d71e07f8c Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:15:53 +0900 Subject: [PATCH 07/18] Update --- ast/sql.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ast/sql.go b/ast/sql.go index 17a89481..53050699 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -958,6 +958,7 @@ func (c *CreateTable) sqlContext(fc *FormatContext) string { func (c *CreateTable) SQL() string { return c.sqlContext(nil) } + func (s *Synonym) SQL() string { return "SYNONYM (" + s.Name.SQL() + ")" } func (c *CreateSequence) SQL() string { From 1574da6459c13c1a7654af4904819b08993f8f59 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:26:06 +0900 Subject: [PATCH 08/18] Update --- ast/sql.go | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 53050699..15d8463f 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -7,6 +7,12 @@ import ( "github.com/cloudspannerecosystem/memefish/token" ) +// ================================================================================ +// +// Experimental format with indentation +// +// ================================================================================ + type FormatOption struct { Newline bool Indent int @@ -33,29 +39,29 @@ func (fc *FormatContext) SQL(node Node) string { } } -func (fc *FormatContext) newlineOrSpace() string { +func (fc *FormatContext) newlineOr(s string) string { if fc != nil && fc.Option.Newline { return "\n" + strings.Repeat(" ", fc.Current) } - return " " + return s +} + +func (fc *FormatContext) newlineOrSpace() string { + return fc.newlineOr(" ") } func (fc *FormatContext) newlineOrEmpty() string { - if fc != nil && fc.Option.Newline { - return "\n" + strings.Repeat(" ", fc.Current) - } - return "" + return fc.newlineOr("") } func (fc *FormatContext) indentScope(f func(fc *FormatContext) string) string { - var newFc FormatContext - if fc != nil { - newFc = *fc - newFc.Current += newFc.Option.Indent - return f(&newFc) - } else { + if fc == nil { return f(emptyFormatContext) } + + newFc := *fc + newFc.Current += newFc.Option.Indent + return f(&newFc) } func sqlOptCtx[T interface { From cf37524046a0f65fcbc107b9bed67d5df6225931 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:27:30 +0900 Subject: [PATCH 09/18] Update --- ast/sql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ast/sql.go b/ast/sql.go index 15d8463f..4f57f985 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -258,7 +258,7 @@ func (c *CTE) SQL() string { func (s *Select) sqlContext(fc *FormatContext) string { return "SELECT" + - strOpt(s.Distinct, " DISTINCT ") + + strOpt(s.Distinct, " DISTINCT") + sqlOptCtx(fc, " ", s.As, "") + fc.indentScope(func(fc *FormatContext) string { if len(s.Results) == 1 { From fa709384860f1a6d810bd91fdfaee4d5d31cd5df Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:34:33 +0900 Subject: [PATCH 10/18] Update --- ast/sql.go | 46 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 4f57f985..584e7f18 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -46,14 +46,6 @@ func (fc *FormatContext) newlineOr(s string) string { return s } -func (fc *FormatContext) newlineOrSpace() string { - return fc.newlineOr(" ") -} - -func (fc *FormatContext) newlineOrEmpty() string { - return fc.newlineOr("") -} - func (fc *FormatContext) indentScope(f func(fc *FormatContext) string) string { if fc == nil { return f(emptyFormatContext) @@ -222,8 +214,8 @@ func paren(p prec, e Expr) string { // ================================================================================ func (q *QueryStatement) sqlContext(fc *FormatContext) string { - return sqlOptCtx(fc, "", q.Hint, fc.newlineOrSpace()) + - sqlOptCtx(fc, "", q.With, fc.newlineOrSpace()) + + return sqlOptCtx(fc, "", q.Hint, fc.newlineOr(" ")) + + sqlOptCtx(fc, "", q.With, fc.newlineOr(" ")) + fc.SQL(q.Query) } @@ -264,14 +256,14 @@ func (s *Select) sqlContext(fc *FormatContext) string { if len(s.Results) == 1 { return " " + fc.SQL(s.Results[0]) } - return fc.newlineOrSpace() + sqlJoinCtx(fc, s.Results, ","+fc.newlineOrSpace()) + return fc.newlineOr(" ") + sqlJoinCtx(fc, s.Results, ","+fc.newlineOr(" ")) }) + - sqlOptCtx(fc, fc.newlineOrSpace(), s.From, "") + - sqlOptCtx(fc, fc.newlineOrSpace(), s.Where, "") + - sqlOptCtx(fc, fc.newlineOrSpace(), s.GroupBy, "") + - sqlOptCtx(fc, fc.newlineOrSpace(), s.Having, "") + - sqlOptCtx(fc, fc.newlineOrSpace(), s.OrderBy, "") + - sqlOptCtx(fc, fc.newlineOrSpace(), s.Limit, "") + sqlOptCtx(fc, fc.newlineOr(" "), s.From, "") + + sqlOptCtx(fc, fc.newlineOr(" "), s.Where, "") + + sqlOptCtx(fc, fc.newlineOr(" "), s.GroupBy, "") + + sqlOptCtx(fc, fc.newlineOr(" "), s.Having, "") + + sqlOptCtx(fc, fc.newlineOr(" "), s.OrderBy, "") + + sqlOptCtx(fc, fc.newlineOr(" "), s.Limit, "") } func (s *Select) SQL() string { @@ -441,9 +433,9 @@ func (e *PathTableExpr) SQL() string { func (s *SubQueryTableExpr) sqlContext(fc *FormatContext) string { return "(" + fc.indentScope(func(fc *FormatContext) string { - return fc.newlineOrEmpty() + fc.SQL(s.Query) + return fc.newlineOr("") + fc.SQL(s.Query) }) + - fc.newlineOrEmpty() + ")" + + fc.newlineOr("") + ")" + sqlOptCtx(fc, " ", s.As, "") + sqlOptCtx(fc, " ", s.Sample, "") } @@ -455,9 +447,9 @@ func (s *SubQueryTableExpr) SQL() string { func (p *ParenTableExpr) sqlContext(fc *FormatContext) string { return "(" + fc.indentScope(func(fc *FormatContext) string { - return fc.newlineOrEmpty() + p.Source.SQL() + return fc.newlineOr("") + p.Source.SQL() }) + - fc.newlineOrEmpty() + ")" + + fc.newlineOr("") + ")" + sqlOpt(" ", p.Sample, "") } @@ -947,13 +939,13 @@ func (c *CreateTable) sqlContext(fc *FormatContext) string { fc.SQL(c.Name) + " (" + fc.indentScope(func(fc *FormatContext) string { - return fc.newlineOrEmpty() + sqlJoinCtx(fc, c.Columns, ","+fc.newlineOrSpace()) + - strOpt(len(c.Columns) > 0 && (len(c.TableConstraints) > 0 || len(c.Synonyms) > 0), ","+fc.newlineOrSpace()) + - sqlJoinCtx(fc, c.TableConstraints, ","+fc.newlineOrSpace()) + - strOpt(len(c.TableConstraints) > 0 && len(c.Synonyms) > 0, ","+fc.newlineOrSpace()) + - sqlJoinCtx(fc, c.Synonyms, ","+fc.newlineOrSpace()) + return fc.newlineOr("") + sqlJoinCtx(fc, c.Columns, ","+fc.newlineOr(" ")) + + strOpt(len(c.Columns) > 0 && (len(c.TableConstraints) > 0 || len(c.Synonyms) > 0), ","+fc.newlineOr(" ")) + + sqlJoinCtx(fc, c.TableConstraints, ","+fc.newlineOr(" ")) + + strOpt(len(c.TableConstraints) > 0 && len(c.Synonyms) > 0, ","+fc.newlineOr(" ")) + + sqlJoinCtx(fc, c.Synonyms, ","+fc.newlineOr(" ")) }) + - fc.newlineOrEmpty() + + fc.newlineOr("") + ") PRIMARY KEY (" + sqlJoinCtx(fc, c.PrimaryKeys, ", ") + ")" + From 505fe89765632668c1bb23863c95d092ec84ef58 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Dec 2024 07:21:35 +0900 Subject: [PATCH 11/18] Unexport newline and indent of FormatOption --- ast/sql.go | 75 ++++++++++++++++++++++++++++----------------- tools/parse/main.go | 5 +-- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index eecf59b3..72c2f4d8 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -14,16 +14,20 @@ import ( // ================================================================================ type FormatOption struct { - Newline bool - Indent int + newline bool + indent int +} + +func FormatOptionCompact() FormatOption { + return FormatOption{} +} + +func FormatOptionPretty(indent int) FormatOption { + return FormatOption{newline: true, indent: indent} } var emptyFormatContext = &FormatContext{ - Option: FormatOption{ - Newline: false, - Indent: 0, - }, - Current: 0, + Option: FormatOptionCompact(), } type FormatContext struct { @@ -40,19 +44,21 @@ func (fc *FormatContext) SQL(node Node) string { } func (fc *FormatContext) newlineOr(s string) string { - if fc != nil && fc.Option.Newline { - return "\n" + strings.Repeat(" ", fc.Current) + if fc == nil { + return s } - return s + + return strIfElse(fc.Option.newline, "\n", s) + strings.Repeat(" ", fc.Current) } +// indentScope executes function with FormatContext with needed indentation. func (fc *FormatContext) indentScope(f func(fc *FormatContext) string) string { if fc == nil { return f(emptyFormatContext) } newFc := *fc - newFc.Current += newFc.Option.Indent + newFc.Current += fc.Option.indent return f(&newFc) } @@ -67,7 +73,8 @@ func sqlOptCtx[T interface { return left + fc.SQL(node) + right } -// sqlJoin outputs joined string of SQL() of all elems by sep. +// sqlJoinCtx outputs joined string of SQL() of all elems by sep. +// It supports FormatContext. // This function corresponds to sqlJoin in ast.go func sqlJoinCtx[T Node](fc *FormatContext, elems []T, sep string) string { var b strings.Builder @@ -241,12 +248,12 @@ func (q *QueryStatement) SQL() string { } func (q *Query) sqlContext(fc *FormatContext) string { - return sqlOptCtx(fc, "", q.With, " ") + + return sqlOptCtx(fc, "", q.With, fc.newlineOr(" ")) + fc.SQL(q.Query) + - sqlOptCtx(fc, " ", q.OrderBy, "") + - sqlOptCtx(fc, " ", q.Limit, "") + - strOpt(len(q.PipeOperators) > 0, " ") + - sqlJoinCtx(fc, q.PipeOperators, " ") + sqlOptCtx(fc, fc.newlineOr(" "), q.OrderBy, "") + + sqlOptCtx(fc, fc.newlineOr(" "), q.Limit, "") + + strOpt(len(q.PipeOperators) > 0, fc.newlineOr(" ")) + + sqlJoinCtx(fc, q.PipeOperators, fc.newlineOr(" ")) } func (q *Query) SQL() string { @@ -261,12 +268,23 @@ func (h *HintRecord) SQL() string { return h.Key.SQL() + "=" + h.Value.SQL() } +func (w *With) sqlContext(fc *FormatContext) string { + return "WITH " + sqlJoinCtx(fc, w.CTEs, ", ") +} + func (w *With) SQL() string { - return "WITH " + sqlJoin(w.CTEs, ", ") + return w.sqlContext(nil) +} +func (c *CTE) sqlContext(fc *FormatContext) string { + return c.Name.SQL() + " AS (" + + fc.indentScope(func(fc *FormatContext) string { + return fc.newlineOr("") + fc.SQL(c.QueryExpr) + }) + + fc.newlineOr("") + ")" } func (c *CTE) SQL() string { - return c.Name.SQL() + " AS (" + c.QueryExpr.SQL() + ")" + return c.sqlContext(nil) } func (s *Select) sqlContext(fc *FormatContext) string { @@ -429,9 +447,7 @@ func (s *SubQueryTableExpr) sqlContext(fc *FormatContext) string { } func (s *SubQueryTableExpr) SQL() string { - return "(" + s.Query.SQL() + ")" + - sqlOpt(" ", s.As, "") + - sqlOpt(" ", s.Sample, "") + return s.sqlContext(nil) } func (p *ParenTableExpr) SQL() string { @@ -439,15 +455,18 @@ func (p *ParenTableExpr) SQL() string { sqlOpt(" ", p.Sample, "") } -func (j *Join) SQL() string { - return j.Left.SQL() + - strOpt(j.Op != CommaJoin, " ") + +func (j *Join) sqlContext(fc *FormatContext) string { + return fc.SQL(j.Left) + + strOpt(j.Op != CommaJoin, fc.newlineOr(" ")) + string(j.Op) + " " + - sqlOpt("", j.Hint, " ") + - j.Right.SQL() + - sqlOpt(" ", j.Cond, "") + sqlOptCtx(fc, "", j.Hint, " ") + + fc.SQL(j.Right) + + sqlOptCtx(fc, " ", j.Cond, "") } +func (j *Join) SQL() string { + return j.sqlContext(nil) +} func (o *On) SQL() string { return "ON " + o.Expr.SQL() } diff --git a/tools/parse/main.go b/tools/parse/main.go index d7cbd845..a9c490c6 100644 --- a/tools/parse/main.go +++ b/tools/parse/main.go @@ -122,10 +122,7 @@ func main() { fmt.Println("--- SQL with indentation") fc := &ast.FormatContext{ - Option: ast.FormatOption{ - Newline: true, - Indent: 2, - }, + Option: ast.FormatOptionPretty(2), Current: 0, } fmt.Println(fc.SQL(node)) From a8ccaeead2ac87107ffaeadba1d1fd64905ec975 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Dec 2024 07:37:32 +0900 Subject: [PATCH 12/18] Add comments --- ast/sql.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 72c2f4d8..b08049db 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -13,15 +13,19 @@ import ( // // ================================================================================ +// FormatOption is container of format configurations. +// You should construct it using FormatOptionCompact() or FormatOptionPretty(). type FormatOption struct { newline bool indent int } +// FormatOptionCompact is format option without newline and indentation. func FormatOptionCompact() FormatOption { return FormatOption{} } +// FormatOptionPretty is format option with newline and configured indentation. func FormatOptionPretty(indent int) FormatOption { return FormatOption{newline: true, indent: indent} } @@ -30,11 +34,14 @@ var emptyFormatContext = &FormatContext{ Option: FormatOptionCompact(), } +// FormatContext is container of FormatOption and current indentation. type FormatContext struct { - Option FormatOption - Current int + Option FormatOption + CurrentIndent int } +// SQL is entry point of pretty printing. +// If node implements NodeFormat, it calls NodeFormat.sqlContext() instead of Node.SQL(). func (fc *FormatContext) SQL(node Node) string { if nodeFormat, ok := node.(NodeFormat); fc != nil && ok { return nodeFormat.sqlContext(fc) @@ -43,25 +50,28 @@ func (fc *FormatContext) SQL(node Node) string { } } +// newlineOr returns newline with indentation if FormatOptionPretty is used. +// Otherwise, it returns argument string. func (fc *FormatContext) newlineOr(s string) string { if fc == nil { return s } - return strIfElse(fc.Option.newline, "\n", s) + strings.Repeat(" ", fc.Current) + return strIfElse(fc.Option.newline, "\n", s) + strings.Repeat(" ", fc.CurrentIndent) } -// indentScope executes function with FormatContext with needed indentation. +// indentScope executes function with FormatContext with deeper indentation. func (fc *FormatContext) indentScope(f func(fc *FormatContext) string) string { if fc == nil { return f(emptyFormatContext) } newFc := *fc - newFc.Current += fc.Option.indent + newFc.CurrentIndent += fc.Option.indent return f(&newFc) } +// sqlOptCtx is sqlOpt with FormatContext. func sqlOptCtx[T interface { Node comparable @@ -73,9 +83,7 @@ func sqlOptCtx[T interface { return left + fc.SQL(node) + right } -// sqlJoinCtx outputs joined string of SQL() of all elems by sep. -// It supports FormatContext. -// This function corresponds to sqlJoin in ast.go +// sqlJoinCtx is sqlJoin with FormatContext. func sqlJoinCtx[T Node](fc *FormatContext, elems []T, sep string) string { var b strings.Builder for i, r := range elems { @@ -87,6 +95,8 @@ func sqlJoinCtx[T Node](fc *FormatContext, elems []T, sep string) string { return b.String() } +// NodeFormat is Node with FormatContext support. +// If it is implemented, (*FormatContext).SQL calls sqlContext() instead of SQL(). type NodeFormat interface { Node // sqlContext will be Node.SQL() finally. From 4c720b2695bddf957017436236c64e2adad5c227 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Dec 2024 07:51:13 +0900 Subject: [PATCH 13/18] Update --- ast/sql.go | 32 +++++++++++++++++++++----------- tools/parse/main.go | 6 +----- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index b08049db..4bcdc69b 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -30,14 +30,21 @@ func FormatOptionPretty(indent int) FormatOption { return FormatOption{newline: true, indent: indent} } -var emptyFormatContext = &FormatContext{ - Option: FormatOptionCompact(), -} - // FormatContext is container of FormatOption and current indentation. +// Note: All methods of FormatContext must support nil receiver. type FormatContext struct { - Option FormatOption - CurrentIndent int + option FormatOption + currentIndent int +} + +// FormatContextCompact is format context without newline and indentation. +func FormatContextCompact() *FormatContext { + return &FormatContext{option: FormatOptionCompact()} +} + +// FormatContextPretty is format context with newline and configured indentation. +func FormatContextPretty(indent int) *FormatContext { + return &FormatContext{option: FormatOptionPretty(indent)} } // SQL is entry point of pretty printing. @@ -57,17 +64,17 @@ func (fc *FormatContext) newlineOr(s string) string { return s } - return strIfElse(fc.Option.newline, "\n", s) + strings.Repeat(" ", fc.CurrentIndent) + return strIfElse(fc.option.newline, "\n", s) + strings.Repeat(" ", fc.currentIndent) } // indentScope executes function with FormatContext with deeper indentation. func (fc *FormatContext) indentScope(f func(fc *FormatContext) string) string { if fc == nil { - return f(emptyFormatContext) + return f(nil) } newFc := *fc - newFc.CurrentIndent += fc.Option.indent + newFc.currentIndent += fc.option.indent return f(&newFc) } @@ -96,10 +103,13 @@ func sqlJoinCtx[T Node](fc *FormatContext, elems []T, sep string) string { } // NodeFormat is Node with FormatContext support. -// If it is implemented, (*FormatContext).SQL calls sqlContext() instead of SQL(). +// If it is implemented, (*FormatContext).SQL calls sqlContext() instead of SQL() type NodeFormat interface { Node - // sqlContext will be Node.SQL() finally. + + // sqlContext is Node.SQL() with FormatContext conceptually. + // It can be called with nil. + // Note: It would become to Node.SQL() finally. sqlContext(fmtCtx *FormatContext) string } diff --git a/tools/parse/main.go b/tools/parse/main.go index a9c490c6..94e3c7e8 100644 --- a/tools/parse/main.go +++ b/tools/parse/main.go @@ -121,11 +121,7 @@ func main() { fmt.Println(node.SQL()) fmt.Println("--- SQL with indentation") - fc := &ast.FormatContext{ - Option: ast.FormatOptionPretty(2), - Current: 0, - } - fmt.Println(fc.SQL(node)) + fmt.Println(ast.FormatContextPretty(2).SQL(node)) if *pos != "" { fmt.Println("--- POS") From 7c3b99934446f843f4c347ba78ed4eac223813e3 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Dec 2024 07:53:43 +0900 Subject: [PATCH 14/18] Implement sqlJoin, sqlOpt using sqlJoinCtx, sqlOptCtx --- ast/sql.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 4bcdc69b..4b7562d0 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -130,11 +130,7 @@ func sqlOpt[T interface { Node comparable }](left string, node T, right string) string { - var zero T - if node == zero { - return "" - } - return left + node.SQL() + right + return sqlOptCtx(nil, left, node, right) } // strOpt outputs: @@ -166,14 +162,7 @@ func strIfElse(pred bool, ifStr string, elseStr string) string { // sqlJoin outputs joined string of SQL() of all elems by sep. // This function corresponds to sqlJoin in ast.go func sqlJoin[T Node](elems []T, sep string) string { - var b strings.Builder - for i, r := range elems { - if i > 0 { - b.WriteString(sep) - } - b.WriteString(r.SQL()) - } - return b.String() + return sqlJoinCtx(nil, elems, sep) } // formatBoolUpper formats bool value as uppercase. @@ -309,7 +298,7 @@ func (c *CTE) SQL() string { func (s *Select) sqlContext(fc *FormatContext) string { return "SELECT" + - strOpt(s.AllOrDistinct != "", string(s.AllOrDistinct)+" ") + + strOpt(s.AllOrDistinct != "", " "+string(s.AllOrDistinct)) + sqlOptCtx(fc, " ", s.As, "") + fc.indentScope(func(fc *FormatContext) string { if len(s.Results) == 1 { From 9a2a437bc3bc02faa53592d024cc7aeb00dad68e Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:07:55 +0900 Subject: [PATCH 15/18] Update --- ast/sql.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 4b7562d0..713436da 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -13,38 +13,38 @@ import ( // // ================================================================================ -// FormatOption is container of format configurations. -// You should construct it using FormatOptionCompact() or FormatOptionPretty(). -type FormatOption struct { +// formatOption is container of format configurations. +// It is conceptually immutable. +type formatOption struct { newline bool indent int } -// FormatOptionCompact is format option without newline and indentation. -func FormatOptionCompact() FormatOption { - return FormatOption{} +// formatOptionCompact is format option without newline and indentation. +func formatOptionCompact() formatOption { + return formatOption{} } -// FormatOptionPretty is format option with newline and configured indentation. -func FormatOptionPretty(indent int) FormatOption { - return FormatOption{newline: true, indent: indent} +// formatOptionPretty is format option with newline and configured indentation. +func formatOptionPretty(indent int) formatOption { + return formatOption{newline: true, indent: indent} } -// FormatContext is container of FormatOption and current indentation. +// FormatContext is container of format option and current indentation. // Note: All methods of FormatContext must support nil receiver. type FormatContext struct { - option FormatOption + option formatOption currentIndent int } // FormatContextCompact is format context without newline and indentation. func FormatContextCompact() *FormatContext { - return &FormatContext{option: FormatOptionCompact()} + return &FormatContext{option: formatOptionCompact()} } // FormatContextPretty is format context with newline and configured indentation. func FormatContextPretty(indent int) *FormatContext { - return &FormatContext{option: FormatOptionPretty(indent)} + return &FormatContext{option: formatOptionPretty(indent)} } // SQL is entry point of pretty printing. @@ -57,7 +57,7 @@ func (fc *FormatContext) SQL(node Node) string { } } -// newlineOr returns newline with indentation if FormatOptionPretty is used. +// newlineOr returns newline with indentation if formatOptionPretty is used. // Otherwise, it returns argument string. func (fc *FormatContext) newlineOr(s string) string { if fc == nil { From e36e8760da57eb881771ae0d18488aac340d3bc2 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:20:54 +0900 Subject: [PATCH 16/18] Update readability --- ast/sql.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 713436da..e8a462f0 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -31,7 +31,7 @@ func formatOptionPretty(indent int) formatOption { } // FormatContext is container of format option and current indentation. -// Note: All methods of FormatContext must support nil receiver. +// If methods are called with nil receiver, they will work as FormatContextCompact() is receiver. type FormatContext struct { option formatOption currentIndent int @@ -49,8 +49,13 @@ func FormatContextPretty(indent int) *FormatContext { // SQL is entry point of pretty printing. // If node implements NodeFormat, it calls NodeFormat.sqlContext() instead of Node.SQL(). +// If it is called with nil receiver, FormatContextCompact() is used instead. func (fc *FormatContext) SQL(node Node) string { - if nodeFormat, ok := node.(NodeFormat); fc != nil && ok { + if fc == nil { + fc = FormatContextCompact() + } + + if nodeFormat, ok := node.(NodeFormat); ok { return nodeFormat.sqlContext(fc) } else { return node.SQL() @@ -61,7 +66,7 @@ func (fc *FormatContext) SQL(node Node) string { // Otherwise, it returns argument string. func (fc *FormatContext) newlineOr(s string) string { if fc == nil { - return s + fc = FormatContextCompact() } return strIfElse(fc.option.newline, "\n", s) + strings.Repeat(" ", fc.currentIndent) @@ -70,7 +75,7 @@ func (fc *FormatContext) newlineOr(s string) string { // indentScope executes function with FormatContext with deeper indentation. func (fc *FormatContext) indentScope(f func(fc *FormatContext) string) string { if fc == nil { - return f(nil) + fc = FormatContextCompact() } newFc := *fc @@ -83,6 +88,10 @@ func sqlOptCtx[T interface { Node comparable }](fc *FormatContext, left string, node T, right string) string { + if fc == nil { + fc = FormatContextCompact() + } + var zero T if node == zero { return "" @@ -108,7 +117,7 @@ type NodeFormat interface { Node // sqlContext is Node.SQL() with FormatContext conceptually. - // It can be called with nil. + // If it is called with nil FormatContext, FormatContextCompact() is used instead. // Note: It would become to Node.SQL() finally. sqlContext(fmtCtx *FormatContext) string } From 51b292dd45b561c15f9cee5ca867b70f137268a7 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:23:21 +0900 Subject: [PATCH 17/18] Remove unneeded symbols --- ast/sql.go | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index e8a462f0..56653da0 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -20,17 +20,8 @@ type formatOption struct { indent int } -// formatOptionCompact is format option without newline and indentation. -func formatOptionCompact() formatOption { - return formatOption{} -} - -// formatOptionPretty is format option with newline and configured indentation. -func formatOptionPretty(indent int) formatOption { - return formatOption{newline: true, indent: indent} -} - // FormatContext is container of format option and current indentation. +// You should initialize this struct using FormatContextCompact() or FormatContextPretty(). // If methods are called with nil receiver, they will work as FormatContextCompact() is receiver. type FormatContext struct { option formatOption @@ -39,12 +30,12 @@ type FormatContext struct { // FormatContextCompact is format context without newline and indentation. func FormatContextCompact() *FormatContext { - return &FormatContext{option: formatOptionCompact()} + return &FormatContext{option: formatOption{}} } // FormatContextPretty is format context with newline and configured indentation. func FormatContextPretty(indent int) *FormatContext { - return &FormatContext{option: formatOptionPretty(indent)} + return &FormatContext{option: formatOption{newline: true, indent: indent}} } // SQL is entry point of pretty printing. From 407c064fcaa41aeed830e2ee474c6bb408f4980e Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:28:49 +0900 Subject: [PATCH 18/18] Update --- ast/sql.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 56653da0..5070450f 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -301,10 +301,8 @@ func (s *Select) sqlContext(fc *FormatContext) string { strOpt(s.AllOrDistinct != "", " "+string(s.AllOrDistinct)) + sqlOptCtx(fc, " ", s.As, "") + fc.indentScope(func(fc *FormatContext) string { - if len(s.Results) == 1 { - return " " + fc.SQL(s.Results[0]) - } - return fc.newlineOr(" ") + sqlJoinCtx(fc, s.Results, ","+fc.newlineOr(" ")) + return strIfElse(len(s.Results) > 1, fc.newlineOr(" "), " ") + + sqlJoinCtx(fc, s.Results, ","+fc.newlineOr(" ")) }) + sqlOptCtx(fc, fc.newlineOr(" "), s.From, "") + sqlOptCtx(fc, fc.newlineOr(" "), s.Where, "") +