From aa3775b18bc0ac194bf3783b42b9b91a210f530b Mon Sep 17 00:00:00 2001 From: Andrew Churchill Date: Thu, 9 Oct 2025 12:58:49 -0700 Subject: [PATCH 1/6] add CreateOrReplaceTable --- createtable.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/createtable.go b/createtable.go index 31b163d..e696c72 100644 --- a/createtable.go +++ b/createtable.go @@ -73,6 +73,14 @@ func (ctb *CreateTableBuilder) CreateTempTable(table string) *CreateTableBuilder return ctb } +// CreateOrReplaceTable sets the table name and changes the verb of ctb to CREATE OR REPLACE TABLE. +func (ctb *CreateTableBuilder) CreateOrReplaceTable(table string) *CreateTableBuilder { + ctb.verb = "CREATE OR REPLACE TABLE" + ctb.table = Escape(table) + ctb.marker = createTableMarkerAfterCreate + return ctb +} + // IfNotExists adds IF NOT EXISTS before table name in CREATE TABLE. func (ctb *CreateTableBuilder) IfNotExists() *CreateTableBuilder { ctb.ifNotExists = true From 5a25b47ce0e84c7fabd853b846efd428d90d7826 Mon Sep 17 00:00:00 2001 From: Andrew Churchill Date: Fri, 10 Oct 2025 01:13:22 -0700 Subject: [PATCH 2/6] add array join option --- select.go | 1 + 1 file changed, 1 insertion(+) diff --git a/select.go b/select.go index da7f98e..889a8af 100644 --- a/select.go +++ b/select.go @@ -29,6 +29,7 @@ type JoinOption string // Join options. const ( + ArrayJoin JoinOption = "ARRAY JOIN" FullJoin JoinOption = "FULL" FullOuterJoin JoinOption = "FULL OUTER" InnerJoin JoinOption = "INNER" From 09ba88ee9603b95c3073579eb00051cc65b331fb Mon Sep 17 00:00:00 2001 From: Andrew Churchill Date: Fri, 10 Oct 2025 01:26:21 -0700 Subject: [PATCH 3/6] add left array join option --- select.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/select.go b/select.go index 889a8af..557607d 100644 --- a/select.go +++ b/select.go @@ -29,10 +29,11 @@ type JoinOption string // Join options. const ( - ArrayJoin JoinOption = "ARRAY JOIN" + ArrayJoin JoinOption = "ARRAY" FullJoin JoinOption = "FULL" FullOuterJoin JoinOption = "FULL OUTER" InnerJoin JoinOption = "INNER" + LeftArrayJoin JoinOption = "LEFT ARRAY" LeftJoin JoinOption = "LEFT" LeftOuterJoin JoinOption = "LEFT OUTER" RightJoin JoinOption = "RIGHT" From 11f54a0730a3efee81dba487199b2b2db03aa077 Mon Sep 17 00:00:00 2001 From: Andrew Churchill Date: Fri, 10 Oct 2025 14:31:14 -0700 Subject: [PATCH 4/6] allow multiple .With calls on CTEBuilder --- cte.go | 6 +++--- cte_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/cte.go b/cte.go index b32dcfa..a9d2b1b 100644 --- a/cte.go +++ b/cte.go @@ -64,7 +64,7 @@ type CTEBuilder struct { var _ Builder = new(CTEBuilder) -// With sets the CTE name and columns. +// With adds the CTE queries to the CTE builder. func (cteb *CTEBuilder) With(queries ...*CTEQueryBuilder) *CTEBuilder { queryBuilderVars := make([]string, 0, len(queries)) @@ -72,8 +72,8 @@ func (cteb *CTEBuilder) With(queries ...*CTEQueryBuilder) *CTEBuilder { queryBuilderVars = append(queryBuilderVars, cteb.args.Add(query)) } - cteb.queries = append([]*CTEQueryBuilder(nil), queries...) - cteb.queryBuilderVars = queryBuilderVars + cteb.queries = append(cteb.queries, queries...) + cteb.queryBuilderVars = append(cteb.queryBuilderVars, queryBuilderVars...) cteb.marker = cteMarkerAfterWith return cteb } diff --git a/cte_test.go b/cte_test.go index 1a9520e..5f2f0ce 100644 --- a/cte_test.go +++ b/cte_test.go @@ -144,6 +144,44 @@ func TestCTEBuilder(t *testing.T) { a.Equal(sql, "/* table init */ t (a, b) /* after table */ AS (SELECT a, b FROM t) /* after table as */") } +func TestMultipleCTEBuilder(t *testing.T) { + a := assert.New(t) + cteb := newCTEBuilder() + ctetb1 := newCTEQueryBuilder() + ctetb2 := newCTEQueryBuilder() + cteb.SQL("/* init */") + cteb.With(ctetb1) + cteb.SQL("/* after with */") + + cteb.With(ctetb2) + + ctetb1.SQL("/* table 1 init */") + ctetb1.Table("t1", "a", "b") + ctetb1.SQL("/* after table 1 */") + + ctetb1.As(Select("a", "b").From("t1")) + ctetb1.SQL("/* after table 1 as */") + + ctetb2.SQL("/* table 2 init */") + ctetb2.Table("t2", "c", "d") + ctetb2.SQL("/* after table 2 */") + + ctetb2.As(Select("c", "d").From("t2")) + ctetb2.SQL("/* after table 2 as */") + + a.Equal(cteb.TableNames(), []string{ctetb1.TableName(), ctetb2.TableName()}) + + sql, args := cteb.Build() + a.Equal(sql, "/* init */ WITH /* table 1 init */ t1 (a, b) /* after table 1 */ AS (SELECT a, b FROM t1) /* after table 1 as */, /* table 2 init */ t2 (c, d) /* after table 2 */ AS (SELECT c, d FROM t2) /* after table 2 as */ /* after with */") + a.Assert(args == nil) + + sql = ctetb1.String() + a.Equal(sql, "/* table 1 init */ t1 (a, b) /* after table 1 */ AS (SELECT a, b FROM t1) /* after table 1 as */") + + sql = ctetb2.String() + a.Equal(sql, "/* table 2 init */ t2 (c, d) /* after table 2 */ AS (SELECT c, d FROM t2) /* after table 2 as */") +} + func TestRecursiveCTEBuilder(t *testing.T) { a := assert.New(t) cteb := newCTEBuilder() From 1ff0c95dc85ea4111c6fd6c3a63049357b8a49bc Mon Sep 17 00:00:00 2001 From: Andrew Churchill Date: Fri, 10 Oct 2025 14:58:50 -0700 Subject: [PATCH 5/6] better api for SelectBuilder#With --- cte.go | 2 +- cte_test.go | 22 ++++++++-------------- select.go | 17 ++++++++++++++--- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/cte.go b/cte.go index a9d2b1b..9bd07b7 100644 --- a/cte.go +++ b/cte.go @@ -87,7 +87,7 @@ func (cteb *CTEBuilder) WithRecursive(queries ...*CTEQueryBuilder) *CTEBuilder { // Select creates a new SelectBuilder to build a SELECT statement using this CTE. func (cteb *CTEBuilder) Select(col ...string) *SelectBuilder { sb := cteb.args.Flavor.NewSelectBuilder() - return sb.With(cteb).Select(col...) + return sb.withCTEBuilder(cteb).Select(col...) } // DeleteFrom creates a new DeleteBuilder to build a DELETE statement using this CTE. diff --git a/cte_test.go b/cte_test.go index 5f2f0ce..bd35a11 100644 --- a/cte_test.go +++ b/cte_test.go @@ -57,13 +57,10 @@ func ExampleCTEBuilder() { usersBuilder.Where( usersBuilder.GreaterEqualThan("level", 10), ) - cteb := With( - CTETable("valid_users").As(usersBuilder), - ) - fmt.Println(cteb) - sb := Select("valid_users.id", "valid_users.name", "orders.id"). - From("users").With(cteb). + sb := With(CTETable("valid_users").As(usersBuilder)). + Select("valid_users.id", "valid_users.name", "orders.id"). + From("users"). Join("orders", "users.id = orders.user_id") sb.Where( sb.LessEqualThan("orders.price", 200), @@ -76,7 +73,6 @@ func ExampleCTEBuilder() { fmt.Println(sb.TableNames()) // Output: - // WITH valid_users AS (SELECT id, name, level FROM users WHERE level >= ?) // WITH valid_users AS (SELECT id, name, level FROM users WHERE level >= ?) SELECT valid_users.id, valid_users.name, orders.id FROM users, valid_users JOIN orders ON users.id = orders.user_id WHERE orders.price <= ? AND valid_users.level < orders.min_level ORDER BY orders.price DESC // [10 200] // [users valid_users] @@ -234,15 +230,13 @@ func TestCTEQueryBuilderGetFlavor(t *testing.T) { func TestCTEBuildClone(t *testing.T) { a := assert.New(t) - cteb := With( - CTETable("users", "id", "name").As( - Select("id", "name").From("users").Where("name IS NOT NULL"), - ), + cte := CTETable("users", "id", "name").As( + Select("id", "name").From("users").Where("name IS NOT NULL"), ) - ctebClone := cteb.Clone() - a.Equal(cteb.String(), ctebClone.String()) + ctebClone := cte.Clone() + a.Equal(cte.String(), ctebClone.String()) - sb := Select("users.id", "users.name", "orders.id").From("users").With(cteb) + sb := Select("users.id", "users.name", "orders.id").From("users").With(cte) sbClone := sb.Clone() a.Equal(sb.String(), sbClone.String()) diff --git a/select.go b/select.go index 557607d..d56cf05 100644 --- a/select.go +++ b/select.go @@ -139,11 +139,22 @@ func (sb *SelectBuilder) TableNames() []string { return tableNames } +func (sb *SelectBuilder) withCTEBuilder(cteBuilder *CTEBuilder) *SelectBuilder { + sb.marker = selectMarkerAfterWith + sb.cteBuilderVar = sb.Var(cteBuilder) + sb.cteBuilder = cteBuilder + return sb +} + // With sets WITH clause (the Common Table Expression) before SELECT. -func (sb *SelectBuilder) With(builder *CTEBuilder) *SelectBuilder { +func (sb *SelectBuilder) With(ctes ...*CTEQueryBuilder) *SelectBuilder { sb.marker = selectMarkerAfterWith - sb.cteBuilderVar = sb.Var(builder) - sb.cteBuilder = builder + + if sb.cteBuilder == nil { + sb.withCTEBuilder(newCTEBuilder()) + } + + sb.cteBuilder.With(ctes...) return sb } From 563fdcbf4c574d19eae712f4a1f0bd8d3bbd7287 Mon Sep 17 00:00:00 2001 From: Andrew Churchill Date: Fri, 10 Oct 2025 15:29:38 -0700 Subject: [PATCH 6/6] add Struct#SelectFromQuery method --- struct.go | 18 ++++++++++++++---- struct_test.go | 9 +++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/struct.go b/struct.go index a23b9c3..b8d5884 100644 --- a/struct.go +++ b/struct.go @@ -282,6 +282,12 @@ func (s *Struct) SelectFrom(table string) *SelectBuilder { return s.selectFromWithTags(table, s.withTags, s.withoutTags) } +// SelectFromQuery uses the given `SelectBuilder` as the base query. +// By default, all exported fields of the s are listed as columns in SELECT. +func (s *Struct) SelectFromQuery(query *SelectBuilder, table string) *SelectBuilder { + return s.selectFromQueryWithTags(query, table, s.withTags, s.withoutTags) +} + // SelectFromForTag creates a new `SelectBuilder` with table name for a specified tag. // By default, all fields of the s tagged with tag are listed as columns in SELECT. // @@ -294,15 +300,19 @@ func (s *Struct) SelectFromForTag(table string, tag string) (sb *SelectBuilder) } func (s *Struct) selectFromWithTags(table string, with, without []string) (sb *SelectBuilder) { - sfs := s.structFieldsParser() - tagged := sfs.FilterTags(with, without) - sb = s.Flavor.NewSelectBuilder() sb.From(table) + return s.selectFromQueryWithTags(sb, table, with, without) +} + +func (s *Struct) selectFromQueryWithTags(sb *SelectBuilder, table string, with, without []string) *SelectBuilder { + sfs := s.structFieldsParser() + tagged := sfs.FilterTags(with, without) + if tagged == nil { sb.Select("*") - return + return sb } buf := newStringBuilder() diff --git a/struct_test.go b/struct_test.go index bb419f8..a16c197 100644 --- a/struct_test.go +++ b/struct_test.go @@ -33,6 +33,15 @@ func TestStructSelectFrom(t *testing.T) { a.Equal(args, nil) } +func TestStructSelectFromQuery(t *testing.T) { + a := assert.New(t) + sb := userForTest.SelectFromQuery(Select().From("user").Where("user.id = 123"), "user") + sql, args := sb.Build() + + a.Equal(sql, "SELECT user.id, user.Name, user.status, user.created_at FROM user WHERE user.id = 123") + a.Equal(args, nil) +} + func TestStructSelectFromForTag(t *testing.T) { a := assert.New(t) sb := userForTest.SelectFromForTag("user", "important")