Skip to content

Commit

Permalink
feat: add SetDefaultPrepared which controls query value interpolation
Browse files Browse the repository at this point in the history
This switches the datasets' internal isPrepared bools into a custom type
that resolves back into a bool when passed into the SQL builder. This
could have also been a *bool but I wanted to avoid nil checks and
potential sources of confusion if e.g. a child dataset happened to
dereference and mutate the pointer's value.
  • Loading branch information
Deiz committed Jul 30, 2021
1 parent 6c631e9 commit c689876
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 24 deletions.
10 changes: 5 additions & 5 deletions delete_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var ErrBadFromArgument = errors.New("unsupported DeleteDataset#From argument, a
type DeleteDataset struct {
dialect SQLDialect
clauses exp.DeleteClauses
isPrepared bool
isPrepared prepared
queryFactory exec.QueryFactory
err error
}
Expand All @@ -23,7 +23,7 @@ func newDeleteDataset(d string, queryFactory exec.QueryFactory) *DeleteDataset {
clauses: exp.NewDeleteClauses(),
dialect: GetDialect(d),
queryFactory: queryFactory,
isPrepared: false,
isPrepared: preparedNoPreference,
err: nil,
}
}
Expand All @@ -46,13 +46,13 @@ func (dd *DeleteDataset) Clone() exp.Expression {
// prepared: If true the dataset WILL NOT interpolate the parameters.
func (dd *DeleteDataset) Prepared(prepared bool) *DeleteDataset {
ret := dd.copy(dd.clauses)
ret.isPrepared = prepared
ret.isPrepared = preparedFromBool(prepared)
return ret
}

// Returns true if Prepared(true) has been called on this dataset
func (dd *DeleteDataset) IsPrepared() bool {
return dd.isPrepared
return dd.isPrepared.Bool()
}

// Sets the adapter used to serialize values and create the SQL statement
Expand Down Expand Up @@ -235,7 +235,7 @@ func (dd *DeleteDataset) Executor() exec.QueryExecutor {
}

func (dd *DeleteDataset) deleteSQLBuilder() sb.SQLBuilder {
buf := sb.NewSQLBuilder(dd.isPrepared)
buf := sb.NewSQLBuilder(dd.isPrepared.Bool())
if dd.err != nil {
return buf.SetError(dd.err)
}
Expand Down
15 changes: 15 additions & 0 deletions delete_dataset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ func (dds *deleteDatasetSuite) TestPrepared() {
dds.False(ds.IsPrepared())
// should apply the prepared to any datasets created from the root
dds.True(preparedDs.Where(goqu.Ex{"a": 1}).IsPrepared())

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

// should be prepared by default
ds = goqu.Delete("test")
dds.True(ds.IsPrepared())
}

func (dds *deleteDatasetSuite) TestGetClauses() {
Expand Down Expand Up @@ -445,6 +452,14 @@ func (dds *deleteDatasetSuite) TestExecutor() {
dds.NoError(err)
dds.Equal([]interface{}{int64(10)}, args)
dds.Equal(`DELETE FROM "items" WHERE ("id" > ?)`, dsql)

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

dsql, args, err = ds.Executor().ToSQL()
dds.NoError(err)
dds.Equal([]interface{}{int64(10)}, args)
dds.Equal(`DELETE FROM "items" WHERE ("id" > ?)`, dsql)
}

func (dds *deleteDatasetSuite) TestSetError() {
Expand Down
8 changes: 4 additions & 4 deletions insert_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
type InsertDataset struct {
dialect SQLDialect
clauses exp.InsertClauses
isPrepared bool
isPrepared prepared
queryFactory exec.QueryFactory
err error
}
Expand All @@ -39,12 +39,12 @@ func Insert(table interface{}) *InsertDataset {
// prepared: If true the dataset WILL NOT interpolate the parameters.
func (id *InsertDataset) Prepared(prepared bool) *InsertDataset {
ret := id.copy(id.clauses)
ret.isPrepared = prepared
ret.isPrepared = preparedFromBool(prepared)
return ret
}

func (id *InsertDataset) IsPrepared() bool {
return id.isPrepared
return id.isPrepared.Bool()
}

// Sets the adapter used to serialize values and create the SQL statement
Expand Down Expand Up @@ -257,7 +257,7 @@ func (id *InsertDataset) Executor() exec.QueryExecutor {
}

func (id *InsertDataset) insertSQLBuilder() sb.SQLBuilder {
buf := sb.NewSQLBuilder(id.isPrepared)
buf := sb.NewSQLBuilder(id.isPrepared.Bool())
if id.err != nil {
return buf.SetError(id.err)
}
Expand Down
15 changes: 15 additions & 0 deletions insert_dataset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ func (ids *insertDatasetSuite) TestPrepared() {
ids.False(ds.IsPrepared())
// should apply the prepared to any datasets created from the root
ids.True(preparedDs.Returning(goqu.C("col")).IsPrepared())

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

// should be prepared by default
ds = goqu.Insert("test")
ids.True(ds.IsPrepared())
}

func (ids *insertDatasetSuite) TestGetClauses() {
Expand Down Expand Up @@ -440,6 +447,14 @@ func (ids *insertDatasetSuite) TestExecutor() {
ids.NoError(err)
ids.Equal([]interface{}{"111 Test Addr", "Test1"}, args)
ids.Equal(`INSERT INTO "items" ("address", "name") VALUES (?, ?)`, isql)

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

isql, args, err = ds.Executor().ToSQL()
ids.NoError(err)
ids.Equal([]interface{}{"111 Test Addr", "Test1"}, args)
ids.Equal(`INSERT INTO "items" ("address", "name") VALUES (?, ?)`, isql)
}

func (ids *insertDatasetSuite) TestInsertStruct() {
Expand Down
48 changes: 48 additions & 0 deletions prepared.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package goqu

var (
// defaultPrepared is controlled by SetDefaultPrepared
defaultPrepared bool
)

type prepared int

const (
// zero value that defers to defaultPrepared
preparedNoPreference prepared = iota

// explicitly enabled via Prepared(true) on a dataset
preparedEnabled

// explicitly disabled via Prepared(false) on a dataset
preparedDisabled
)

// Bool converts the ternary prepared state into a boolean. If the prepared
// state is preparedNoPreference, the value depends on the last value that
// SetDefaultPrepared was called with which is false by default.
func (p prepared) Bool() bool {
if p == preparedNoPreference {
return defaultPrepared
} else if p == preparedEnabled {
return true
}

return false
}

// preparedFromBool converts a bool from e.g. Prepared(true) into a prepared
// const.
func preparedFromBool(prepared bool) prepared {
if prepared {
return preparedEnabled
}

return preparedDisabled
}

// SetDefaultPrepared controls the default Prepared state of all datasets. If
// set to true, any new dataset will use prepared queries by default.
func SetDefaultPrepared(prepared bool) {
defaultPrepared = prepared
}
14 changes: 7 additions & 7 deletions select_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
type SelectDataset struct {
dialect SQLDialect
clauses exp.SelectClauses
isPrepared bool
isPrepared prepared
queryFactory exec.QueryFactory
err error
}
Expand Down Expand Up @@ -52,12 +52,12 @@ func (sd *SelectDataset) WithDialect(dl string) *SelectDataset {
// prepared: If true the dataset WILL NOT interpolate the parameters.
func (sd *SelectDataset) Prepared(prepared bool) *SelectDataset {
ret := sd.copy(sd.clauses)
ret.isPrepared = prepared
ret.isPrepared = preparedFromBool(prepared)
return ret
}

func (sd *SelectDataset) IsPrepared() bool {
return sd.isPrepared
return sd.isPrepared.Bool()
}

// Returns the current adapter on the dataset
Expand Down Expand Up @@ -101,7 +101,7 @@ func (sd *SelectDataset) copy(clauses exp.SelectClauses) *SelectDataset {
// `ORDER , and `LIMIT`
func (sd *SelectDataset) Update() *UpdateDataset {
u := newUpdateDataset(sd.dialect.Dialect(), sd.queryFactory).
Prepared(sd.isPrepared)
Prepared(sd.isPrepared.Bool())
if sd.clauses.HasSources() {
u = u.Table(sd.GetClauses().From().Columns()[0])
}
Expand All @@ -128,7 +128,7 @@ func (sd *SelectDataset) Update() *UpdateDataset {
// insert.
func (sd *SelectDataset) Insert() *InsertDataset {
i := newInsertDataset(sd.dialect.Dialect(), sd.queryFactory).
Prepared(sd.isPrepared)
Prepared(sd.isPrepared.Bool())
if sd.clauses.HasSources() {
i = i.Into(sd.GetClauses().From().Columns()[0])
}
Expand All @@ -144,7 +144,7 @@ func (sd *SelectDataset) Insert() *InsertDataset {
// `ORDER , and `LIMIT`
func (sd *SelectDataset) Delete() *DeleteDataset {
d := newDeleteDataset(sd.dialect.Dialect(), sd.queryFactory).
Prepared(sd.isPrepared)
Prepared(sd.isPrepared.Bool())
if sd.clauses.HasSources() {
d = d.From(sd.clauses.From().Columns()[0])
}
Expand Down Expand Up @@ -686,7 +686,7 @@ func (sd *SelectDataset) PluckContext(ctx context.Context, i interface{}, col st
}

func (sd *SelectDataset) selectSQLBuilder() sb.SQLBuilder {
buf := sb.NewSQLBuilder(sd.isPrepared)
buf := sb.NewSQLBuilder(sd.isPrepared.Bool())
if sd.err != nil {
return buf.SetError(sd.err)
}
Expand Down
7 changes: 7 additions & 0 deletions select_dataset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ func (sds *selectDatasetSuite) TestPrepared() {
sds.False(ds.IsPrepared())
// should apply the prepared to any datasets created from the root
sds.True(preparedDs.Where(goqu.Ex{"a": 1}).IsPrepared())

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

// should be prepared by default
ds = goqu.From("test")
sds.True(ds.IsPrepared())
}

func (sds *selectDatasetSuite) TestGetClauses() {
Expand Down
8 changes: 4 additions & 4 deletions truncate_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
type TruncateDataset struct {
dialect SQLDialect
clauses exp.TruncateClauses
isPrepared bool
isPrepared prepared
queryFactory exec.QueryFactory
err error
}
Expand Down Expand Up @@ -39,12 +39,12 @@ func (td *TruncateDataset) WithDialect(dl string) *TruncateDataset {
// prepared: If true the dataset WILL NOT interpolate the parameters.
func (td *TruncateDataset) Prepared(prepared bool) *TruncateDataset {
ret := td.copy(td.clauses)
ret.isPrepared = prepared
ret.isPrepared = preparedFromBool(prepared)
return ret
}

func (td *TruncateDataset) IsPrepared() bool {
return td.isPrepared
return td.isPrepared.Bool()
}

// Returns the current adapter on the dataset
Expand Down Expand Up @@ -160,7 +160,7 @@ func (td *TruncateDataset) Executor() exec.QueryExecutor {
}

func (td *TruncateDataset) truncateSQLBuilder() sb.SQLBuilder {
buf := sb.NewSQLBuilder(td.isPrepared)
buf := sb.NewSQLBuilder(td.isPrepared.Bool())
if td.err != nil {
return buf.SetError(td.err)
}
Expand Down
15 changes: 15 additions & 0 deletions truncate_dataset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ func (tds *truncateDatasetSuite) TestPrepared() {
tds.False(ds.IsPrepared())
// should apply the prepared to any datasets created from the root
tds.True(preparedDs.Restrict().IsPrepared())

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

// should be prepared by default
ds = goqu.Truncate("test")
tds.True(ds.IsPrepared())
}

func (tds *truncateDatasetSuite) TestGetClauses() {
Expand Down Expand Up @@ -274,6 +281,14 @@ func (tds *truncateDatasetSuite) TestExecutor() {
tds.NoError(err)
tds.Empty(args)
tds.Equal(`TRUNCATE "table1", "table2"`, tsql)

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

tsql, args, err = ds.Executor().ToSQL()
tds.NoError(err)
tds.Empty(args)
tds.Equal(`TRUNCATE "table1", "table2"`, tsql)
}

func (tds *truncateDatasetSuite) TestSetError() {
Expand Down
8 changes: 4 additions & 4 deletions update_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
type UpdateDataset struct {
dialect SQLDialect
clauses exp.UpdateClauses
isPrepared bool
isPrepared prepared
queryFactory exec.QueryFactory
err error
}
Expand All @@ -35,12 +35,12 @@ func Update(table interface{}) *UpdateDataset {
// prepared: If true the dataset WILL NOT interpolate the parameters.
func (ud *UpdateDataset) Prepared(prepared bool) *UpdateDataset {
ret := ud.copy(ud.clauses)
ret.isPrepared = prepared
ret.isPrepared = preparedFromBool(prepared)
return ret
}

func (ud *UpdateDataset) IsPrepared() bool {
return ud.isPrepared
return ud.isPrepared.Bool()
}

// Sets the adapter used to serialize values and create the SQL statement
Expand Down Expand Up @@ -236,7 +236,7 @@ func (ud *UpdateDataset) Executor() exec.QueryExecutor {
}

func (ud *UpdateDataset) updateSQLBuilder() sb.SQLBuilder {
buf := sb.NewSQLBuilder(ud.isPrepared)
buf := sb.NewSQLBuilder(ud.isPrepared.Bool())
if ud.err != nil {
return buf.SetError(ud.err)
}
Expand Down
15 changes: 15 additions & 0 deletions update_dataset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ func (uds *updateDatasetSuite) TestPrepared() {
uds.False(ds.IsPrepared())
// should apply the prepared to any datasets created from the root
uds.True(preparedDs.Where(goqu.Ex{"a": 1}).IsPrepared())

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

// should be prepared by default
ds = goqu.Update("test")
uds.True(ds.IsPrepared())
}

func (uds *updateDatasetSuite) TestGetClauses() {
Expand Down Expand Up @@ -449,6 +456,14 @@ func (uds *updateDatasetSuite) TestExecutor() {
uds.NoError(err)
uds.Equal([]interface{}{"111 Test Addr", "Test1"}, args)
uds.Equal(`UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL)`, updateSQL)

defer goqu.SetDefaultPrepared(false)
goqu.SetDefaultPrepared(true)

updateSQL, args, err = ds.Executor().ToSQL()
uds.NoError(err)
uds.Equal([]interface{}{"111 Test Addr", "Test1"}, args)
uds.Equal(`UPDATE "items" SET "address"=?,"name"=? WHERE ("name" IS NULL)`, updateSQL)
}

func (uds *updateDatasetSuite) TestSetError() {
Expand Down

0 comments on commit c689876

Please sign in to comment.