From 4236da9f0284758dd9a2ca5a985bff16be43c004 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:14:46 +0900 Subject: [PATCH 01/12] Bump Go version to 1.20, and add util.go --- ast/util.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 ast/util.go diff --git a/ast/util.go b/ast/util.go new file mode 100644 index 00000000..8a631ee5 --- /dev/null +++ b/ast/util.go @@ -0,0 +1,59 @@ +package ast + +import ( + "github.com/cloudspannerecosystem/memefish/token" + "strings" +) + +// sqlOpt outputs: +// +// when node != nil: left + node.SQL() + right +// else : empty string +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 +} + +func lastElem[T any](s []T) T { + return s[len(s)-1] +} + +func firstValidEnd(ns ...Node) token.Pos { + for _, n := range ns { + if n != nil && n.End() != token.InvalidPos { + return n.End() + } + } + return token.InvalidPos +} + +func firstPos[T Node](s []T) token.Pos { + if len(s) == 0 { + return token.InvalidPos + } + return s[0].Pos() +} + +func lastEnd[T Node](s []T) token.Pos { + if len(s) == 0 { + return token.InvalidPos + } + return lastElem(s).End() +} + +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() +} diff --git a/go.mod b/go.mod index 0defff2e..e65f1d20 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cloudspannerecosystem/memefish -go 1.19 +go 1.20 require ( github.com/MakeNowJust/heredoc/v2 v2.0.1 From 3f8aca2c5c51280ef94377c983058cc321a8c82f Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:04:23 +0900 Subject: [PATCH 02/12] Add comments to ast/util.go --- ast/util.go | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/ast/util.go b/ast/util.go index 8a631ee5..a8ae97c8 100644 --- a/ast/util.go +++ b/ast/util.go @@ -5,10 +5,14 @@ import ( "strings" ) +// Helper functions for SQL() + // sqlOpt outputs: // // when node != nil: left + node.SQL() + right // else : empty string +// +// This function corresponds to sqlOpt in ast.go func sqlOpt[T interface { Node comparable @@ -20,19 +24,42 @@ func sqlOpt[T interface { return left + node.SQL() + right } +// 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() +} + +// Helper functions for Pos(), End() + +// lastElem returns last element of slice s. +// This function corresponds to NodeSliceVar[$] in ast.go. func lastElem[T any](s []T) T { return s[len(s)-1] } +// firstValidEnd returns the first valid Pos() in argument. +// "valid" means the node is not nil and Pos().Invalid() is not true. +// This function corresponds to "(n0 ?? n1 ?? ...).End()" func firstValidEnd(ns ...Node) token.Pos { for _, n := range ns { - if n != nil && n.End() != token.InvalidPos { + if n != nil && !n.End().Invalid() { return n.End() } } return token.InvalidPos } +// firstPos returns the Pos() of the first node. +// If argument is an empty slice, this function returns token.InvalidPos. +// This function corresponds to NodeSliceVar[0].pos in ast.go. func firstPos[T Node](s []T) token.Pos { if len(s) == 0 { return token.InvalidPos @@ -40,20 +67,12 @@ func firstPos[T Node](s []T) token.Pos { return s[0].Pos() } +// lastEnd returns the End() of the last node. +// If argument is an empty slice, this function returns token.InvalidPos. +// This function corresponds to NodeSliceVar[$].end in ast.go. func lastEnd[T Node](s []T) token.Pos { if len(s) == 0 { return token.InvalidPos } return lastElem(s).End() } - -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() -} From 19f717d7d158d3c52bae2fc7404d986e809b3b54 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:13:34 +0900 Subject: [PATCH 03/12] Add strOpt in ast/util.go --- ast/util.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ast/util.go b/ast/util.go index a8ae97c8..92229a72 100644 --- a/ast/util.go +++ b/ast/util.go @@ -24,6 +24,19 @@ func sqlOpt[T interface { return left + node.SQL() + right } +// strOpt outputs: +// +// when pred == true: s +// else : empty string +// +// This function corresponds to {{if pred}}s{{end}} in ast.go +func strOpt(pred bool, s string) string { + if pred { + return s + } + return "" +} + // 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 { From b0c66585a8869439834c3103b1f9437e77883459 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:13:51 +0900 Subject: [PATCH 04/12] Refactor (*CallExpr).SQL() --- ast/sql.go | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index ad5e7193..5ff362f7 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -464,27 +464,11 @@ func (i *IndexExpr) SQL() string { } func (c *CallExpr) SQL() string { - sql := c.Func.SQL() + "(" - if c.Distinct { - sql += "DISTINCT " - } - for i, a := range c.Args { - if i != 0 { - sql += ", " - } - sql += a.SQL() - } - if len(c.Args) > 0 && len(c.NamedArgs) > 0 { - sql += ", " - } - for i, v := range c.NamedArgs { - if i != 0 { - sql += ", " - } - sql += v.SQL() - } - sql += ")" - return sql + return c.Func.SQL() + "(" + strOpt(c.Distinct, "DISTINCT ") + + sqlJoin(c.Args, ", ") + + strOpt(len(c.Args) > 0 && len(c.NamedArgs) > 0, ", ") + + sqlJoin(c.NamedArgs, ", ") + + ")" } func (n *NamedArg) SQL() string { return n.Name.SQL() + " => " + n.Value.SQL() } From 50c42040f698a3eb37bd9adc95fa5956b5f33194 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:17:18 +0900 Subject: [PATCH 05/12] Refactor (*Select).SQL() --- ast/sql.go | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 5ff362f7..6a3dc360 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -116,36 +116,16 @@ func (c *CTE) SQL() string { } func (s *Select) SQL() string { - sql := "SELECT " - if s.Distinct { - sql += "DISTINCT " - } - if s.AsStruct { - sql += "AS STRUCT " - } - sql += s.Results[0].SQL() - for _, r := range s.Results[1:] { - sql += ", " + r.SQL() - } - if s.From != nil { - sql += " " + s.From.SQL() - } - if s.Where != nil { - sql += " " + s.Where.SQL() - } - if s.GroupBy != nil { - sql += " " + s.GroupBy.SQL() - } - if s.Having != nil { - sql += " " + s.Having.SQL() - } - if s.OrderBy != nil { - sql += " " + s.OrderBy.SQL() - } - if s.Limit != nil { - sql += " " + s.Limit.SQL() - } - return sql + return "SELECT " + + strOpt(s.Distinct, "DISTINCT ") + + strOpt(s.AsStruct, "AS STRUCT ") + + sqlJoin(s.Results, ", ") + + sqlOpt(" ", s.From, "") + + sqlOpt(" ", s.Where, "") + + sqlOpt(" ", s.GroupBy, "") + + sqlOpt(" ", s.Having, "") + + sqlOpt(" ", s.OrderBy, "") + + sqlOpt(" ", s.Limit, "") } func (c *CompoundQuery) SQL() string { From 803f45ed7b5498307df55b2af7c5df7639f02966 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:19:22 +0900 Subject: [PATCH 06/12] Refactor (*Select).End() --- ast/pos.go | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/ast/pos.go b/ast/pos.go index 4e3ebb3b..fe90b554 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -39,25 +39,7 @@ func (c *CTE) End() token.Pos { return c.Rparen + 1 } func (s *Select) Pos() token.Pos { return s.Select } func (s *Select) End() token.Pos { - if s.Limit != nil { - return s.Limit.End() - } - if s.OrderBy != nil { - return s.OrderBy.End() - } - if s.Having != nil { - return s.Having.End() - } - if s.GroupBy != nil { - return s.GroupBy.End() - } - if s.Where != nil { - return s.Where.End() - } - if s.From != nil { - return s.From.End() - } - return s.Results[len(s.Results)-1].End() + return firstValidEnd(s.Limit, s.OrderBy, s.Having, s.GroupBy, s.Where, s.From, lastElem(s.Results)) } func (c *CompoundQuery) Pos() token.Pos { From 18d6085bee121dc9565759a616138ef8ceffde66 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:48:23 +0900 Subject: [PATCH 07/12] Bump Go version in .github/workflows --- .github/workflows/go.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d34a5425..c8197beb 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.19 - uses: actions/setup-go@v5 + - name: Set up Go 1.20 + uses: actions/setup-go@v4 with: - go-version: 1.19 + go-version: "1.20" id: go - name: Check out code into the Go module directory From 9decfc94f55ae0e83d892aa40e0efc1df74abd41 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:39:57 +0900 Subject: [PATCH 08/12] Refactor (*Path).Pos(), (*Path).End() --- ast/pos.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ast/pos.go b/ast/pos.go index fe90b554..dda56a3f 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -358,8 +358,8 @@ func (p *Param) End() token.Pos { return p.Atmark + 1 + token.Pos(len(p.Name)) } func (i *Ident) Pos() token.Pos { return i.NamePos } func (i *Ident) End() token.Pos { return i.NameEnd } -func (p *Path) Pos() token.Pos { return p.Idents[0].Pos() } -func (p *Path) End() token.Pos { return p.Idents[len(p.Idents)-1].End() } +func (p *Path) Pos() token.Pos { return firstPos(p.Idents) } +func (p *Path) End() token.Pos { return lastEnd(p.Idents) } func (a *ArrayLiteral) Pos() token.Pos { if !a.Array.Invalid() { From 94a198a7920a2dab096b4b3cd3b245bcf3cfafc7 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:40:55 +0900 Subject: [PATCH 09/12] Refactor (*Path).SQL() --- ast/sql.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ast/sql.go b/ast/sql.go index 6a3dc360..a80a5539 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -559,11 +559,7 @@ func (i *Ident) SQL() string { } func (p *Path) SQL() string { - sql := p.Idents[0].SQL() - for _, id := range p.Idents[1:] { - sql += "." + id.SQL() - } - return sql + return sqlJoin(p.Idents, ".") } func (a *ArrayLiteral) SQL() string { From e1a3e815ecd3fca895317482ec3d072e999d1eb5 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:07:32 +0900 Subject: [PATCH 10/12] Remove lastElem --- ast/pos.go | 2 +- ast/util.go | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ast/pos.go b/ast/pos.go index b952ea9e..458aa1ce 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -39,7 +39,7 @@ func (c *CTE) End() token.Pos { return c.Rparen + 1 } func (s *Select) Pos() token.Pos { return s.Select } func (s *Select) End() token.Pos { - return firstValidEnd(s.Limit, s.OrderBy, s.Having, s.GroupBy, s.Where, s.From, lastElem(s.Results)) + return firstValidEnd(s.Limit, s.OrderBy, s.Having, s.GroupBy, s.Where, s.From, s.Results[len(s.Results)-1]) } func (c *CompoundQuery) Pos() token.Pos { diff --git a/ast/util.go b/ast/util.go index 92229a72..0bbbff6d 100644 --- a/ast/util.go +++ b/ast/util.go @@ -52,12 +52,6 @@ func sqlJoin[T Node](elems []T, sep string) string { // Helper functions for Pos(), End() -// lastElem returns last element of slice s. -// This function corresponds to NodeSliceVar[$] in ast.go. -func lastElem[T any](s []T) T { - return s[len(s)-1] -} - // firstValidEnd returns the first valid Pos() in argument. // "valid" means the node is not nil and Pos().Invalid() is not true. // This function corresponds to "(n0 ?? n1 ?? ...).End()" @@ -87,5 +81,5 @@ func lastEnd[T Node](s []T) token.Pos { if len(s) == 0 { return token.InvalidPos } - return lastElem(s).End() + return s[len(s)-1].End() } From aa8820b0c6386e539898403cd4fb16a67485126e Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:16:24 +0900 Subject: [PATCH 11/12] Revert "Remove lastElem" This reverts commit e1a3e815ecd3fca895317482ec3d072e999d1eb5. --- ast/pos.go | 2 +- ast/util.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ast/pos.go b/ast/pos.go index 458aa1ce..b952ea9e 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -39,7 +39,7 @@ func (c *CTE) End() token.Pos { return c.Rparen + 1 } func (s *Select) Pos() token.Pos { return s.Select } func (s *Select) End() token.Pos { - return firstValidEnd(s.Limit, s.OrderBy, s.Having, s.GroupBy, s.Where, s.From, s.Results[len(s.Results)-1]) + return firstValidEnd(s.Limit, s.OrderBy, s.Having, s.GroupBy, s.Where, s.From, lastElem(s.Results)) } func (c *CompoundQuery) Pos() token.Pos { diff --git a/ast/util.go b/ast/util.go index 0bbbff6d..92229a72 100644 --- a/ast/util.go +++ b/ast/util.go @@ -52,6 +52,12 @@ func sqlJoin[T Node](elems []T, sep string) string { // Helper functions for Pos(), End() +// lastElem returns last element of slice s. +// This function corresponds to NodeSliceVar[$] in ast.go. +func lastElem[T any](s []T) T { + return s[len(s)-1] +} + // firstValidEnd returns the first valid Pos() in argument. // "valid" means the node is not nil and Pos().Invalid() is not true. // This function corresponds to "(n0 ?? n1 ?? ...).End()" @@ -81,5 +87,5 @@ func lastEnd[T Node](s []T) token.Pos { if len(s) == 0 { return token.InvalidPos } - return s[len(s)-1].End() + return lastElem(s).End() } From 47ea7094bc4ac09ecce9ef99bb7ce995f743d0e5 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:24:25 +0900 Subject: [PATCH 12/12] Re-organize helper functions --- ast/pos.go | 47 ++++++++++++++++++++++++++- ast/sql.go | 51 ++++++++++++++++++++++++++++++ ast/util.go | 91 ----------------------------------------------------- 3 files changed, 97 insertions(+), 92 deletions(-) delete mode 100644 ast/util.go diff --git a/ast/pos.go b/ast/pos.go index b952ea9e..c817c775 100644 --- a/ast/pos.go +++ b/ast/pos.go @@ -4,6 +4,51 @@ import ( "github.com/cloudspannerecosystem/memefish/token" ) +// ================================================================================ +// +// Helper functions for Pos(), End() +// These functions are intended for use within this file only. +// +// ================================================================================ + +// lastNode returns last element of Node slice. +// This function corresponds to NodeSliceVar[$] in ast.go. +func lastNode[T Node](s []T) T { + return s[len(s)-1] +} + +// firstValidEnd returns the first valid Pos() in argument. +// "valid" means the node is not nil and Pos().Invalid() is not true. +// This function corresponds to "(n0 ?? n1 ?? ...).End()" +func firstValidEnd(ns ...Node) token.Pos { + for _, n := range ns { + if n != nil && !n.End().Invalid() { + return n.End() + } + } + return token.InvalidPos +} + +// firstPos returns the Pos() of the first node. +// If argument is an empty slice, this function returns token.InvalidPos. +// This function corresponds to NodeSliceVar[0].pos in ast.go. +func firstPos[T Node](s []T) token.Pos { + if len(s) == 0 { + return token.InvalidPos + } + return s[0].Pos() +} + +// lastEnd returns the End() of the last node. +// If argument is an empty slice, this function returns token.InvalidPos. +// This function corresponds to NodeSliceVar[$].end in ast.go. +func lastEnd[T Node](s []T) token.Pos { + if len(s) == 0 { + return token.InvalidPos + } + return lastNode(s).End() +} + // ================================================================================ // // SELECT @@ -39,7 +84,7 @@ func (c *CTE) End() token.Pos { return c.Rparen + 1 } func (s *Select) Pos() token.Pos { return s.Select } func (s *Select) End() token.Pos { - return firstValidEnd(s.Limit, s.OrderBy, s.Having, s.GroupBy, s.Where, s.From, lastElem(s.Results)) + return firstValidEnd(s.Limit, s.OrderBy, s.Having, s.GroupBy, s.Where, s.From, lastNode(s.Results)) } func (c *CompoundQuery) Pos() token.Pos { diff --git a/ast/sql.go b/ast/sql.go index 82eb6cdc..2cb2c744 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -2,8 +2,59 @@ package ast import ( "github.com/cloudspannerecosystem/memefish/token" + "strings" ) +// ================================================================================ +// +// Helper functions for SQL() +// These functions are intended for use within this file only. +// +// ================================================================================ + +// sqlOpt outputs: +// +// when node != nil: left + node.SQL() + right +// else : empty string +// +// This function corresponds to sqlOpt in ast.go +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 +} + +// strOpt outputs: +// +// when pred == true: s +// else : empty string +// +// This function corresponds to {{if pred}}s{{end}} in ast.go +func strOpt(pred bool, s string) string { + if pred { + return s + } + return "" +} + +// 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() +} + type prec int const ( diff --git a/ast/util.go b/ast/util.go deleted file mode 100644 index 92229a72..00000000 --- a/ast/util.go +++ /dev/null @@ -1,91 +0,0 @@ -package ast - -import ( - "github.com/cloudspannerecosystem/memefish/token" - "strings" -) - -// Helper functions for SQL() - -// sqlOpt outputs: -// -// when node != nil: left + node.SQL() + right -// else : empty string -// -// This function corresponds to sqlOpt in ast.go -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 -} - -// strOpt outputs: -// -// when pred == true: s -// else : empty string -// -// This function corresponds to {{if pred}}s{{end}} in ast.go -func strOpt(pred bool, s string) string { - if pred { - return s - } - return "" -} - -// 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() -} - -// Helper functions for Pos(), End() - -// lastElem returns last element of slice s. -// This function corresponds to NodeSliceVar[$] in ast.go. -func lastElem[T any](s []T) T { - return s[len(s)-1] -} - -// firstValidEnd returns the first valid Pos() in argument. -// "valid" means the node is not nil and Pos().Invalid() is not true. -// This function corresponds to "(n0 ?? n1 ?? ...).End()" -func firstValidEnd(ns ...Node) token.Pos { - for _, n := range ns { - if n != nil && !n.End().Invalid() { - return n.End() - } - } - return token.InvalidPos -} - -// firstPos returns the Pos() of the first node. -// If argument is an empty slice, this function returns token.InvalidPos. -// This function corresponds to NodeSliceVar[0].pos in ast.go. -func firstPos[T Node](s []T) token.Pos { - if len(s) == 0 { - return token.InvalidPos - } - return s[0].Pos() -} - -// lastEnd returns the End() of the last node. -// If argument is an empty slice, this function returns token.InvalidPos. -// This function corresponds to NodeSliceVar[$].end in ast.go. -func lastEnd[T Node](s []T) token.Pos { - if len(s) == 0 { - return token.InvalidPos - } - return lastElem(s).End() -}