Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v8.0.1 #113

Merged
merged 2 commits into from
Jul 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## v8.0.1

* [ADDED] Multi table update support for `mysql` and `postgres` [#60](https://github.com/doug-martin/goqu/issues/60)
* [ADDED] `goqu.V` so values can be used on the LHS of expressions [#104](https://github.com/doug-martin/goqu/issues/104)

## v8.0.0

A major change the the API was made in `v8` to seperate concerns between the different SQL statement types.
Expand Down
2 changes: 2 additions & 0 deletions dialect/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func DialectOptions() *goqu.SQLDialectOptions {
opts.SupportsWithCTE = false
opts.SupportsWithCTERecursive = false

opts.UseFromClauseForMultipleUpdateTables = false

opts.PlaceHolderRune = '?'
opts.IncludePlaceholderNum = false
opts.QuoteRune = '`'
Expand Down
10 changes: 10 additions & 0 deletions dialect/mysql/mysql_dialect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,17 @@ func (mds *mysqlDialectSuite) TestBooleanOperations() {
sql, _, err = ds.Where(col.NotILike(regexp.MustCompile("(a|b)"))).ToSQL()
assert.NoError(t, err)
assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')")
}

func (mds *mysqlDialectSuite) TestUpdateSQL() {
ds := mds.GetDs("test").Update()
sql, _, err := ds.
Set(goqu.Record{"foo": "bar"}).
From("test_2").
Where(goqu.I("test.id").Eq(goqu.I("test_2.test_id"))).
ToSQL()
mds.NoError(err)
mds.Equal("UPDATE `test`,`test_2` SET `foo`='bar' WHERE (`test`.`id` = `test_2`.`test_id`)", sql)
}

func TestDatasetAdapterSuite(t *testing.T) {
Expand Down
18 changes: 18 additions & 0 deletions dialect/mysql/mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,24 @@ func (mt *mysqlTest) TestQuery() {
assert.Len(t, entries, 0)
}

func (mt *mysqlTest) TestQuery_ValueExpressions() {
type wrappedEntry struct {
entry
BoolValue bool `db:"bool_value"`
}
expectedDate, err := time.Parse("2006-01-02 15:04:05", "2015-02-22 19:19:55")
mt.NoError(err)
ds := mt.db.From("entry").Select(goqu.Star(), goqu.V(true).As("bool_value")).Where(goqu.Ex{"int": 1})
var we wrappedEntry
found, err := ds.ScanStruct(&we)
mt.NoError(err)
mt.True(found)
mt.Equal(we, wrappedEntry{
entry{2, 1, 0.100000, "0.100000", expectedDate, false, []byte("0.100000")},
true,
})
}

func (mt *mysqlTest) TestCount() {
t := mt.T()
ds := mt.db.From("entry")
Expand Down
32 changes: 32 additions & 0 deletions dialect/postgres/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,27 @@ func (pt *postgresTest) TestQuery() {
assert.Len(t, entries, 0)
}

func (pt *postgresTest) TestQuery_ValueExpressions() {
type wrappedEntry struct {
entry
BoolValue bool `db:"bool_value"`
}
expectedDate, err := time.Parse(time.RFC3339Nano, "2015-02-22T19:19:55.000000000-00:00")
pt.NoError(err)
ds := pt.db.From("entry").Select(goqu.Star(), goqu.V(true).As("bool_value")).Where(goqu.Ex{"int": 1})
var we wrappedEntry
found, err := ds.ScanStruct(&we)
pt.NoError(err)
pt.True(found)
pt.Equal(1, we.Int)
pt.Equal(0.100000, we.Float)
pt.Equal("0.100000", we.String)
pt.Equal(expectedDate.Unix(), we.Time.Unix())
pt.Equal(false, we.Bool)
pt.Equal([]byte("0.100000"), we.Bytes)
pt.True(we.BoolValue)
}

func (pt *postgresTest) TestCount() {
t := pt.T()
ds := pt.db.From("entry")
Expand Down Expand Up @@ -311,6 +332,17 @@ func (pt *postgresTest) TestUpdate() {
assert.Equal(t, id, e.ID)
}

func (pt *postgresTest) TestUpdateSQL_multipleTables() {
ds := pt.db.Update("test")
updateSQL, _, err := ds.
Set(goqu.Record{"foo": "bar"}).
From("test_2").
Where(goqu.I("test.id").Eq(goqu.I("test_2.test_id"))).
ToSQL()
pt.NoError(err)
pt.Equal(`UPDATE "test" SET "foo"='bar' FROM "test_2" WHERE ("test"."id" = "test_2"."test_id")`, updateSQL)
}

func (pt *postgresTest) TestDelete() {
t := pt.T()
ds := pt.db.From("entry")
Expand Down
1 change: 1 addition & 0 deletions dialect/sqlite3/sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func DialectOptions() *goqu.SQLDialectOptions {
opts.SupportsConflictUpdateWhere = false
opts.SupportsInsertIgnoreSyntax = true
opts.SupportsConflictTarget = false
opts.SupportsMultipleUpdateTables = false
opts.WrapCompoundsInParens = false

opts.PlaceHolderRune = '?'
Expand Down
10 changes: 10 additions & 0 deletions dialect/sqlite3/sqlite3_dialect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ func (sds *sqlite3DialectSuite) TestIdentifiers() {
assert.Equal(t, sql, "SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`")
}

func (sds *sqlite3DialectSuite) TestUpdateSQL_multipleTables() {
ds := sds.GetDs("test").Update()
_, _, err := ds.
Set(goqu.Record{"foo": "bar"}).
From("test_2").
Where(goqu.I("test.id").Eq(goqu.I("test_2.test_id"))).
ToSQL()
sds.EqualError(err, "goqu: sqlite3 dialect does not support multiple tables in UPDATE")
}

func (sds *sqlite3DialectSuite) TestCompoundExpressions() {
t := sds.T()
ds1 := sds.GetDs("test").Select("a")
Expand Down
18 changes: 18 additions & 0 deletions dialect/sqlite3/sqlite3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,24 @@ func (st *sqlite3Suite) TestQuery() {
assert.Len(t, entries, 0)
}

func (st *sqlite3Suite) TestQuery_ValueExpressions() {
type wrappedEntry struct {
entry
BoolValue bool `db:"bool_value"`
}
expectedDate, err := time.Parse("2006-01-02 15:04:05", "2015-02-22 19:19:55")
st.NoError(err)
ds := st.db.From("entry").Select(goqu.Star(), goqu.V(true).As("bool_value")).Where(goqu.Ex{"int": 1})
var we wrappedEntry
found, err := ds.ScanStruct(&we)
st.NoError(err)
st.True(found)
st.Equal(we, wrappedEntry{
entry{2, 1, 0.100000, "0.100000", expectedDate, false, []byte("0.100000")},
true,
})
}

func (st *sqlite3Suite) TestCount() {
t := st.T()
ds := st.db.From("entry")
Expand Down
59 changes: 58 additions & 1 deletion docs/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
* [`T`](#T) - An Identifier that represents a Table. With a Table identifier you can fully qualify columns.
* [`C`](#C) - An Identifier that represents a Column. See the docs for more examples
* [`I`](#I) - An Identifier represents a schema, table, or column or any combination. I parses identifiers seperated by a . character.
* [`L`](#L) - An SQL literal.
* [`L`](#L) - An SQL literal.
* [`V`](#V) - An Value to be used in SQL.
* [`And`](#and) - AND multiple expressions together.
* [`Or`](#or) - OR multiple expressions together.
* [Complex Example] - Complex Example using most of the Expression DSL.
Expand Down Expand Up @@ -201,6 +202,61 @@ SELECT * FROM "test" WHERE ("json"::TEXT = "other_json"::TEXT) AND col IN ('a',
SELECT * FROM "test" WHERE ("json"::TEXT = "other_json"::TEXT) AND col IN ($1, $2, $3) [a, b, c]
```

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

Sometimes you may have a value that you want to use directly in SQL.

**NOTE** This is a shorter version of `goqu.L("?", val)`

For example you may want to select a value as a column.

```go
ds := goqu.From("user").Select(
goqu.V(true).As("is_verified"),
goqu.V(1.2).As("version"),
"first_name",
"last_name",
)

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

Output:
```
SELECT TRUE AS "is_verified", 1.2 AS "version", "first_name", "last_name" FROM "user" []
```

You can also use `goqu.V` in where clauses.

```
ds := goqu.From("user").Where(goqu.V(1).Neq(1))
sql, args, _ := ds.ToSQL()
fmt.Println(sql, args)
```

Output:

```
SELECT * FROM "user" WHERE (1 != 1) []
```

You can also use them in prepared statements.

```
ds := goqu.From("user").Where(goqu.V(1).Neq(1))
sql, args, _ := ds.Prepared(true).ToSQL()
fmt.Println(sql, args)
```

Output:

```
SELECT * FROM "user" WHERE (? != ?) [1, 1]
```


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

Expand Down Expand Up @@ -388,3 +444,4 @@ HAVING (AVG("test3"."age") > ?)
ORDER BY "test"."created" DESC NULLS LAST [^(a|b) passed active registered 10]
```


45 changes: 45 additions & 0 deletions docs/updating.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* [Set with `goqu.Record`](#set-record)
* [Set with struct](#set-struct)
* [Set with map](#set-map)
* [Multi Table](#from)
* [Where](#where)
* [Order](#order)
* [Limit](#limit)
Expand Down Expand Up @@ -167,6 +168,50 @@ Output:
UPDATE "items" SET "address"='111 Test Addr',"name"='Test' []
```

<a name="from"></a>
**[From / Multi Table](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.From)**

`goqu` allows joining multiple tables in a update clause through `From`.

**NOTE** The `sqlite3` adapter does not support a multi table syntax.

`Postgres` Example

```go
dialect := goqu.Dialect("postgres")

ds := dialect.Update("table_one").
Set(goqu.Record{"foo": goqu.I("table_two.bar")}).
From("table_two").
Where(goqu.Ex{"table_one.id": goqu.I("table_two.id")})

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

Output:
```sql
UPDATE "table_one" SET "foo"="table_two"."bar" FROM "table_two" WHERE ("table_one"."id" = "table_two"."id")
```

`MySQL` Example

```go
dialect := goqu.Dialect("mysql")

ds := dialect.Update("table_one").
Set(goqu.Record{"foo": goqu.I("table_two.bar")}).
From("table_two").
Where(goqu.Ex{"table_one.id": goqu.I("table_two.id")})

sql, _, _ := ds.ToSQL()
fmt.Println(sql)
```
Output:
```sql
UPDATE `table_one`,`table_two` SET `foo`=`table_two`.`bar` WHERE (`table_one`.`id` = `table_two`.`id`)
```

<a name="where"></a>
**[Where](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Where)**

Expand Down
26 changes: 26 additions & 0 deletions exp/update_clauses.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ type (
SetTable(table Expression) UpdateClauses

SetValues() interface{}
HasSetValues() bool
SetSetValues(values interface{}) UpdateClauses

From() ColumnListExpression
HasFrom() bool
SetFrom(tables ColumnListExpression) UpdateClauses

Where() ExpressionList
ClearWhere() UpdateClauses
WhereAppend(expressions ...Expression) UpdateClauses
Expand All @@ -38,6 +43,7 @@ type (
commonTables []CommonTableExpression
table Expression
setValues interface{}
from ColumnListExpression
where ExpressionList
order ColumnListExpression
limit interface{}
Expand All @@ -58,6 +64,7 @@ func (uc *updateClauses) clone() *updateClauses {
commonTables: uc.commonTables,
table: uc.table,
setValues: uc.setValues,
from: uc.from,
where: uc.where,
order: uc.order,
limit: uc.limit,
Expand Down Expand Up @@ -86,12 +93,31 @@ func (uc *updateClauses) SetTable(table Expression) UpdateClauses {
func (uc *updateClauses) SetValues() interface{} {
return uc.setValues
}

func (uc *updateClauses) HasSetValues() bool {
return uc.setValues != nil
}

func (uc *updateClauses) SetSetValues(values interface{}) UpdateClauses {
ret := uc.clone()
ret.setValues = values
return ret
}

func (uc *updateClauses) From() ColumnListExpression {
return uc.from
}

func (uc *updateClauses) HasFrom() bool {
return uc.from != nil && !uc.from.IsEmpty()
}

func (uc *updateClauses) SetFrom(from ColumnListExpression) UpdateClauses {
ret := uc.clone()
ret.from = from
return ret
}

func (uc *updateClauses) Where() ExpressionList {
return uc.where
}
Expand Down
23 changes: 23 additions & 0 deletions exp/update_clauses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,29 @@ func (ucs *updateClausesSuite) TestSetSetValues() {
assert.Equal(t, r2, c2.SetValues())
}

func (ucs *updateClausesSuite) TestFrom() {
t := ucs.T()
c := NewUpdateClauses()
ce := NewColumnListExpression("a", "b")
c2 := c.SetFrom(ce)

assert.Nil(t, c.From())

assert.Equal(t, ce, c2.From())
}

func (ucs *updateClausesSuite) TestSetFrom() {
t := ucs.T()
ce1 := NewColumnListExpression("a", "b")
c := NewUpdateClauses().SetFrom(ce1)
ce2 := NewColumnListExpression("a", "b")
c2 := c.SetFrom(ce2)

assert.Equal(t, ce1, c.From())

assert.Equal(t, ce2, c2.From())
}

func (ucs *updateClausesSuite) TestWhere() {
t := ucs.T()
w := Ex{"a": 1}
Expand Down
6 changes: 6 additions & 0 deletions expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ func Literal(sql string, args ...interface{}) exp.LiteralExpression {
return L(sql, args...)
}

// Create a new SQL value ( alias for goqu.L("?", val) ). The prrimary use case for this would be in selects.
// See examples.
func V(val interface{}) exp.LiteralExpression {
return exp.NewLiteralExpression("?", val)
}

// Creates a new Range to be used with a Between expression
// exp.C("col").Between(exp.Range(1, 10))
func Range(start, end interface{}) exp.RangeVal {
Expand Down
Loading