diff --git a/delete_dataset.go b/delete_dataset.go index 0c28a778..5f5474b2 100644 --- a/delete_dataset.go +++ b/delete_dataset.go @@ -14,6 +14,7 @@ type DeleteDataset struct { clauses exp.DeleteClauses isPrepared bool queryFactory exec.QueryFactory + err error } // used internally by database to create a database with a specific adapter @@ -74,6 +75,7 @@ func (dd *DeleteDataset) copy(clauses exp.DeleteClauses) *DeleteDataset { clauses: clauses, isPrepared: dd.isPrepared, queryFactory: dd.queryFactory, + err: dd.err, } } @@ -170,6 +172,22 @@ func (dd *DeleteDataset) Returning(returning ...interface{}) *DeleteDataset { return dd.copy(dd.clauses.SetReturning(exp.NewColumnListExpression(returning...))) } +// Get any error that has been set or nil if no error has been set. +func (dd *DeleteDataset) Error() error { + return dd.err +} + +// Set an error on the dataset if one has not already been set. This error will be returned by a future call to Error +// or as part of ToSQL. This can be used by end users to record errors while building up queries without having to +// track those separately. +func (dd *DeleteDataset) SetError(err error) *DeleteDataset { + if dd.err == nil { + dd.err = err + } + + return dd +} + // Generates a DELETE sql statement, if Prepared has been called with true then the parameters will not be interpolated. // See examples. // @@ -189,6 +207,9 @@ func (dd *DeleteDataset) Executor() exec.QueryExecutor { func (dd *DeleteDataset) deleteSQLBuilder() sb.SQLBuilder { buf := sb.NewSQLBuilder(dd.isPrepared) + if dd.err != nil { + return buf.SetError(dd.err) + } dd.dialect.ToDeleteSQL(buf, dd.clauses) return buf } diff --git a/delete_dataset_test.go b/delete_dataset_test.go index aa2e4ec3..e171effd 100644 --- a/delete_dataset_test.go +++ b/delete_dataset_test.go @@ -425,6 +425,51 @@ func (dds *deleteDatasetSuite) TestExecutor() { dds.Equal(`DELETE FROM "items" WHERE ("id" > ?)`, dsql) } +func (dds *deleteDatasetSuite) TestSetError() { + + err1 := errors.New("error #1") + err2 := errors.New("error #2") + err3 := errors.New("error #3") + + // Verify initial error set/get works properly + md := new(mocks.SQLDialect) + ds := Delete("test").SetDialect(md) + ds = ds.SetError(err1) + dds.Equal(err1, ds.Error()) + sql, args, err := ds.ToSQL() + dds.Empty(sql) + dds.Empty(args) + dds.Equal(err1, err) + + // Repeated SetError calls on Dataset should not overwrite the original error + ds = ds.SetError(err2) + dds.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + dds.Empty(sql) + dds.Empty(args) + dds.Equal(err1, err) + + // Builder functions should not lose the error + ds = ds.ClearLimit() + dds.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + dds.Empty(sql) + dds.Empty(args) + dds.Equal(err1, err) + + // Deeper errors inside SQL generation should still return original error + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToDeleteSQL", sqlB, c).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(err3) + }).Once() + + sql, args, err = ds.ToSQL() + dds.Empty(sql) + dds.Empty(args) + dds.Equal(err1, err) +} + func TestDeleteDataset(t *testing.T) { suite.Run(t, new(deleteDatasetSuite)) } diff --git a/docs/deleting.md b/docs/deleting.md index c342fd25..b8fba106 100644 --- a/docs/deleting.md +++ b/docs/deleting.md @@ -8,9 +8,10 @@ * [Order](#order) * [Limit](#limit) * [Returning](#returning) + * [SetError](#seterror) * [Executing](#exec) - - + + To create a [`DeleteDataset`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset) you can use **[`goqu.Delete`](https://godoc.org/github.com/doug-martin/goqu/#Delete)** @@ -214,6 +215,51 @@ Output: DELETE FROM "test" RETURNING "test".* ``` + +**[`SetError`](https://godoc.org/github.com/doug-martin/goqu/#DeleteDataset.SetError)** + +Sometimes while building up a query with goqu you will encounter situations where certain +preconditions are not met or some end-user contraint has been violated. While you could +track this error case separately, goqu provides a convenient built-in mechanism to set an +error on a dataset if one has not already been set to simplify query building. + +Set an Error on a dataset: + +```go +func GetDelete(name string, value string) *goqu.DeleteDataset { + + var ds = goqu.Delete("test") + + if len(name) == 0 { + return ds.SetError(fmt.Errorf("name is empty")) + } + + if len(value) == 0 { + return ds.SetError(fmt.Errorf("value is empty")) + } + + return ds.Where(goqu.C(name).Eq(value)) +} + +``` + +This error is returned on any subsequent call to `Error` or `ToSQL`: + +```go +var field, value string +ds = GetDelete(field, value) +fmt.Println(ds.Error()) + +sql, args, err = ds.ToSQL() +fmt.Println(err) +``` + +Output: +``` +name is empty +name is empty +``` + ## Executing Deletes To execute DELETES use [`Database.Delete`](https://godoc.org/github.com/doug-martin/goqu/#Database.Delete) to create your dataset @@ -265,4 +311,4 @@ Output: ``` Deleted users [ids:=[1 2 3]] -``` \ No newline at end of file +``` diff --git a/docs/inserting.md b/docs/inserting.md index a10b4854..985d8e1a 100644 --- a/docs/inserting.md +++ b/docs/inserting.md @@ -8,9 +8,10 @@ * [Insert Map](#insert-map) * [Insert From Query](#insert-from-query) * [Returning](#returning) + * [SetError](#seterror) * [Executing](#executing) - - + + To create a [`InsertDataset`](https://godoc.org/github.com/doug-martin/goqu/#InsertDataset) you can use **[`goqu.Insert`](https://godoc.org/github.com/doug-martin/goqu/#Insert)** @@ -109,7 +110,7 @@ insertSQL, args, _ := ds.ToSQL() fmt.Println(insertSQL, args) ``` -Output: +Output: ```sql INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') [] ``` @@ -373,6 +374,51 @@ Output: INSERT INTO "test" ("a", "b") VALUES ('a', 'b') RETURNING "test".* ``` + +**[`SetError`](https://godoc.org/github.com/doug-martin/goqu/#InsertDataset.SetError)** + +Sometimes while building up a query with goqu you will encounter situations where certain +preconditions are not met or some end-user contraint has been violated. While you could +track this error case separately, goqu provides a convenient built-in mechanism to set an +error on a dataset if one has not already been set to simplify query building. + +Set an Error on a dataset: + +```go +func GetInsert(name string, value string) *goqu.InsertDataset { + + var ds = goqu.Insert("test") + + if len(field) == 0 { + return ds.SetError(fmt.Errorf("name is empty")) + } + + if len(value) == 0 { + return ds.SetError(fmt.Errorf("value is empty")) + } + + return ds.Rows(goqu.Record{name: value}) +} + +``` + +This error is returned on any subsequent call to `Error` or `ToSQL`: + +```go +var field, value string +ds = GetInsert(field, value) +fmt.Println(ds.Error()) + +sql, args, err = ds.ToSQL() +fmt.Println(err) +``` + +Output: +``` +name is empty +name is empty +``` + ## Executing Inserts @@ -447,4 +493,4 @@ Output: ``` Inserted 1 user id:=5 -``` \ No newline at end of file +``` diff --git a/docs/selecting.md b/docs/selecting.md index 116104b5..0b7d9730 100644 --- a/docs/selecting.md +++ b/docs/selecting.md @@ -12,6 +12,7 @@ * [`GroupBy`](#group_by) * [`Having`](#having) * [`Window`](#window) + * [`SetError`](#seterror) * Executing Queries * [`ScanStructs`](#scan-structs) - Scans rows into a slice of structs * [`ScanStruct`](#scan-struct) - Scans a row into a slice a struct, returns false if a row wasnt found @@ -20,7 +21,7 @@ * [`Count`](#count) - Returns the count for the current query * [`Pluck`](#pluck) - Selects a single column and stores the results into a slice of primitive values - + To create a [`SelectDataset`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset) you can use **[`goqu.From`](https://godoc.org/github.com/doug-martin/goqu/#From) and [`goqu.Select`](https://godoc.org/github.com/doug-martin/goqu/#Select)** @@ -95,7 +96,7 @@ sql, _, _ := goqu.From("test").Select("a", "b", "c").ToSQL() fmt.Println(sql) ``` -Output: +Output: ```sql SELECT "a", "b", "c" FROM "test" ``` @@ -647,12 +648,53 @@ Output: SELECT ROW_NUMBER() OVER "w" FROM "test" WINDOW "w" AS (PARTITION BY "a" ORDER BY "b") ``` + +**[`SetError`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.SetError)** + +Sometimes while building up a query with goqu you will encounter situations where certain +preconditions are not met or some end-user contraint has been violated. While you could +track this error case separately, goqu provides a convenient built-in mechanism to set an +error on a dataset if one has not already been set to simplify query building. + +Set an Error on a dataset: + +```go +func GetSelect(name string) *goqu.SelectDataset { + + var ds = goqu.From("test") + + if len(name) == 0 { + return ds.SetError(fmt.Errorf("name is empty")) + } + + return ds.Select(name) +} + +``` + +This error is returned on any subsequent call to `Error` or `ToSQL`: + +```go +var name string = "" +ds = GetSelect(name) +fmt.Println(ds.Error()) + +sql, args, err = ds.ToSQL() +fmt.Println(err) +``` + +Output: +``` +name is empty +name is empty +``` + ## Executing Queries To execute your query use [`goqu.Database#From`](https://godoc.org/github.com/doug-martin/goqu/#Database.From) to create your dataset -**[`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanStructs)** +**[`ScanStructs`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanStructs)** Scans rows into a slice of structs @@ -742,7 +784,7 @@ fmt.Printf("\n%+v", ids) [`ScanVal`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.ScanVal) -Scans a row of 1 column into a primitive value, returns false if a row wasnt found. +Scans a row of 1 column into a primitive value, returns false if a row wasnt found. **Note** when using the dataset a `LIMIT` of 1 is automatically applied. ```go @@ -774,7 +816,7 @@ fmt.Printf("\nCount:= %d", count) ``` -**[`Pluck`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.Pluck)** +**[`Pluck`](http://godoc.org/github.com/doug-martin/goqu#SelectDataset.Pluck)** Selects a single column and stores the results into a slice of primitive values diff --git a/docs/updating.md b/docs/updating.md index 398d5287..a1246702 100644 --- a/docs/updating.md +++ b/docs/updating.md @@ -10,9 +10,10 @@ * [Order](#order) * [Limit](#limit) * [Returning](#returning) + * [SetError](#seterror) * [Executing](#executing) - - + + To create a [`UpdateDataset`](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset) you can use **[`goqu.Update`](https://godoc.org/github.com/doug-martin/goqu/#Update)** @@ -268,7 +269,7 @@ UPDATE "items" SET "address"='111 Test Addr',"name"='Test' [] **NOTE** The `sqlite3` adapter does not support a multi table syntax. -`Postgres` Example +`Postgres` Example ```go dialect := goqu.Dialect("postgres") @@ -415,6 +416,51 @@ Output: UPDATE "test" SET "foo"='bar' RETURNING "test".* ``` + +**[`SetError`](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.SetError)** + +Sometimes while building up a query with goqu you will encounter situations where certain +preconditions are not met or some end-user contraint has been violated. While you could +track this error case separately, goqu provides a convenient built-in mechanism to set an +error on a dataset if one has not already been set to simplify query building. + +Set an Error on a dataset: + +```go +func GetUpdate(name string, value string) *goqu.UpdateDataset { + + var ds = goqu.Update("test") + + if len(name) == 0 { + return ds.SetError(fmt.Errorf("name is empty")) + } + + if len(value) == 0 { + return ds.SetError(fmt.Errorf("value is empty")) + } + + return ds.Set(goqu.Record{name: value}) +} + +``` + +This error is returned on any subsequent call to `Error` or `ToSQL`: + +```go +var field, value string +ds = GetUpdate(field, value) +fmt.Println(ds.Error()) + +sql, args, err = ds.ToSQL() +fmt.Println(err) +``` + +Output: +``` +name is empty +name is empty +``` + ## Executing Updates @@ -468,4 +514,4 @@ if err := update.ScanVals(&ids); err != nil { Output: ``` Updated users with ids [1 2 3] -``` \ No newline at end of file +``` diff --git a/insert_dataset.go b/insert_dataset.go index 1147cb7d..637939be 100644 --- a/insert_dataset.go +++ b/insert_dataset.go @@ -12,6 +12,7 @@ type InsertDataset struct { clauses exp.InsertClauses isPrepared bool queryFactory exec.QueryFactory + err error } var errUnsupportedIntoType = errors.New("unsupported table type, a string or identifier expression is required") @@ -84,6 +85,7 @@ func (id *InsertDataset) copy(clauses exp.InsertClauses) *InsertDataset { clauses: clauses, isPrepared: id.isPrepared, queryFactory: id.queryFactory, + err: id.err, } } @@ -179,6 +181,22 @@ func (id *InsertDataset) ClearOnConflict() *InsertDataset { return id.OnConflict(nil) } +// Get any error that has been set or nil if no error has been set. +func (id *InsertDataset) Error() error { + return id.err +} + +// Set an error on the dataset if one has not already been set. This error will be returned by a future call to Error +// or as part of ToSQL. This can be used by end users to record errors while building up queries without having to +// track those separately. +func (id *InsertDataset) SetError(err error) *InsertDataset { + if id.err == nil { + id.err = err + } + + return id +} + // Generates the default INSERT statement. If Prepared has been called with true then the statement will not be // interpolated. See examples. When using structs you may specify a column to be skipped in the insert, (e.g. id) by // specifying a goqu tag with `skipinsert` @@ -209,6 +227,9 @@ func (id *InsertDataset) Executor() exec.QueryExecutor { func (id *InsertDataset) insertSQLBuilder() sb.SQLBuilder { buf := sb.NewSQLBuilder(id.isPrepared) + if id.err != nil { + return buf.SetError(id.err) + } id.dialect.ToInsertSQL(buf, id.clauses) return buf } diff --git a/insert_dataset_test.go b/insert_dataset_test.go index 7840863a..bb779ed0 100644 --- a/insert_dataset_test.go +++ b/insert_dataset_test.go @@ -438,6 +438,51 @@ func (ids *insertDatasetSuite) TestToSQL_ReturnedError() { md.AssertExpectations(ids.T()) } +func (ids *insertDatasetSuite) TestSetError() { + + err1 := errors.New("error #1") + err2 := errors.New("error #2") + err3 := errors.New("error #3") + + // Verify initial error set/get works properly + md := new(mocks.SQLDialect) + ds := Insert("test").SetDialect(md) + ds = ds.SetError(err1) + ids.Equal(err1, ds.Error()) + sql, args, err := ds.ToSQL() + ids.Empty(sql) + ids.Empty(args) + ids.Equal(err1, err) + + // Repeated SetError calls on Dataset should not overwrite the original error + ds = ds.SetError(err2) + ids.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + ids.Empty(sql) + ids.Empty(args) + ids.Equal(err1, err) + + // Builder functions should not lose the error + ds = ds.Cols("a", "b") + ids.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + ids.Empty(sql) + ids.Empty(args) + ids.Equal(err1, err) + + // Deeper errors inside SQL generation should still return original error + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToInsertSQL", sqlB, c).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(err3) + }).Once() + + sql, args, err = ds.ToSQL() + ids.Empty(sql) + ids.Empty(args) + ids.Equal(err1, err) +} + func TestInsertDataset(t *testing.T) { suite.Run(t, new(insertDatasetSuite)) } diff --git a/select_dataset.go b/select_dataset.go index 90167bc6..00f218e4 100644 --- a/select_dataset.go +++ b/select_dataset.go @@ -16,6 +16,7 @@ type SelectDataset struct { clauses exp.SelectClauses isPrepared bool queryFactory exec.QueryFactory + err error } var ( @@ -94,6 +95,7 @@ func (sd *SelectDataset) copy(clauses exp.SelectClauses) *SelectDataset { clauses: clauses, isPrepared: sd.isPrepared, queryFactory: sd.queryFactory, + err: sd.err, } } @@ -511,6 +513,22 @@ func (sd *SelectDataset) ClearWindow() *SelectDataset { return sd.copy(sd.clauses.ClearWindows()) } +// Get any error that has been set or nil if no error has been set. +func (sd *SelectDataset) Error() error { + return sd.err +} + +// Set an error on the dataset if one has not already been set. This error will be returned by a future call to Error +// or as part of ToSQL. This can be used by end users to record errors while building up queries without having to +// track those separately. +func (sd *SelectDataset) SetError(err error) *SelectDataset { + if sd.err == nil { + sd.err = err + } + + return sd +} + // Generates a SELECT sql statement, if Prepared has been called with true then the parameters will not be interpolated. // See examples. // @@ -658,6 +676,9 @@ func (sd *SelectDataset) PluckContext(ctx context.Context, i interface{}, col st func (sd *SelectDataset) selectSQLBuilder() sb.SQLBuilder { buf := sb.NewSQLBuilder(sd.isPrepared) + if sd.err != nil { + return buf.SetError(sd.err) + } sd.dialect.ToSelectSQL(buf, sd.GetClauses()) return buf } diff --git a/select_dataset_test.go b/select_dataset_test.go index a33cc414..1518c4fc 100644 --- a/select_dataset_test.go +++ b/select_dataset_test.go @@ -1465,6 +1465,51 @@ func (sds *selectDatasetSuite) TestPluck_WithPreparedStatement() { sds.Equal([]string{"Bob", "Sally", "Billy"}, names) } +func (sds *selectDatasetSuite) TestSetError() { + + err1 := errors.New("error #1") + err2 := errors.New("error #2") + err3 := errors.New("error #3") + + // Verify initial error set/get works properly + md := new(mocks.SQLDialect) + ds := From("test").SetDialect(md) + ds = ds.SetError(err1) + sds.Equal(err1, ds.Error()) + sql, args, err := ds.ToSQL() + sds.Empty(sql) + sds.Empty(args) + sds.Equal(err1, err) + + // Repeated SetError calls on Dataset should not overwrite the original error + ds = ds.SetError(err2) + sds.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + sds.Empty(sql) + sds.Empty(args) + sds.Equal(err1, err) + + // Builder functions should not lose the error + ds = ds.ClearWindow() + sds.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + sds.Empty(sql) + sds.Empty(args) + sds.Equal(err1, err) + + // Deeper errors inside SQL generation should still return original error + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToInsertSQL", sqlB, c).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(err3) + }).Once() + + sql, args, err = ds.ToSQL() + sds.Empty(sql) + sds.Empty(args) + sds.Equal(err1, err) +} + func TestSelectDataset(t *testing.T) { suite.Run(t, new(selectDatasetSuite)) } diff --git a/truncate_dataset.go b/truncate_dataset.go index ba63403c..a7985ca9 100644 --- a/truncate_dataset.go +++ b/truncate_dataset.go @@ -11,6 +11,7 @@ type TruncateDataset struct { clauses exp.TruncateClauses isPrepared bool queryFactory exec.QueryFactory + err error } // used internally by database to create a database with a specific adapter @@ -79,6 +80,7 @@ func (td *TruncateDataset) copy(clauses exp.TruncateClauses) *TruncateDataset { clauses: clauses, isPrepared: td.isPrepared, queryFactory: td.queryFactory, + err: td.err, } } @@ -126,6 +128,22 @@ func (td *TruncateDataset) Identity(identity string) *TruncateDataset { return td.copy(td.clauses.SetOptions(opts)) } +// Get any error that has been set or nil if no error has been set. +func (td *TruncateDataset) Error() error { + return td.err +} + +// Set an error on the dataset if one has not already been set. This error will be returned by a future call to Error +// or as part of ToSQL. This can be used by end users to record errors while building up queries without having to +// track those separately. +func (td *TruncateDataset) SetError(err error) *TruncateDataset { + if td.err == nil { + td.err = err + } + + return td +} + // Generates a TRUNCATE sql statement, if Prepared has been called with true then the parameters will not be interpolated. // See examples. // @@ -143,6 +161,9 @@ func (td *TruncateDataset) Executor() exec.QueryExecutor { func (td *TruncateDataset) truncateSQLBuilder() sb.SQLBuilder { buf := sb.NewSQLBuilder(td.isPrepared) + if td.err != nil { + return buf.SetError(td.err) + } td.dialect.ToTruncateSQL(buf, td.clauses) return buf } diff --git a/truncate_dataset_test.go b/truncate_dataset_test.go index 065d5986..708f763a 100644 --- a/truncate_dataset_test.go +++ b/truncate_dataset_test.go @@ -277,6 +277,51 @@ func (tds *truncateDatasetSuite) TestExecutor() { tds.Equal(`TRUNCATE "table1", "table2"`, tsql) } +func (tds *truncateDatasetSuite) TestSetError() { + + err1 := errors.New("error #1") + err2 := errors.New("error #2") + err3 := errors.New("error #3") + + // Verify initial error set/get works properly + md := new(mocks.SQLDialect) + ds := Truncate("test").SetDialect(md) + ds = ds.SetError(err1) + tds.Equal(err1, ds.Error()) + sql, args, err := ds.ToSQL() + tds.Empty(sql) + tds.Empty(args) + tds.Equal(err1, err) + + // Repeated SetError calls on Dataset should not overwrite the original error + ds = ds.SetError(err2) + tds.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + tds.Empty(sql) + tds.Empty(args) + tds.Equal(err1, err) + + // Builder functions should not lose the error + ds = ds.Cascade() + tds.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + tds.Empty(sql) + tds.Empty(args) + tds.Equal(err1, err) + + // Deeper errors inside SQL generation should still return original error + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToTruncateSQL", sqlB, c).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(err3) + }).Once() + + sql, args, err = ds.ToSQL() + tds.Empty(sql) + tds.Empty(args) + tds.Equal(err1, err) +} + func TestTruncateDataset(t *testing.T) { suite.Run(t, new(truncateDatasetSuite)) } diff --git a/update_dataset.go b/update_dataset.go index b3c137b2..0cd714ac 100644 --- a/update_dataset.go +++ b/update_dataset.go @@ -12,6 +12,7 @@ type UpdateDataset struct { clauses exp.UpdateClauses isPrepared bool queryFactory exec.QueryFactory + err error } var errUnsupportedUpdateTableType = errors.New("unsupported table type, a string or identifier expression is required") @@ -82,6 +83,7 @@ func (ud *UpdateDataset) copy(clauses exp.UpdateClauses) *UpdateDataset { clauses: clauses, isPrepared: ud.isPrepared, queryFactory: ud.queryFactory, + err: ud.err, } } @@ -184,6 +186,22 @@ func (ud *UpdateDataset) Returning(returning ...interface{}) *UpdateDataset { return ud.copy(ud.clauses.SetReturning(exp.NewColumnListExpression(returning...))) } +// Get any error that has been set or nil if no error has been set. +func (ud *UpdateDataset) Error() error { + return ud.err +} + +// Set an error on the dataset if one has not already been set. This error will be returned by a future call to Error +// or as part of ToSQL. This can be used by end users to record errors while building up queries without having to +// track those separately. +func (ud *UpdateDataset) SetError(err error) *UpdateDataset { + if ud.err == nil { + ud.err = err + } + + return ud +} + // Generates an UPDATE sql statement, if Prepared has been called with true then the parameters will not be interpolated. // See examples. // @@ -201,6 +219,9 @@ func (ud *UpdateDataset) Executor() exec.QueryExecutor { func (ud *UpdateDataset) updateSQLBuilder() sb.SQLBuilder { buf := sb.NewSQLBuilder(ud.isPrepared) + if ud.err != nil { + return buf.SetError(ud.err) + } ud.dialect.ToUpdateSQL(buf, ud.clauses) return buf } diff --git a/update_dataset_test.go b/update_dataset_test.go index f779fa10..61893314 100644 --- a/update_dataset_test.go +++ b/update_dataset_test.go @@ -436,6 +436,51 @@ func (uds *updateDatasetSuite) TestExecutor() { uds.Equal(`UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL)`, updateSQL) } +func (uds *updateDatasetSuite) TestSetError() { + + err1 := errors.New("error #1") + err2 := errors.New("error #2") + err3 := errors.New("error #3") + + // Verify initial error set/get works properly + md := new(mocks.SQLDialect) + ds := Update("test").SetDialect(md) + ds = ds.SetError(err1) + uds.Equal(err1, ds.Error()) + sql, args, err := ds.ToSQL() + uds.Empty(sql) + uds.Empty(args) + uds.Equal(err1, err) + + // Repeated SetError calls on Dataset should not overwrite the original error + ds = ds.SetError(err2) + uds.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + uds.Empty(sql) + uds.Empty(args) + uds.Equal(err1, err) + + // Builder functions should not lose the error + ds = ds.ClearLimit() + uds.Equal(err1, ds.Error()) + sql, args, err = ds.ToSQL() + uds.Empty(sql) + uds.Empty(args) + uds.Equal(err1, err) + + // Deeper errors inside SQL generation should still return original error + c := ds.GetClauses() + sqlB := sb.NewSQLBuilder(false) + md.On("ToUpdateSQL", sqlB, c).Run(func(args mock.Arguments) { + args.Get(0).(sb.SQLBuilder).SetError(err3) + }).Once() + + sql, args, err = ds.ToSQL() + uds.Empty(sql) + uds.Empty(args) + uds.Equal(err1, err) +} + func TestUpdateDataset(t *testing.T) { suite.Run(t, new(updateDatasetSuite)) }