Skip to content

Commit b80d936

Browse files
authored
Merge pull request #169 from doug-martin/issue164
[ADDED] Using Update, Insert, or Delete datasets in sub selects and C…
2 parents c485935 + 7e97aca commit b80d936

19 files changed

Lines changed: 428 additions & 40 deletions

HISTORY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# v9.3.0
2+
3+
* [ADDED] Using Update, Insert, or Delete datasets in sub selects and CTEs [#164](https://github.com/doug-martin/goqu/issues/164)
4+
15
# v9.2.0
26

37
* [ADDED] exec.Scanner: New exposed scanner supports iterative scanning [#157](https://github.com/doug-martin/goqu/pull/157) - [@akarl](https://github.com/akarl)

delete_dataset.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ func Delete(table interface{}) *DeleteDataset {
3030
return newDeleteDataset("default", nil).From(table)
3131
}
3232

33+
func (dd *DeleteDataset) Expression() exp.Expression {
34+
return dd
35+
}
36+
37+
// Clones the dataset
38+
func (dd *DeleteDataset) Clone() exp.Expression {
39+
return dd.copy(dd.clauses)
40+
}
41+
3342
// Set the parameter interpolation behavior. See examples
3443
//
3544
// prepared: If true the dataset WILL NOT interpolate the parameters.
@@ -197,6 +206,20 @@ func (dd *DeleteDataset) ToSQL() (sql string, params []interface{}, err error) {
197206
return dd.deleteSQLBuilder().ToSQL()
198207
}
199208

209+
// Appends this Dataset's DELETE statement to the SQLBuilder
210+
// This is used internally when using deletes in CTEs
211+
func (dd *DeleteDataset) AppendSQL(b sb.SQLBuilder) {
212+
dd.dialect.ToDeleteSQL(b, dd.GetClauses())
213+
}
214+
215+
func (dd *DeleteDataset) GetAs() exp.IdentifierExpression {
216+
return nil
217+
}
218+
219+
func (dd *DeleteDataset) ReturnsColumns() bool {
220+
return !dd.clauses.Returning().IsEmpty()
221+
}
222+
200223
// Creates an QueryExecutor to execute the query.
201224
// db.Delete("test").Exec()
202225
//

delete_dataset_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ func (dds *deleteDatasetSuite) TearDownSuite() {
4949
DeregisterDialect("order-on-delete")
5050
}
5151

52+
func (dds *deleteDatasetSuite) TestDelete() {
53+
ds := Delete("test")
54+
dds.IsType(&DeleteDataset{}, ds)
55+
dds.Implements((*exp.Expression)(nil), ds)
56+
dds.Implements((*exp.AppendableExpression)(nil), ds)
57+
}
58+
59+
func (dds *deleteDatasetSuite) TestClone() {
60+
ds := Delete("test")
61+
dds.Equal(ds.Clone(), ds)
62+
}
63+
64+
func (dds *deleteDatasetSuite) TestExpression() {
65+
ds := Delete("test")
66+
dds.Equal(ds.Expression(), ds)
67+
}
68+
5269
func (dds *deleteDatasetSuite) TestDialect() {
5370
ds := Delete("test")
5471
dds.NotNil(ds.Dialect())

docs/selecting.md

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* [`GroupBy`](#group_by)
1313
* [`Having`](#having)
1414
* [`Window`](#window)
15+
* [`With`](#with)
1516
* [`SetError`](#seterror)
1617
* Executing Queries
1718
* [`ScanStructs`](#scan-structs) - Scans rows into a slice of structs
@@ -613,13 +614,132 @@ Output:
613614
SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000)
614615
```
615616

617+
<a name="with"></a>
618+
**[`With`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.With)**
619+
620+
To use CTEs in `SELECT` statements you can use the `With` method.
621+
622+
Simple Example
623+
624+
```go
625+
sql, _, _ := goqu.From("one").
626+
With("one", goqu.From().Select(goqu.L("1"))).
627+
Select(goqu.Star()).
628+
ToSQL()
629+
fmt.Println(sql)
630+
```
631+
632+
Output:
633+
634+
```
635+
WITH one AS (SELECT 1) SELECT * FROM "one"
636+
```
637+
638+
Dependent `WITH` clauses:
639+
640+
```go
641+
sql, _, _ = goqu.From("derived").
642+
With("intermed", goqu.From("test").Select(goqu.Star()).Where(goqu.C("x").Gte(5))).
643+
With("derived", goqu.From("intermed").Select(goqu.Star()).Where(goqu.C("x").Lt(10))).
644+
Select(goqu.Star()).
645+
ToSQL()
646+
fmt.Println(sql)
647+
```
648+
649+
Output:
650+
```
651+
WITH intermed AS (SELECT * FROM "test" WHERE ("x" >= 5)), derived AS (SELECT * FROM "intermed" WHERE ("x" < 10)) SELECT * FROM "derived"
652+
```
653+
654+
`WITH` clause with arguments
655+
656+
```go
657+
sql, _, _ = goqu.From("multi").
658+
With("multi(x,y)", goqu.From().Select(goqu.L("1"), goqu.L("2"))).
659+
Select(goqu.C("x"), goqu.C("y")).
660+
ToSQL()
661+
fmt.Println(sql)
662+
```
663+
664+
Output:
665+
```
666+
WITH multi(x,y) AS (SELECT 1, 2) SELECT "x", "y" FROM "multi"
667+
```
668+
669+
Using a `InsertDataset`.
670+
671+
```go
672+
insertDs := goqu.Insert("foo").Rows(goqu.Record{"user_id": 10}).Returning("id")
673+
674+
ds := goqu.From("bar").
675+
With("ins", insertDs).
676+
Select("bar_name").
677+
Where(goqu.Ex{"bar.user_id": goqu.I("ins.user_id")})
678+
679+
sql, _, _ := ds.ToSQL()
680+
fmt.Println(sql)
681+
682+
sql, args, _ := ds.Prepared(true).ToSQL()
683+
fmt.Println(sql, args)
684+
```
685+
Output:
686+
```
687+
WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (10) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")
688+
WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (?) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id") [10]
689+
```
690+
691+
Using an `UpdateDataset`
692+
693+
```go
694+
updateDs := goqu.Update("foo").Set(goqu.Record{"bar": "baz"}).Returning("id")
695+
696+
ds := goqu.From("bar").
697+
With("upd", updateDs).
698+
Select("bar_name").
699+
Where(goqu.Ex{"bar.user_id": goqu.I("ins.user_id")})
700+
701+
sql, _, _ := ds.ToSQL()
702+
fmt.Println(sql)
703+
704+
sql, args, _ := ds.Prepared(true).ToSQL()
705+
fmt.Println(sql, args)
706+
```
707+
708+
Output:
709+
```
710+
WITH upd AS (UPDATE "foo" SET "bar"='baz' RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")
711+
WITH upd AS (UPDATE "foo" SET "bar"=? RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id") [baz]
712+
```
713+
714+
Using a `DeleteDataset`
715+
716+
```go
717+
deleteDs := goqu.Delete("foo").Where(goqu.Ex{"bar": "baz"}).Returning("id")
718+
719+
ds := goqu.From("bar").
720+
With("del", deleteDs).
721+
Select("bar_name").
722+
Where(goqu.Ex{"bar.user_id": goqu.I("del.user_id")})
723+
724+
sql, _, _ := ds.ToSQL()
725+
fmt.Println(sql)
726+
727+
sql, args, _ := ds.Prepared(true).ToSQL()
728+
fmt.Println(sql, args)
729+
```
730+
731+
Output:
732+
```
733+
WITH del AS (DELETE FROM "foo" WHERE ("bar" = 'baz') RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")
734+
WITH del AS (DELETE FROM "foo" WHERE ("bar" = ?) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id") [baz]
735+
```
616736

617737
<a name="window"></a>
618738
**[`Window Function`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Window)**
619739

620740
**NOTE** currently only the `postgres`, `mysql8` (NOT `mysql`) and the default dialect support `Window Function`
621741

622-
To use windowing in select you can use the `Over` method on an `SQLFunction`
742+
To use windowing in `SELECT` statements you can use the `Over` method on an `SQLFunction`
623743

624744
```go
625745
sql, _, _ := goqu.From("test").Select(
@@ -916,3 +1036,4 @@ fmt.Printf("\nIds := %+v", ids)
9161036
```
9171037

9181038

1039+

exp/exp.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ type (
143143
AppendableExpression interface {
144144
Expression
145145
AppendSQL(b sb.SQLBuilder)
146-
GetClauses() SelectClauses
146+
// Returns the alias value as an identiier expression
147+
GetAs() IdentifierExpression
148+
149+
// Returns true if this expression returns columns.
150+
// Used to determine if a Select, Update, Insert, or Delete query returns columns
151+
ReturnsColumns() bool
147152
}
148153
// Expression for Aliased expressions
149154
// I("a").As("b") -> "a" AS "b"

insert_dataset.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,20 @@ func (id *InsertDataset) ToSQL() (sql string, params []interface{}, err error) {
218218
return id.insertSQLBuilder().ToSQL()
219219
}
220220

221+
// Appends this Dataset's INSERT statement to the SQLBuilder
222+
// This is used internally when using inserts in CTEs
223+
func (id *InsertDataset) AppendSQL(b sb.SQLBuilder) {
224+
id.dialect.ToInsertSQL(b, id.GetClauses())
225+
}
226+
227+
func (id *InsertDataset) GetAs() exp.IdentifierExpression {
228+
return nil
229+
}
230+
231+
func (id *InsertDataset) ReturnsColumns() bool {
232+
return !id.clauses.Returning().IsEmpty()
233+
}
234+
221235
// Generates the INSERT sql, and returns an QueryExecutor struct with the sql set to the INSERT statement
222236
// db.Insert("test").Rows(Record{"name":"Bob"}).Executor().Exec()
223237
//

insert_dataset_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ func (ids *insertDatasetSuite) assertCases(cases ...insertTestCase) {
3030
}
3131
}
3232

33+
func (ids *insertDatasetSuite) TestInsert() {
34+
ds := Insert("test")
35+
ids.IsType(&InsertDataset{}, ds)
36+
ids.Implements((*exp.Expression)(nil), ds)
37+
ids.Implements((*exp.AppendableExpression)(nil), ds)
38+
}
39+
3340
func (ids *insertDatasetSuite) TestClone() {
3441
ds := Insert("test")
3542
ids.Equal(ds.Clone(), ds)

issues_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,82 @@ func (gis *githubIssuesSuite) TestIssue140() {
248248

249249
}
250250

251+
// Test for https://github.com/doug-martin/goqu/issues/164
252+
func (gis *githubIssuesSuite) TestIssue164() {
253+
insertDs := goqu.Insert("foo").Rows(goqu.Record{"user_id": 10}).Returning("id")
254+
255+
ds := goqu.From("bar").
256+
With("ins", insertDs).
257+
Select("bar_name").
258+
Where(goqu.Ex{"bar.user_id": goqu.I("ins.user_id")})
259+
260+
sql, args, err := ds.ToSQL()
261+
gis.NoError(err)
262+
gis.Empty(args)
263+
gis.Equal(
264+
`WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (10) RETURNING "id") `+
265+
`SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")`,
266+
sql,
267+
)
268+
269+
sql, args, err = ds.Prepared(true).ToSQL()
270+
gis.NoError(err)
271+
gis.Equal([]interface{}{int64(10)}, args)
272+
gis.Equal(
273+
`WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (?) RETURNING "id")`+
274+
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")`,
275+
sql,
276+
)
277+
278+
updateDs := goqu.Update("foo").Set(goqu.Record{"bar": "baz"}).Returning("id")
279+
280+
ds = goqu.From("bar").
281+
With("upd", updateDs).
282+
Select("bar_name").
283+
Where(goqu.Ex{"bar.user_id": goqu.I("upd.user_id")})
284+
285+
sql, args, err = ds.ToSQL()
286+
gis.NoError(err)
287+
gis.Empty(args)
288+
gis.Equal(
289+
`WITH upd AS (UPDATE "foo" SET "bar"='baz' RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")`,
290+
sql,
291+
)
292+
293+
sql, args, err = ds.Prepared(true).ToSQL()
294+
gis.NoError(err)
295+
gis.Equal([]interface{}{"baz"}, args)
296+
gis.Equal(
297+
`WITH upd AS (UPDATE "foo" SET "bar"=? RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")`,
298+
sql,
299+
)
300+
301+
deleteDs := goqu.Delete("foo").Where(goqu.Ex{"bar": "baz"}).Returning("id")
302+
303+
ds = goqu.From("bar").
304+
With("del", deleteDs).
305+
Select("bar_name").
306+
Where(goqu.Ex{"bar.user_id": goqu.I("del.user_id")})
307+
308+
sql, args, err = ds.ToSQL()
309+
gis.NoError(err)
310+
gis.Empty(args)
311+
gis.Equal(
312+
`WITH del AS (DELETE FROM "foo" WHERE ("bar" = 'baz') RETURNING "id")`+
313+
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")`,
314+
sql,
315+
)
316+
317+
sql, args, err = ds.Prepared(true).ToSQL()
318+
gis.NoError(err)
319+
gis.Equal([]interface{}{"baz"}, args)
320+
gis.Equal(
321+
`WITH del AS (DELETE FROM "foo" WHERE ("bar" = ?) RETURNING "id")`+
322+
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")`,
323+
sql,
324+
)
325+
}
326+
251327
func TestGithubIssuesSuite(t *testing.T) {
252328
suite.Run(t, new(githubIssuesSuite))
253329
}

select_dataset.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,11 @@ func (sd *SelectDataset) As(alias string) *SelectDataset {
498498
return sd.copy(sd.clauses.SetAlias(T(alias)))
499499
}
500500

501+
// Returns the alias value as an identiier expression
502+
func (sd *SelectDataset) GetAs() exp.IdentifierExpression {
503+
return sd.clauses.Alias()
504+
}
505+
501506
// Sets the WINDOW clauses
502507
func (sd *SelectDataset) Window(ws ...exp.WindowExpression) *SelectDataset {
503508
return sd.copy(sd.clauses.SetWindows(ws))
@@ -552,6 +557,10 @@ func (sd *SelectDataset) AppendSQL(b sb.SQLBuilder) {
552557
sd.dialect.ToSelectSQL(b, sd.GetClauses())
553558
}
554559

560+
func (sd *SelectDataset) ReturnsColumns() bool {
561+
return true
562+
}
563+
555564
// Generates the SELECT sql for this dataset and uses Exec#ScanStructs to scan the results into a slice of structs.
556565
//
557566
// ScanStructs will only select the columns that can be scanned in to the struct unless you have explicitly selected

0 commit comments

Comments
 (0)