From 3aa0c348b324aaa3e8b52ae92e213cdc50511ba9 Mon Sep 17 00:00:00 2001 From: Evan Phoenix Date: Thu, 6 Mar 2014 15:47:50 -0800 Subject: [PATCH 01/14] Close the prepared stmt if we don't use the LRU --- query.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/query.go b/query.go index 3ec7952..5e2ae8e 100644 --- a/query.go +++ b/query.go @@ -75,6 +75,8 @@ func (q *jetQuery) Rows(v interface{}) (err error) { } if useLru { q.db.lru.put(query, stmt) + } else { + defer stmt.Close() } } From 986829b51e5d65225274794ac940830e11941f85 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Mon, 9 Mar 2015 13:17:28 -0400 Subject: [PATCH 02/14] expose sql.Tx.Exec on jet.Tx --- tx.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tx.go b/tx.go index 7124808..ac00ff1 100644 --- a/tx.go +++ b/tx.go @@ -19,6 +19,11 @@ func (tx *Tx) Query(query string, args ...interface{}) Runnable { return q } +// Exec calls Exec on the underlying sql.Tx. +func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) { + return tx.tx.Exec(query, args...) +} + // Commit commits the transaction func (tx *Tx) Commit() error { if tx.db.LogFunc != nil { From c3a9966b06f4d9563b0ff25045c5b080cf6b6906 Mon Sep 17 00:00:00 2001 From: Martin Angers Date: Mon, 9 Mar 2015 13:35:07 -0400 Subject: [PATCH 03/14] add nil check --- tx.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tx.go b/tx.go index ac00ff1..aa5f4f4 100644 --- a/tx.go +++ b/tx.go @@ -2,6 +2,7 @@ package jet import ( "database/sql" + "errors" ) // Tx represents a transaction instance. @@ -21,6 +22,9 @@ func (tx *Tx) Query(query string, args ...interface{}) Runnable { // Exec calls Exec on the underlying sql.Tx. func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) { + if tx == nil || tx.tx == nil { + return nil, errors.New("jet: Exec called on nil transaction") + } return tx.tx.Exec(query, args...) } From dbc95b5c283dc2cbb36d0de8a99f889b222f4ef0 Mon Sep 17 00:00:00 2001 From: Melvin Date: Thu, 15 Dec 2016 16:59:03 -0800 Subject: [PATCH 04/14] Handle some special cases --- mapper.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mapper.go b/mapper.go index 96f2ec5..e1c464b 100644 --- a/mapper.go +++ b/mapper.go @@ -82,6 +82,16 @@ func (m *mapper) unpackStruct(keys []string, values []interface{}, out reflect.V convKey = m.conv.ColumnToFieldName(k) } field := out.FieldByName(convKey) + + // If the field is not found it can mean that we don't want it or that + // we have special case like UserID, UUID, userUUID + // So fix the name and try again + if !field.IsValid() { + convKey = strings.Replace(convKey, "Uuid", "UUID", -1) + convKey = strings.Replace(convKey, "Id", "ID", -1) + field = out.FieldByName(convKey) + } + if field.IsValid() { m.unpackValue(nil, values[i:i+1], field) } From be6033dcee1dd57ed97b84d662895d904b84f6fb Mon Sep 17 00:00:00 2001 From: Melvin Date: Mon, 16 Jan 2017 12:10:49 -0800 Subject: [PATCH 05/14] Handle IP/Ip --- mapper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mapper.go b/mapper.go index e1c464b..4d07ea8 100644 --- a/mapper.go +++ b/mapper.go @@ -89,6 +89,7 @@ func (m *mapper) unpackStruct(keys []string, values []interface{}, out reflect.V if !field.IsValid() { convKey = strings.Replace(convKey, "Uuid", "UUID", -1) convKey = strings.Replace(convKey, "Id", "ID", -1) + convKey = strings.Replace(convKey, "Ip", "IP", -1) field = out.FieldByName(convKey) } From 99d2163b02cfe6e80efedc3d4fd1aa0f5dccbcdf Mon Sep 17 00:00:00 2001 From: Melvin Date: Mon, 6 Feb 2017 12:04:16 -0800 Subject: [PATCH 06/14] Handle URL/Url --- mapper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mapper.go b/mapper.go index 4d07ea8..7ea98cc 100644 --- a/mapper.go +++ b/mapper.go @@ -90,6 +90,7 @@ func (m *mapper) unpackStruct(keys []string, values []interface{}, out reflect.V convKey = strings.Replace(convKey, "Uuid", "UUID", -1) convKey = strings.Replace(convKey, "Id", "ID", -1) convKey = strings.Replace(convKey, "Ip", "IP", -1) + convKey = strings.Replace(convKey, "Url", "URL", -1) field = out.FieldByName(convKey) } From 1cb59310e57976055d6c5e82735a730707373457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hock=20Isaza?= Date: Thu, 24 Jan 2019 17:04:51 -0500 Subject: [PATCH 07/14] Add `OpenFunc()` function This commit adds the `OpenFunc()` function that allows us to handle how the DB connection will be opened. You can pass any function that has the same signature as `sql.Open()` and it'll use it to start the connection. The idea is to open the connections with DataDog [SQL wrapper][1] to start collecting APM metrics for our queries. [1]: https://godoc.org/gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql --- db.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/db.go b/db.go index fca8ff4..7427880 100644 --- a/db.go +++ b/db.go @@ -25,7 +25,12 @@ type Db struct { // Open opens a new database connection. func Open(driverName, dataSourceName string) (*Db, error) { - db, err := sql.Open(driverName, dataSourceName) + return OpenFunc(driverName, dataSourceName, sql.Open) +} + +// OpenFunc opens a new database connection by using the passed `fn`. +func OpenFunc(driverName, dataSourceName string, fn func(string, string) (*sql.DB, error)) (*Db, error) { + db, err := fn(driverName, dataSourceName) if err != nil { return nil, err } From 014d65c8ed681cee6e6c42689983e8de7cc81d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hock=20Isaza?= Date: Wed, 30 Jan 2019 08:07:29 -0500 Subject: [PATCH 08/14] Add `QueryContext` methods for Jet objects This commit adds `db/trx.QueryContext()` methods that are similar to `Query()` but accept a context (same as the STD `sql.QueryContext`). This will allow us to send tracing information to queries that will be tied to a given span. * https://golang.org/pkg/database/sql/#DB.QueryContext --- db.go | 8 +++++++- query.go | 13 ++++++++++--- tx.go | 8 +++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/db.go b/db.go index 7427880..b8287ad 100644 --- a/db.go +++ b/db.go @@ -1,6 +1,7 @@ package jet import ( + "context" "database/sql" ) @@ -69,5 +70,10 @@ func (db *Db) Begin() (*Tx, error) { // Query creates a prepared query that can be run with Rows or Run. func (db *Db) Query(query string, args ...interface{}) Runnable { - return newQuery(db, db, query, args...) + return db.QueryContext(context.Background(), query, args...) +} + +// QueryContext creates a prepared query that can be run with Rows or Run. +func (db *Db) QueryContext(ctx context.Context, query string, args ...interface{}) Runnable { + return newQuery(ctx, db, db, query, args...) } diff --git a/query.go b/query.go index 5e2ae8e..905ee60 100644 --- a/query.go +++ b/query.go @@ -1,6 +1,7 @@ package jet import ( + "context" "database/sql" "sync" ) @@ -12,16 +13,18 @@ type jetQuery struct { id string query string args []interface{} + ctx context.Context } // newQuery initiates a new query for the provided query object (either *sql.Tx or *sql.DB) -func newQuery(qo queryObject, db *Db, query string, args ...interface{}) *jetQuery { +func newQuery(ctx context.Context, qo queryObject, db *Db, query string, args ...interface{}) *jetQuery { return &jetQuery{ qo: qo, db: db, id: newQueryId(), query: query, args: args, + ctx: ctx, } } @@ -33,6 +36,10 @@ func (q *jetQuery) Rows(v interface{}) (err error) { q.m.Lock() defer q.m.Unlock() + if q.ctx == nil { + q.ctx = context.Background() + } + // disable lru in transactions useLru := true switch q.qo.(type) { @@ -82,12 +89,12 @@ func (q *jetQuery) Rows(v interface{}) (err error) { // If no rows need to be unpacked use Exec if v == nil { - _, err := stmt.Exec(args...) + _, err := stmt.ExecContext(q.ctx, args...) return err } // run query - rows, err := stmt.Query(args...) + rows, err := stmt.QueryContext(q.ctx, args...) if err != nil { return err } diff --git a/tx.go b/tx.go index aa5f4f4..f47a908 100644 --- a/tx.go +++ b/tx.go @@ -1,6 +1,7 @@ package jet import ( + "context" "database/sql" "errors" ) @@ -15,7 +16,12 @@ type Tx struct { // Query creates a prepared query that can be run with Rows or Run. func (tx *Tx) Query(query string, args ...interface{}) Runnable { - q := newQuery(tx.tx, tx.db, query, args...) + return tx.QueryContext(context.Background(), query, args...) +} + +// QueryContext creates a prepared query that can be run with Rows or Run. +func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) Runnable { + q := newQuery(ctx, tx.tx, tx.db, query, args...) q.id = tx.qid return q } From 176580281cdc94c0702d45cf311a493c8d650448 Mon Sep 17 00:00:00 2001 From: John Bramlett Date: Wed, 18 Aug 2021 13:37:17 -0400 Subject: [PATCH 09/14] Tweaking the mapping of complex type to support setting to blank --- mapper.go | 9 +++ mapper_test.go | 147 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 154 insertions(+), 2 deletions(-) diff --git a/mapper.go b/mapper.go index 7ea98cc..e029c0c 100644 --- a/mapper.go +++ b/mapper.go @@ -23,6 +23,15 @@ func (m *mapper) unpack(keys []string, values []interface{}, out interface{}) er func (m *mapper) unpackValue(keys []string, values []interface{}, out reflect.Value) error { switch out.Interface().(type) { case ComplexValue: + if values[0] == nil || reflect.ValueOf(values[0]).IsZero(){ + if out.IsZero() { + return nil + } + if out.CanSet() { + out.Set(reflect.Zero(out.Type())) + return nil + } + } if out.IsNil() { out.Set(reflect.New(out.Type().Elem())) } diff --git a/mapper_test.go b/mapper_test.go index 8ea56ff..6b4a0fe 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -73,8 +73,13 @@ func (c *custom) Decode(v interface{}) error { } s, ok := v.(string) if ok { - c.a = string(s[0]) - c.b = string(s[1]) + if len(s) > 1 { + c.a = string(s[0]) + c.b = string(s[1]) + } else { + c.a = "" + c.b = "" + } } return nil } @@ -181,6 +186,144 @@ func TestUnpackStruct(t *testing.T) { } } +func TestUnpackStructExistingValueToEmpty(t *testing.T) { + keys := []string{"m"} + vals := []interface{}{ + "", + } + mppr := &mapper{ + conv: SnakeCaseConverter, + } + + var v struct { + M plainCustom + } + v.M = "abc" + + err := mppr.unpack(keys, vals, &v) + if err != nil { + t.Fatal(err) + } + if v.M != "" { + t.Fatal(v.M) + } +} + +func TestUnpackStructEmptyToEmpty(t *testing.T) { + keys := []string{"m"} + vals := []interface{}{ + "", + } + mppr := &mapper{ + conv: SnakeCaseConverter, + } + + var v struct { + M plainCustom + } + + err := mppr.unpack(keys, vals, &v) + if err != nil { + t.Fatal(err) + } + if v.M != "" { + t.Fatal(v.M) + } +} + +func TestUnpackStructExistingValueToNil(t *testing.T) { + keys := []string{"j"} + vals := []interface{}{ + nil, + } + mppr := &mapper{ + conv: SnakeCaseConverter, + } + + var v struct { + J *custom + } + v.J = &custom{a: "a", b: "b"} + + err := mppr.unpack(keys, vals, &v) + if err != nil { + t.Fatal(err) + } + if v.J != nil { + t.Fatal(v.J) + } +} + +func TestUnpackStructExistingValueNonPtrToEmpty(t *testing.T) { + keys := []string{"j"} + vals := []interface{}{ + "", + } + mppr := &mapper{ + conv: SnakeCaseConverter, + } + + var v struct { + J custom + } + v.J = custom{a: "a", b: "b"} + + err := mppr.unpack(keys, vals, &v) + if err != nil { + t.Fatal(err) + } + if v.J.a != "" || v.J.b != "" { + t.Fatal(v.J) + } +} + +func TestUnpackStructComplexExistingValueToEmpty(t *testing.T) { + keys := []string{"j"} + vals := []interface{}{ + "", + } + mppr := &mapper{ + conv: SnakeCaseConverter, + } + + var v struct { + J *custom + } + v.J = &custom{a: "a", b: "b"} + + err := mppr.unpack(keys, vals, &v) + if err != nil { + t.Fatal(err) + } + if v.J != nil { + t.Fatal(v.J) + } +} + + +func TestUnpackStructNilComplexToNil(t *testing.T) { + keys := []string{"j"} + vals := []interface{}{ + nil, + } + mppr := &mapper{ + conv: SnakeCaseConverter, + } + + var v struct { + J *custom + } + + err := mppr.unpack(keys, vals, &v) + if err != nil { + t.Fatal(err) + } + if v.J != nil { + t.Fatal(v.J) + } +} + + func TestUnpackMap(t *testing.T) { keys := []string{"ab_c", "c_d", "e"} vals := []interface{}{int64(9), "hello", "unsettable"} From 6ce4b7850080128bbc18b9725f27df14bd8ba0ca Mon Sep 17 00:00:00 2001 From: John Bramlett Date: Wed, 18 Aug 2021 16:38:27 -0400 Subject: [PATCH 10/14] Adding additional test and logic to account for how we do this in the db query --- mapper.go | 18 +++++++++++++++++- mapper_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/mapper.go b/mapper.go index e029c0c..cfd065d 100644 --- a/mapper.go +++ b/mapper.go @@ -20,10 +20,26 @@ func (m *mapper) unpack(keys []string, values []interface{}, out interface{}) er return m.unpackValue(keys, values, val) } +func isNil(val interface{}) bool { + if val == nil { + return true + } + if reflect.ValueOf(val).IsZero() { + return true + } + if reflect.ValueOf(val).Kind() == reflect.Ptr { + if reflect.ValueOf(val).Elem().Kind() == reflect.Struct || reflect.ValueOf(val).Elem().Kind() == reflect.Interface { + return reflect.ValueOf(val).Elem().IsNil() + } + } + + return false +} + func (m *mapper) unpackValue(keys []string, values []interface{}, out reflect.Value) error { switch out.Interface().(type) { case ComplexValue: - if values[0] == nil || reflect.ValueOf(values[0]).IsZero(){ + if isNil(values[0]) { if out.IsZero() { return nil } diff --git a/mapper_test.go b/mapper_test.go index 6b4a0fe..a3c0949 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -301,6 +301,32 @@ func TestUnpackStructComplexExistingValueToEmpty(t *testing.T) { } +func TestUnpackStructNilLikeDBQuery(t *testing.T) { + keys := []string{"j"} + vals := make([]interface{}, 0, len(keys)) + for i := 0; i < cap(vals); i++ { + vals = append(vals, new(interface{})) + } + mppr := &mapper{ + conv: SnakeCaseConverter, + } + + var v struct { + J *custom + } + v.J = &custom{ + a: "a", b: "b", + } + + err := mppr.unpack(keys, vals, &v) + if err != nil { + t.Fatal(err) + } + if v.J != nil { + t.Fatal(v.J) + } +} + func TestUnpackStructNilComplexToNil(t *testing.T) { keys := []string{"j"} vals := []interface{}{ From 5a986e78d81c16ab3e8f7fb812a0b700e568ce8a Mon Sep 17 00:00:00 2001 From: John Bramlett Date: Sun, 22 Jan 2023 16:18:59 -0500 Subject: [PATCH 11/14] Adding configurable LRU cachesize + ability to disable use of prepared stmts --- db.go | 25 +++++++++-------- lru.go | 4 +-- query.go | 84 +++++++++++++++++++++++++++++++++++--------------------- 3 files changed, 69 insertions(+), 44 deletions(-) diff --git a/db.go b/db.go index b8287ad..aa67637 100644 --- a/db.go +++ b/db.go @@ -3,6 +3,7 @@ package jet import ( "context" "database/sql" + "strings" ) // LogFunc can be set on the Db instance to allow query logging. @@ -19,27 +20,29 @@ type Db struct { // Defaults to SnakeCaseConverter. ColumnConverter ColumnConverter - driver string - source string - lru *lru + driver string + source string + lru *lru + skipPreparedStmts bool } // Open opens a new database connection. -func Open(driverName, dataSourceName string) (*Db, error) { - return OpenFunc(driverName, dataSourceName, sql.Open) +func Open(driverName, dataSourceName string, preparedStmtCacheSize int) (*Db, error) { + return OpenFunc(driverName, dataSourceName, sql.Open, preparedStmtCacheSize) } // OpenFunc opens a new database connection by using the passed `fn`. -func OpenFunc(driverName, dataSourceName string, fn func(string, string) (*sql.DB, error)) (*Db, error) { +func OpenFunc(driverName, dataSourceName string, fn func(string, string) (*sql.DB, error), preparedStmtCacheSize int) (*Db, error) { db, err := fn(driverName, dataSourceName) if err != nil { return nil, err } j := &Db{ - ColumnConverter: SnakeCaseConverter, // default - driver: driverName, - source: dataSourceName, - lru: newLru(), + ColumnConverter: SnakeCaseConverter, // default + driver: driverName, + source: dataSourceName, + lru: newLru(preparedStmtCacheSize), + skipPreparedStmts: strings.Contains(dataSourceName, "interpolateParams=true"), } j.DB = db @@ -75,5 +78,5 @@ func (db *Db) Query(query string, args ...interface{}) Runnable { // QueryContext creates a prepared query that can be run with Rows or Run. func (db *Db) QueryContext(ctx context.Context, query string, args ...interface{}) Runnable { - return newQuery(ctx, db, db, query, args...) + return newQuery(ctx, db.skipPreparedStmts, db, db, query, args...) } diff --git a/lru.go b/lru.go index 759a3d9..11d424f 100644 --- a/lru.go +++ b/lru.go @@ -19,9 +19,9 @@ type lruItem struct { stmt *sql.Stmt } -func newLru() *lru { +func newLru(maxItems int) *lru { return &lru{ - maxItems: 500, + maxItems: maxItems, keys: make(map[string]*list.Element), list: list.New(), } diff --git a/query.go b/query.go index 905ee60..1cd0907 100644 --- a/query.go +++ b/query.go @@ -7,24 +7,26 @@ import ( ) type jetQuery struct { - m sync.Mutex - db *Db - qo queryObject - id string - query string - args []interface{} - ctx context.Context + m sync.Mutex + db *Db + qo queryObject + id string + query string + args []interface{} + ctx context.Context + skipPreparedStmts bool } // newQuery initiates a new query for the provided query object (either *sql.Tx or *sql.DB) -func newQuery(ctx context.Context, qo queryObject, db *Db, query string, args ...interface{}) *jetQuery { +func newQuery(ctx context.Context, skipPreparedStmts bool, qo queryObject, db *Db, query string, args ...interface{}) *jetQuery { return &jetQuery{ - qo: qo, - db: db, - id: newQueryId(), - query: query, - args: args, - ctx: ctx, + qo: qo, + db: db, + id: newQueryId(), + query: query, + args: args, + ctx: ctx, + skipPreparedStmts: skipPreparedStmts, } } @@ -46,6 +48,9 @@ func (q *jetQuery) Rows(v interface{}) (err error) { case *sql.Tx: useLru = false } + if q.skipPreparedStmts { + useLru = false + } query, args := substituteMapAndArrayMarks(q.query, q.args...) @@ -74,30 +79,47 @@ func (q *jetQuery) Rows(v interface{}) (err error) { } // prepare statement - stmt, ok := q.db.lru.get(query) - if !useLru || !ok { - stmt, err = q.qo.Prepare(query) + var rows *sql.Rows + if q.skipPreparedStmts { + conn, err := q.db.DB.Conn(q.ctx) if err != nil { return err } - if useLru { - q.db.lru.put(query, stmt) - } else { - defer stmt.Close() + defer conn.Close() + + if v == nil { + _, err := conn.ExecContext(q.ctx, query, args...) + return err } - } - // If no rows need to be unpacked use Exec - if v == nil { - _, err := stmt.ExecContext(q.ctx, args...) - return err - } + rows, err = conn.QueryContext(q.ctx, query, args...) + } else { + stmt, ok := q.db.lru.get(query) + if !useLru || !ok { + stmt, err = q.qo.Prepare(query) + if err != nil { + return err + } + if useLru { + q.db.lru.put(query, stmt) + } else { + defer stmt.Close() + } + } + // If no rows need to be unpacked use Exec + if v == nil { + _, err := stmt.ExecContext(q.ctx, args...) + return err + } + + // run query + rows, err = stmt.QueryContext(q.ctx, args...) + if err != nil { + return err + } - // run query - rows, err := stmt.QueryContext(q.ctx, args...) - if err != nil { - return err } + defer rows.Close() cols, err := rows.Columns() From 9cc29defb3d0f6d524337901161a0b1a4d947632 Mon Sep 17 00:00:00 2001 From: John Bramlett Date: Sun, 22 Jan 2023 16:19:53 -0500 Subject: [PATCH 12/14] Updating .gitignore to ignore .idea dir --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ecfc354..e74c392 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ cpu.out *.sublime-project *.sublime-workspace + +.idea From 66689b3e992126901ed2fb3e405213be18264ebe Mon Sep 17 00:00:00 2001 From: John Bramlett Date: Sun, 22 Jan 2023 19:43:10 -0500 Subject: [PATCH 13/14] Adding the ability to get the cache size --- db.go | 4 ++++ lru.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/db.go b/db.go index aa67637..22295e3 100644 --- a/db.go +++ b/db.go @@ -80,3 +80,7 @@ func (db *Db) Query(query string, args ...interface{}) Runnable { func (db *Db) QueryContext(ctx context.Context, query string, args ...interface{}) Runnable { return newQuery(ctx, db.skipPreparedStmts, db, db, query, args...) } + +func (db *Db) CacheSize() int { + return db.lru.size() +} diff --git a/lru.go b/lru.go index 11d424f..541a4b0 100644 --- a/lru.go +++ b/lru.go @@ -79,6 +79,10 @@ func (c *lru) clean() { } } +func (c *lru) size() int { + return c.list.Len() +} + // makeKey hashes the key to save some bytes func makeKey(k string) string { buffer := sha1.New() From c4fcf2b440290613b2be57174aa3be739c9a1083 Mon Sep 17 00:00:00 2001 From: John Bramlett Date: Mon, 23 Jan 2023 08:38:18 -0500 Subject: [PATCH 14/14] Making use of prepared stmts optional --- db.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/db.go b/db.go index 22295e3..a14664e 100644 --- a/db.go +++ b/db.go @@ -3,7 +3,6 @@ package jet import ( "context" "database/sql" - "strings" ) // LogFunc can be set on the Db instance to allow query logging. @@ -27,12 +26,12 @@ type Db struct { } // Open opens a new database connection. -func Open(driverName, dataSourceName string, preparedStmtCacheSize int) (*Db, error) { - return OpenFunc(driverName, dataSourceName, sql.Open, preparedStmtCacheSize) +func Open(driverName, dataSourceName string, usePreparedStmts bool, preparedStmtCacheSize int) (*Db, error) { + return OpenFunc(driverName, dataSourceName, sql.Open, usePreparedStmts, preparedStmtCacheSize) } // OpenFunc opens a new database connection by using the passed `fn`. -func OpenFunc(driverName, dataSourceName string, fn func(string, string) (*sql.DB, error), preparedStmtCacheSize int) (*Db, error) { +func OpenFunc(driverName, dataSourceName string, fn func(string, string) (*sql.DB, error), usePreparedStmts bool, preparedStmtCacheSize int) (*Db, error) { db, err := fn(driverName, dataSourceName) if err != nil { return nil, err @@ -42,7 +41,7 @@ func OpenFunc(driverName, dataSourceName string, fn func(string, string) (*sql.D driver: driverName, source: dataSourceName, lru: newLru(preparedStmtCacheSize), - skipPreparedStmts: strings.Contains(dataSourceName, "interpolateParams=true"), + skipPreparedStmts: usePreparedStmts, } j.DB = db