Skip to content

Commit 8cd72ce

Browse files
authored
Merge pull request #162 from arikkfir/add-recursive-cte-support
Fix #161: Add support for recursive CTEs
2 parents 2cc1f8c + 4b034a7 commit 8cd72ce

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

cte.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ func With(tables ...*CTETableBuilder) *CTEBuilder {
1313
return DefaultFlavor.NewCTEBuilder().With(tables...)
1414
}
1515

16+
// WithRecursive creates a new recursive CTE builder with default flavor.
17+
func WithRecursive(tables ...*CTETableBuilder) *CTEBuilder {
18+
return DefaultFlavor.NewCTEBuilder().WithRecursive(tables...)
19+
}
20+
1621
func newCTEBuilder() *CTEBuilder {
1722
return &CTEBuilder{
1823
args: &Args{},
@@ -22,6 +27,7 @@ func newCTEBuilder() *CTEBuilder {
2227

2328
// CTEBuilder is a CTE (Common Table Expression) builder.
2429
type CTEBuilder struct {
30+
recursive bool
2531
tableNames []string
2632
tableBuilderVars []string
2733

@@ -49,6 +55,12 @@ func (cteb *CTEBuilder) With(tables ...*CTETableBuilder) *CTEBuilder {
4955
return cteb
5056
}
5157

58+
// WithRecursive sets the CTE name and columns and turns on the RECURSIVE keyword.
59+
func (cteb *CTEBuilder) WithRecursive(tables ...*CTETableBuilder) *CTEBuilder {
60+
cteb.With(tables...).recursive = true
61+
return cteb
62+
}
63+
5264
// Select creates a new SelectBuilder to build a SELECT statement using this CTE.
5365
func (cteb *CTEBuilder) Select(col ...string) *SelectBuilder {
5466
sb := cteb.args.Flavor.NewSelectBuilder()
@@ -73,6 +85,9 @@ func (cteb *CTEBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{}
7385

7486
if len(cteb.tableBuilderVars) > 0 {
7587
buf.WriteLeadingString("WITH ")
88+
if cteb.recursive {
89+
buf.WriteString("RECURSIVE ")
90+
}
7691
buf.WriteStrings(cteb.tableBuilderVars, ", ")
7792
}
7893

cte_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,28 @@ func ExampleWith() {
3030
// WITH users (id, name) AS (SELECT id, name FROM users WHERE name IS NOT NULL), devices AS (SELECT device_id FROM devices) SELECT users.id, orders.id, devices.device_id FROM users, devices JOIN orders ON users.id = orders.user_id AND devices.device_id = orders.device_id
3131
}
3232

33+
func ExampleWithRecursive() {
34+
sb := WithRecursive(
35+
CTETable("source_accounts", "id", "parent_id").As(
36+
UnionAll(
37+
Select("p.id", "p.parent_id").
38+
From("accounts AS p").
39+
Where("p.id = 2"), // Show orders for account 2 and all its child accounts
40+
Select("c.id", "c.parent_id").
41+
From("accounts AS c").
42+
Join("source_accounts AS sa", "c.parent_id = sa.id"),
43+
),
44+
),
45+
).Select("o.id", "o.date", "o.amount").
46+
From("orders AS o").
47+
Join("source_accounts", "o.account_id = source_accounts.id")
48+
49+
fmt.Println(sb)
50+
51+
// Output:
52+
// WITH RECURSIVE source_accounts (id, parent_id) AS ((SELECT p.id, p.parent_id FROM accounts AS p WHERE p.id = 2) UNION ALL (SELECT c.id, c.parent_id FROM accounts AS c JOIN source_accounts AS sa ON c.parent_id = sa.id)) SELECT o.id, o.date, o.amount FROM orders AS o JOIN source_accounts ON o.account_id = source_accounts.id
53+
}
54+
3355
func ExampleCTEBuilder() {
3456
usersBuilder := Select("id", "name", "level").From("users")
3557
usersBuilder.Where(
@@ -82,3 +104,27 @@ func TestCTEBuilder(t *testing.T) {
82104
sql = ctetb.String()
83105
a.Equal(sql, "/* table init */ t (a, b) /* after table */ AS (SELECT a, b FROM t) /* after table as */")
84106
}
107+
108+
func TestRecursiveCTEBuilder(t *testing.T) {
109+
a := assert.New(t)
110+
cteb := newCTEBuilder()
111+
cteb.recursive = true
112+
ctetb := newCTETableBuilder()
113+
cteb.SQL("/* init */")
114+
cteb.With(ctetb)
115+
cteb.SQL("/* after with */")
116+
117+
ctetb.SQL("/* table init */")
118+
ctetb.Table("t", "a", "b")
119+
ctetb.SQL("/* after table */")
120+
121+
ctetb.As(Select("a", "b").From("t"))
122+
ctetb.SQL("/* after table as */")
123+
124+
sql, args := cteb.Build()
125+
a.Equal(sql, "/* init */ WITH RECURSIVE /* table init */ t (a, b) /* after table */ AS (SELECT a, b FROM t) /* after table as */ /* after with */")
126+
a.Assert(args == nil)
127+
128+
sql = ctetb.String()
129+
a.Equal(sql, "/* table init */ t (a, b) /* after table */ AS (SELECT a, b FROM t) /* after table as */")
130+
}

0 commit comments

Comments
 (0)