Skip to content

Commit

Permalink
Merge pull request #169 from doug-martin/issue164
Browse files Browse the repository at this point in the history
[ADDED] Using Update, Insert, or Delete datasets in sub selects and C…
  • Loading branch information
doug-martin authored Sep 25, 2019
2 parents c485935 + 7e97aca commit b80d936
Show file tree
Hide file tree
Showing 19 changed files with 428 additions and 40 deletions.
4 changes: 4 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# v9.3.0

* [ADDED] Using Update, Insert, or Delete datasets in sub selects and CTEs [#164](https://github.com/doug-martin/goqu/issues/164)

# v9.2.0

* [ADDED] exec.Scanner: New exposed scanner supports iterative scanning [#157](https://github.com/doug-martin/goqu/pull/157) - [@akarl](https://github.com/akarl)
Expand Down
23 changes: 23 additions & 0 deletions delete_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ func Delete(table interface{}) *DeleteDataset {
return newDeleteDataset("default", nil).From(table)
}

func (dd *DeleteDataset) Expression() exp.Expression {
return dd
}

// Clones the dataset
func (dd *DeleteDataset) Clone() exp.Expression {
return dd.copy(dd.clauses)
}

// Set the parameter interpolation behavior. See examples
//
// prepared: If true the dataset WILL NOT interpolate the parameters.
Expand Down Expand Up @@ -197,6 +206,20 @@ func (dd *DeleteDataset) ToSQL() (sql string, params []interface{}, err error) {
return dd.deleteSQLBuilder().ToSQL()
}

// Appends this Dataset's DELETE statement to the SQLBuilder
// This is used internally when using deletes in CTEs
func (dd *DeleteDataset) AppendSQL(b sb.SQLBuilder) {
dd.dialect.ToDeleteSQL(b, dd.GetClauses())
}

func (dd *DeleteDataset) GetAs() exp.IdentifierExpression {
return nil
}

func (dd *DeleteDataset) ReturnsColumns() bool {
return !dd.clauses.Returning().IsEmpty()
}

// Creates an QueryExecutor to execute the query.
// db.Delete("test").Exec()
//
Expand Down
17 changes: 17 additions & 0 deletions delete_dataset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ func (dds *deleteDatasetSuite) TearDownSuite() {
DeregisterDialect("order-on-delete")
}

func (dds *deleteDatasetSuite) TestDelete() {
ds := Delete("test")
dds.IsType(&DeleteDataset{}, ds)
dds.Implements((*exp.Expression)(nil), ds)
dds.Implements((*exp.AppendableExpression)(nil), ds)
}

func (dds *deleteDatasetSuite) TestClone() {
ds := Delete("test")
dds.Equal(ds.Clone(), ds)
}

func (dds *deleteDatasetSuite) TestExpression() {
ds := Delete("test")
dds.Equal(ds.Expression(), ds)
}

func (dds *deleteDatasetSuite) TestDialect() {
ds := Delete("test")
dds.NotNil(ds.Dialect())
Expand Down
123 changes: 122 additions & 1 deletion docs/selecting.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* [`GroupBy`](#group_by)
* [`Having`](#having)
* [`Window`](#window)
* [`With`](#with)
* [`SetError`](#seterror)
* Executing Queries
* [`ScanStructs`](#scan-structs) - Scans rows into a slice of structs
Expand Down Expand Up @@ -613,13 +614,132 @@ Output:
SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000)
```

<a name="with"></a>
**[`With`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.With)**

To use CTEs in `SELECT` statements you can use the `With` method.

Simple Example

```go
sql, _, _ := goqu.From("one").
With("one", goqu.From().Select(goqu.L("1"))).
Select(goqu.Star()).
ToSQL()
fmt.Println(sql)
```

Output:

```
WITH one AS (SELECT 1) SELECT * FROM "one"
```

Dependent `WITH` clauses:

```go
sql, _, _ = goqu.From("derived").
With("intermed", goqu.From("test").Select(goqu.Star()).Where(goqu.C("x").Gte(5))).
With("derived", goqu.From("intermed").Select(goqu.Star()).Where(goqu.C("x").Lt(10))).
Select(goqu.Star()).
ToSQL()
fmt.Println(sql)
```

Output:
```
WITH intermed AS (SELECT * FROM "test" WHERE ("x" >= 5)), derived AS (SELECT * FROM "intermed" WHERE ("x" < 10)) SELECT * FROM "derived"
```

`WITH` clause with arguments

```go
sql, _, _ = goqu.From("multi").
With("multi(x,y)", goqu.From().Select(goqu.L("1"), goqu.L("2"))).
Select(goqu.C("x"), goqu.C("y")).
ToSQL()
fmt.Println(sql)
```

Output:
```
WITH multi(x,y) AS (SELECT 1, 2) SELECT "x", "y" FROM "multi"
```

Using a `InsertDataset`.

```go
insertDs := goqu.Insert("foo").Rows(goqu.Record{"user_id": 10}).Returning("id")

ds := goqu.From("bar").
With("ins", insertDs).
Select("bar_name").
Where(goqu.Ex{"bar.user_id": goqu.I("ins.user_id")})

sql, _, _ := ds.ToSQL()
fmt.Println(sql)

sql, args, _ := ds.Prepared(true).ToSQL()
fmt.Println(sql, args)
```
Output:
```
WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (10) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")
WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (?) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id") [10]
```

Using an `UpdateDataset`

```go
updateDs := goqu.Update("foo").Set(goqu.Record{"bar": "baz"}).Returning("id")

ds := goqu.From("bar").
With("upd", updateDs).
Select("bar_name").
Where(goqu.Ex{"bar.user_id": goqu.I("ins.user_id")})

sql, _, _ := ds.ToSQL()
fmt.Println(sql)

sql, args, _ := ds.Prepared(true).ToSQL()
fmt.Println(sql, args)
```

Output:
```
WITH upd AS (UPDATE "foo" SET "bar"='baz' RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")
WITH upd AS (UPDATE "foo" SET "bar"=? RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id") [baz]
```

Using a `DeleteDataset`

```go
deleteDs := goqu.Delete("foo").Where(goqu.Ex{"bar": "baz"}).Returning("id")

ds := goqu.From("bar").
With("del", deleteDs).
Select("bar_name").
Where(goqu.Ex{"bar.user_id": goqu.I("del.user_id")})

sql, _, _ := ds.ToSQL()
fmt.Println(sql)

sql, args, _ := ds.Prepared(true).ToSQL()
fmt.Println(sql, args)
```

Output:
```
WITH del AS (DELETE FROM "foo" WHERE ("bar" = 'baz') RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")
WITH del AS (DELETE FROM "foo" WHERE ("bar" = ?) RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id") [baz]
```

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

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

To use windowing in select you can use the `Over` method on an `SQLFunction`
To use windowing in `SELECT` statements you can use the `Over` method on an `SQLFunction`

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



7 changes: 6 additions & 1 deletion exp/exp.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,12 @@ type (
AppendableExpression interface {
Expression
AppendSQL(b sb.SQLBuilder)
GetClauses() SelectClauses
// Returns the alias value as an identiier expression
GetAs() IdentifierExpression

// Returns true if this expression returns columns.
// Used to determine if a Select, Update, Insert, or Delete query returns columns
ReturnsColumns() bool
}
// Expression for Aliased expressions
// I("a").As("b") -> "a" AS "b"
Expand Down
14 changes: 14 additions & 0 deletions insert_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,20 @@ func (id *InsertDataset) ToSQL() (sql string, params []interface{}, err error) {
return id.insertSQLBuilder().ToSQL()
}

// Appends this Dataset's INSERT statement to the SQLBuilder
// This is used internally when using inserts in CTEs
func (id *InsertDataset) AppendSQL(b sb.SQLBuilder) {
id.dialect.ToInsertSQL(b, id.GetClauses())
}

func (id *InsertDataset) GetAs() exp.IdentifierExpression {
return nil
}

func (id *InsertDataset) ReturnsColumns() bool {
return !id.clauses.Returning().IsEmpty()
}

// Generates the INSERT sql, and returns an QueryExecutor struct with the sql set to the INSERT statement
// db.Insert("test").Rows(Record{"name":"Bob"}).Executor().Exec()
//
Expand Down
7 changes: 7 additions & 0 deletions insert_dataset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ func (ids *insertDatasetSuite) assertCases(cases ...insertTestCase) {
}
}

func (ids *insertDatasetSuite) TestInsert() {
ds := Insert("test")
ids.IsType(&InsertDataset{}, ds)
ids.Implements((*exp.Expression)(nil), ds)
ids.Implements((*exp.AppendableExpression)(nil), ds)
}

func (ids *insertDatasetSuite) TestClone() {
ds := Insert("test")
ids.Equal(ds.Clone(), ds)
Expand Down
76 changes: 76 additions & 0 deletions issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,82 @@ func (gis *githubIssuesSuite) TestIssue140() {

}

// Test for https://github.com/doug-martin/goqu/issues/164
func (gis *githubIssuesSuite) TestIssue164() {
insertDs := goqu.Insert("foo").Rows(goqu.Record{"user_id": 10}).Returning("id")

ds := goqu.From("bar").
With("ins", insertDs).
Select("bar_name").
Where(goqu.Ex{"bar.user_id": goqu.I("ins.user_id")})

sql, args, err := ds.ToSQL()
gis.NoError(err)
gis.Empty(args)
gis.Equal(
`WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (10) RETURNING "id") `+
`SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")`,
sql,
)

sql, args, err = ds.Prepared(true).ToSQL()
gis.NoError(err)
gis.Equal([]interface{}{int64(10)}, args)
gis.Equal(
`WITH ins AS (INSERT INTO "foo" ("user_id") VALUES (?) RETURNING "id")`+
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "ins"."user_id")`,
sql,
)

updateDs := goqu.Update("foo").Set(goqu.Record{"bar": "baz"}).Returning("id")

ds = goqu.From("bar").
With("upd", updateDs).
Select("bar_name").
Where(goqu.Ex{"bar.user_id": goqu.I("upd.user_id")})

sql, args, err = ds.ToSQL()
gis.NoError(err)
gis.Empty(args)
gis.Equal(
`WITH upd AS (UPDATE "foo" SET "bar"='baz' RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")`,
sql,
)

sql, args, err = ds.Prepared(true).ToSQL()
gis.NoError(err)
gis.Equal([]interface{}{"baz"}, args)
gis.Equal(
`WITH upd AS (UPDATE "foo" SET "bar"=? RETURNING "id") SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "upd"."user_id")`,
sql,
)

deleteDs := goqu.Delete("foo").Where(goqu.Ex{"bar": "baz"}).Returning("id")

ds = goqu.From("bar").
With("del", deleteDs).
Select("bar_name").
Where(goqu.Ex{"bar.user_id": goqu.I("del.user_id")})

sql, args, err = ds.ToSQL()
gis.NoError(err)
gis.Empty(args)
gis.Equal(
`WITH del AS (DELETE FROM "foo" WHERE ("bar" = 'baz') RETURNING "id")`+
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")`,
sql,
)

sql, args, err = ds.Prepared(true).ToSQL()
gis.NoError(err)
gis.Equal([]interface{}{"baz"}, args)
gis.Equal(
`WITH del AS (DELETE FROM "foo" WHERE ("bar" = ?) RETURNING "id")`+
` SELECT "bar_name" FROM "bar" WHERE ("bar"."user_id" = "del"."user_id")`,
sql,
)
}

func TestGithubIssuesSuite(t *testing.T) {
suite.Run(t, new(githubIssuesSuite))
}
9 changes: 9 additions & 0 deletions select_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,11 @@ func (sd *SelectDataset) As(alias string) *SelectDataset {
return sd.copy(sd.clauses.SetAlias(T(alias)))
}

// Returns the alias value as an identiier expression
func (sd *SelectDataset) GetAs() exp.IdentifierExpression {
return sd.clauses.Alias()
}

// Sets the WINDOW clauses
func (sd *SelectDataset) Window(ws ...exp.WindowExpression) *SelectDataset {
return sd.copy(sd.clauses.SetWindows(ws))
Expand Down Expand Up @@ -552,6 +557,10 @@ func (sd *SelectDataset) AppendSQL(b sb.SQLBuilder) {
sd.dialect.ToSelectSQL(b, sd.GetClauses())
}

func (sd *SelectDataset) ReturnsColumns() bool {
return true
}

// Generates the SELECT sql for this dataset and uses Exec#ScanStructs to scan the results into a slice of structs.
//
// ScanStructs will only select the columns that can be scanned in to the struct unless you have explicitly selected
Expand Down
Loading

0 comments on commit b80d936

Please sign in to comment.