PLT-6341/PLT-6342 Update gorp to mattermost fork and add connection timeout setting (#6410)

* Update gorp to mattermost fork and add connection timeout setting

* Add go dependency

* Rename from connection timeout to query timeout

* Properly add gorp dependency
This commit is contained in:
Joram Wilander
2017-05-29 15:46:35 -04:00
committed by GitHub
parent f9843b3800
commit 860e5d483c
40 changed files with 1423 additions and 434 deletions

View File

@@ -1,4 +1,5 @@
_test
*.test
_testmain.go
_obj
*~

View File

@@ -1,9 +1,13 @@
language: go
go:
- 1.3
- 1.4
- 1.6
- 1.7
- tip
matrix:
allow_failures:
- go: tip
services:
- mysql
- postgres
@@ -24,5 +28,6 @@ before_script:
- go get github.com/go-sql-driver/mysql
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
- go get github.com/onsi/ginkgo/ginkgo
script: ./test_all.sh

34
vendor/github.com/mattermost/gorp/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,34 @@
# Contributions are very welcome!
## First: Create an Issue
Even if your fix is simple, we'd like to have an issue to relate to
the PR. Discussion about the architecture and value can go on the
issue, leaving PR comments exclusively for coding style.
## Second: Make Your PR
- Fork the `master` branch
- Make your change
- Make a PR against the `master` branch
You don't need to wait for comments on the issue before making your
PR. If you do wait for comments, you'll have a better chance of
getting your PR accepted the first time around, but it's not
necessary.
## Third: Be Patient
- If your change breaks backward compatibility, this becomes
especially true.
We all have lives and jobs, and many of us are no longer on projects
that make use of `gorp`. We will get back to you, but it might take a
while.
## Fourth: Consider Becoming a Maintainer
We really do need help. We will likely ask you for help after a good
PR, but if we don't, please create an issue requesting maintainership.
Considering how few of us are currently active, we are unlikely to
refuse good help.

View File

@@ -4,23 +4,40 @@
[![code coverage](https://img.shields.io/coveralls/go-gorp/gorp.svg)](https://coveralls.io/r/go-gorp/gorp)
[![issues](https://img.shields.io/github/issues/go-gorp/gorp.svg)](https://github.com/go-gorp/gorp/issues)
[![godoc v1](https://img.shields.io/badge/godoc-v1-375EAB.svg)](https://godoc.org/gopkg.in/gorp.v1)
[![godoc v2](https://img.shields.io/badge/godoc-v2-375EAB.svg)](https://godoc.org/gopkg.in/gorp.v2)
[![godoc bleeding edge](https://img.shields.io/badge/godoc-bleeding--edge-375EAB.svg)](https://godoc.org/github.com/go-gorp/gorp)
### Update 2015-07-01 Cleanup & feature freeze ([#270](https://github.com/go-gorp/gorp/issues/270))
### Update 2016-11-13: Future versions
We are currently cleaning up the backlog of issues and PR's. When this is done the codebase will be split into separate files and there will be breaking changes to the API's. We're also adding better tests and documentation. As a result of these changes the `master` branch will be unstable. Please use `gopkg.in/gorp.v1`. When the cleanup and changes are done, we will release `v2.0`.
As many of the maintainers have become busy with other projects,
progress toward the ever-elusive v2 has slowed to the point that we're
only occasionally making progress outside of merging pull requests.
In the interest of continuing to release, I'd like to lean toward a
more maintainable path forward.
At this time we won't accept new feature-related pull-requests because of changes to the codebase. Please create an issue for your feature and wait until `v2.0` has been released.
For more information, please read [#270](https://github.com/go-gorp/gorp/issues/270).
For the moment, I am releasing a v2 tag with the current feature set
from master, as some of those features have been actively used and
relied on by more than one project. Our next goal is to continue
cleaning up the code base with non-breaking changes as much as
possible, but if/when a breaking change is needed, we'll just release
new versions. This allows us to continue development at whatever pace
we're capable of, without delaying the release of features or refusing
PRs.
## Introduction
I hesitate to call gorp an ORM. Go doesn't really have objects, at least not in the classic Smalltalk/Java sense. There goes the "O". gorp doesn't know anything about the relationships between your structs (at least not yet). So the "R" is questionable too (but I use it in the name because, well, it seemed more clever).
I hesitate to call gorp an ORM. Go doesn't really have objects, at
least not in the classic Smalltalk/Java sense. There goes the "O".
gorp doesn't know anything about the relationships between your
structs (at least not yet). So the "R" is questionable too (but I use
it in the name because, well, it seemed more clever).
The "M" is alive and well. Given some Go structs and a database, gorp should remove a fair amount of boilerplate busy-work from your code.
The "M" is alive and well. Given some Go structs and a database, gorp
should remove a fair amount of boilerplate busy-work from your code.
I hope that gorp saves you time, minimizes the drudgery of getting data in and out of your database, and helps your code focus on algorithms, not infrastructure.
I hope that gorp saves you time, minimizes the drudgery of getting
data in and out of your database, and helps your code focus on
algorithms, not infrastructure.
* Bind struct fields to table columns via API or tag
* Support for embedded structs
@@ -35,42 +52,59 @@ I hope that gorp saves you time, minimizes the drudgery of getting data in and o
* Bind arbitrary SQL queries to a struct
* Bind slice to SELECT query results without type assertions
* Use positional or named bind parameters in custom SELECT queries
* Optional optimistic locking using a version column (for update/deletes)
* Optional optimistic locking using a version column (for
update/deletes)
## Installation
# install the library:
go get gopkg.in/gorp.v1
// use in your .go code:
import (
"gopkg.in/gorp.v1"
)
Use `go get` or your favorite vendoring tool, using whichever import
path you'd like.
## Versioning
This project provides a stable release (v1.x tags) and a bleeding edge codebase (master).
We use semantic version tags. Feel free to import through `gopkg.in`
(e.g. `gopkg.in/gorp.v2`) to get the latest tag for a major version,
or check out the tag using your favorite vendoring tool.
`gopkg.in/gorp.v1` points to the latest v1.x tag. The API's for v1 are stable and shouldn't change. Development takes place at the master branch. Althought the code in master should always compile and test successfully, it might break API's. We aim to maintain backwards compatibility, but API's and behaviour might be changed to fix a bug. Also note that API's that are new in the master branch can change until released as v2.
Development is not very active right now, but we have plans to
restructure `gorp` as we continue to move toward a more extensible
system. Whenever a breaking change is needed, the major version will
be bumped.
If you want to use bleeding edge, use `github.com/go-gorp/gorp` as import path.
The `master` branch is where all development is done, and breaking
changes may happen from time to time. That said, if you want to live
on the bleeding edge and are comfortable updating your code when we
make a breaking change, you may use `github.com/go-gorp/gorp` as your
import path.
## API Documentation
Full godoc output from the latest v1 release is available here:
https://godoc.org/gopkg.in/gorp.v1
For the latest code in master:
https://godoc.org/github.com/go-gorp/gorp
Check the version tags to see what's available. We'll make a good
faith effort to add badges for new versions, but we make no
guarantees.
## Supported Go versions
This package is compatible with the last 2 major versions of Go, at this time `1.3` and `1.4`.
This package is guaranteed to be compatible with the latest 2 major
versions of Go.
Any earlier versions are only supported on a best effort basis and can be dropped any time.
Go has a great compatibility promise. Upgrading your program to a newer version of Go should never really be a problem.
Any earlier versions are only supported on a best effort basis and can
be dropped any time. Go has a great compatibility promise. Upgrading
your program to a newer version of Go should never really be a
problem.
## Migration guide
#### Pre-v2 to v2
Automatic mapping of the version column used in optimistic locking has
been removed as it could cause problems if the type was not int. The
version column must now explicitly be set with
`tablemap.SetVersionCol()`.
## Help/Support
Use our [`gitter` channel](https://gitter.im/go-gorp/gorp). We used
to use IRC, but with most of us being pulled in many directions, we
often need the email notifications from `gitter` to yell at us to sign
in.
## Quickstart
@@ -530,10 +564,11 @@ Full list of hooks that you can implement:
#### Note that this behaviour has changed in v2. See [Migration Guide](#migration-guide).
gorp provides a simple optimistic locking feature, similar to Java's JPA, that
will raise an error if you try to update/delete a row whose `version` column
has a value different than the one in memory. This provides a safe way to do
"select then update" style operations without explicit read and write locks.
gorp provides a simple optimistic locking feature, similar to Java's
JPA, that will raise an error if you try to update/delete a row whose
`version` column has a value different than the one in memory. This
provides a safe way to do "select then update" style operations
without explicit read and write locks.
```go
// Version is an auto-incremented number, managed by gorp
@@ -582,13 +617,15 @@ if ok {
```
### Adding INDEX(es) on column(s) beyond the primary key ###
Indexes are frequently critical for performance. Here is how to add them to your tables.
Indexes are frequently critical for performance. Here is how to add
them to your tables.
NB: SqlServer and Oracle need testing and possible adjustment to the
NB: SqlServer and Oracle need testing and possible adjustment to the
CreateIndexSuffix() and DropIndexSuffix() methods to make AddIndex()
work for them.
In the example below we put an index both on the Id field, and on the AcctId field.
In the example below we put an index both on the Id field, and on the
AcctId field.
```
type Account struct {
@@ -626,76 +663,111 @@ MariaDB [test]> show create table Account;
## Database Drivers
gorp uses the Go 1 `database/sql` package. A full list of compliant drivers is available here:
gorp uses the Go 1 `database/sql` package. A full list of compliant
drivers is available here:
http://code.google.com/p/go-wiki/wiki/SQLDrivers
Sadly, SQL databases differ on various issues. gorp provides a Dialect interface that should be
implemented per database vendor. Dialects are provided for:
Sadly, SQL databases differ on various issues. gorp provides a Dialect
interface that should be implemented per database vendor. Dialects
are provided for:
* MySQL
* PostgreSQL
* sqlite3
Each of these three databases pass the test suite. See `gorp_test.go` for example
DSNs for these three databases.
Each of these three databases pass the test suite. See `gorp_test.go`
for example DSNs for these three databases.
Support is also provided for:
* Oracle (contributed by @klaidliadon)
* SQL Server (contributed by @qrawl) - use driver: github.com/denisenkom/go-mssqldb
* SQL Server (contributed by @qrawl) - use driver:
github.com/denisenkom/go-mssqldb
Note that these databases are not covered by CI and I (@coopernurse) have no good way to
test them locally. So please try them and send patches as needed, but expect a bit more
unpredicability.
Note that these databases are not covered by CI and I (@coopernurse)
have no good way to test them locally. So please try them and send
patches as needed, but expect a bit more unpredicability.
## Sqlite3 Extensions
In order to use sqlite3 extensions you need to first register a custom driver:
```go
import (
"database/sql"
// use whatever database/sql driver you wish
sqlite "github.com/mattn/go-sqlite3"
)
func customDriver() (*sql.DB, error) {
// create custom driver with extensions defined
sql.Register("sqlite3-custom", &sqlite.SQLiteDriver{
Extensions: []string{
"mod_spatialite",
},
})
// now you can then connect using the 'sqlite3-custom' driver instead of 'sqlite3'
return sql.Open("sqlite3-custom", "/tmp/post_db.bin")
}
```
## Known Issues
### SQL placeholder portability
Different databases use different strings to indicate variable placeholders in
prepared SQL statements. Unlike some database abstraction layers (such as JDBC),
Go's `database/sql` does not standardize this.
Different databases use different strings to indicate variable
placeholders in prepared SQL statements. Unlike some database
abstraction layers (such as JDBC), Go's `database/sql` does not
standardize this.
SQL generated by gorp in the `Insert`, `Update`, `Delete`, and `Get` methods delegates
to a Dialect implementation for each database, and will generate portable SQL.
SQL generated by gorp in the `Insert`, `Update`, `Delete`, and `Get`
methods delegates to a Dialect implementation for each database, and
will generate portable SQL.
Raw SQL strings passed to `Exec`, `Select`, `SelectOne`, `SelectInt`, etc will not be
parsed. Consequently you may have portability issues if you write a query like this:
Raw SQL strings passed to `Exec`, `Select`, `SelectOne`, `SelectInt`,
etc will not be parsed. Consequently you may have portability issues
if you write a query like this:
```go
// works on MySQL and Sqlite3, but not with Postgresql
err := dbmap.SelectOne(&val, "select * from foo where id = ?", 30)
```
```go // works on MySQL and Sqlite3, but not with Postgresql err :=
dbmap.SelectOne(&val, "select * from foo where id = ?", 30) ```
In `Select` and `SelectOne` you can use named parameters to work around this.
The following is portable:
In `Select` and `SelectOne` you can use named parameters to work
around this. The following is portable:
```go
err := dbmap.SelectOne(&val, "select * from foo where id = :id",
map[string]interface{} { "id": 30})
```
```go err := dbmap.SelectOne(&val, "select * from foo where id = :id",
map[string]interface{} { "id": 30}) ```
Additionally, when using Postgres as your database, you should utilize `$1` instead
of `?` placeholders as utilizing `?` placeholders when querying Postgres will result
in `pq: operator does not exist` errors. Alternatively, use
`dbMap.Dialect.BindVar(varIdx)` to get the proper variable binding for your dialect.
Additionally, when using Postgres as your database, you should utilize
`$1` instead of `?` placeholders as utilizing `?` placeholders when
querying Postgres will result in `pq: operator does not exist`
errors. Alternatively, use `dbMap.Dialect.BindVar(varIdx)` to get the
proper variable binding for your dialect.
### time.Time and time zones
gorp will pass `time.Time` fields through to the `database/sql` driver, but note that
the behavior of this type varies across database drivers.
gorp will pass `time.Time` fields through to the `database/sql`
driver, but note that the behavior of this type varies across database
drivers.
MySQL users should be especially cautious. See: https://github.com/ziutek/mymysql/pull/77
MySQL users should be especially cautious. See:
https://github.com/ziutek/mymysql/pull/77
To avoid any potential issues with timezone/DST, consider using an integer field for time
data and storing UNIX time.
To avoid any potential issues with timezone/DST, consider:
- Using an integer field for time data and storing UNIX time.
- Using a custom time type that implements some SQL types:
- [`"database/sql".Scanner`](https://golang.org/pkg/database/sql/#Scanner)
- [`"database/sql/driver".Valuer`](https://golang.org/pkg/database/sql/driver/#Valuer)
## Running the tests
The included tests may be run against MySQL, Postgresql, or sqlite3.
You must set two environment variables so the test code knows which driver to
use, and how to connect to your database.
You must set two environment variables so the test code knows which
driver to use, and how to connect to your database.
```sh
# MySQL example:
@@ -709,34 +781,18 @@ go test
go test -bench="Bench" -benchtime 10
```
Valid `GORP_TEST_DIALECT` values are: "mysql"(for mymysql), "gomysql"(for go-sql-driver), "postgres", "sqlite"
See the `test_all.sh` script for examples of all 3 databases. This is the script I run
locally to test the library.
Valid `GORP_TEST_DIALECT` values are: "mysql"(for mymysql),
"gomysql"(for go-sql-driver), "postgres", "sqlite" See the
`test_all.sh` script for examples of all 3 databases. This is the
script I run locally to test the library.
## Performance
gorp uses reflection to construct SQL queries and bind parameters. See the BenchmarkNativeCrud vs BenchmarkGorpCrud in gorp_test.go for a simple perf test. On my MacBook Pro gorp is about 2-3% slower than hand written SQL.
gorp uses reflection to construct SQL queries and bind parameters.
See the BenchmarkNativeCrud vs BenchmarkGorpCrud in gorp_test.go for a
simple perf test. On my MacBook Pro gorp is about 2-3% slower than
hand written SQL.
## Migration guide
#### Pre-v2 to v2
Automatic mapping of the version column used in optimistic locking has been removed as it could cause problems if the type was not int. The version column must now explicitly be set with tablemap.SetVersionCol().
## Help/Support
IRC: #gorp
Mailing list: gorp-dev@googlegroups.com
Bugs/Enhancements: Create a github issue
## Pull requests / Contributions
Contributions are very welcome. Please follow these guidelines:
* Fork the `master` branch and issue pull requests targeting the `master` branch
* If you are adding an enhancement, please open an issue first with your proposed change.
* Changes that break backwards compatibility in the public API are only accepted after we
discuss on a GitHub issue for a while.
Thanks!
## Contributors

View File

@@ -13,6 +13,7 @@ package gorp
import (
"bytes"
"context"
"database/sql"
"database/sql/driver"
"errors"
@@ -41,9 +42,34 @@ type DbMap struct {
TypeConverter TypeConverter
tables []*TableMap
logger GorpLogger
logPrefix string
QueryTimeout time.Duration
tables []*TableMap
tablesDynamic map[string]*TableMap // tables that use same go-struct and different db table names
logger GorpLogger
logPrefix string
}
func (m *DbMap) dynamicTableAdd(tableName string, tbl *TableMap) {
if m.tablesDynamic == nil {
m.tablesDynamic = make(map[string]*TableMap)
}
m.tablesDynamic[tableName] = tbl
}
func (m *DbMap) dynamicTableFind(tableName string) (*TableMap, bool) {
if m.tablesDynamic == nil {
return nil, false
}
tbl, found := m.tablesDynamic[tableName]
return tbl, found
}
func (m *DbMap) dynamicTableMap() map[string]*TableMap {
if m.tablesDynamic == nil {
m.tablesDynamic = make(map[string]*TableMap)
}
return m.tablesDynamic
}
func (m *DbMap) CreateIndex() error {
@@ -52,36 +78,52 @@ func (m *DbMap) CreateIndex() error {
dialect := reflect.TypeOf(m.Dialect)
for _, table := range m.tables {
for _, index := range table.indexes {
s := bytes.Buffer{}
s.WriteString("create")
if index.Unique {
s.WriteString(" unique")
}
s.WriteString(" index")
s.WriteString(fmt.Sprintf(" %s on %s", index.IndexName, table.TableName))
if dname := dialect.Name(); dname == "PostgresDialect" && index.IndexType != "" {
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
}
s.WriteString(" (")
for x, col := range index.columns {
if x > 0 {
s.WriteString(", ")
}
s.WriteString(m.Dialect.QuoteField(col))
}
s.WriteString(")")
if dname := dialect.Name(); dname == "MySQLDialect" && index.IndexType != "" {
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
}
s.WriteString(";")
_, err = m.Exec(s.String())
err = m.createIndexImpl(dialect, table, index)
if err != nil {
break
}
}
}
for _, table := range m.dynamicTableMap() {
for _, index := range table.indexes {
err = m.createIndexImpl(dialect, table, index)
if err != nil {
break
}
}
}
return err
}
func (m *DbMap) createIndexImpl(dialect reflect.Type,
table *TableMap,
index *IndexMap) error {
s := bytes.Buffer{}
s.WriteString("create")
if index.Unique {
s.WriteString(" unique")
}
s.WriteString(" index")
s.WriteString(fmt.Sprintf(" %s on %s", index.IndexName, table.TableName))
if dname := dialect.Name(); dname == "PostgresDialect" && index.IndexType != "" {
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
}
s.WriteString(" (")
for x, col := range index.columns {
if x > 0 {
s.WriteString(", ")
}
s.WriteString(m.Dialect.QuoteField(col))
}
s.WriteString(")")
if dname := dialect.Name(); dname == "MySQLDialect" && index.IndexType != "" {
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
}
s.WriteString(";")
_, err := m.ExecNoTimeout(s.String())
return err
}
@@ -98,7 +140,7 @@ func (t *TableMap) DropIndex(name string) error {
s.WriteString(fmt.Sprintf(" %s %s", t.dbmap.Dialect.DropIndexSuffix(), t.TableName))
}
s.WriteString(";")
_, e := t.dbmap.Exec(s.String())
_, e := t.dbmap.ExecNoTimeout(s.String())
if e != nil {
err = e
}
@@ -155,6 +197,36 @@ func (m *DbMap) AddTableWithNameAndSchema(i interface{}, schema string, name str
return tmap
}
// AddTableDynamic registers the given interface type with gorp.
// The table name will be dynamically determined at runtime by
// using the GetTableName method on DynamicTable interface
func (m *DbMap) AddTableDynamic(inp DynamicTable, schema string) *TableMap {
val := reflect.ValueOf(inp)
elm := val.Elem()
t := elm.Type()
name := inp.TableName()
if name == "" {
panic("Missing table name in DynamicTable instance")
}
// Check if there is another dynamic table with the same name
if _, found := m.dynamicTableFind(name); found {
panic(fmt.Sprintf("A table with the same name %v already exists", name))
}
tmap := &TableMap{gotype: t, TableName: name, SchemaName: schema, dbmap: m}
var primaryKey []*ColumnMap
tmap.Columns, primaryKey = m.readStructColumns(t)
if len(primaryKey) > 0 {
tmap.keys = append(tmap.keys, primaryKey...)
}
m.dynamicTableAdd(name, tmap)
return tmap
}
func (m *DbMap) readStructColumns(t reflect.Type) (cols []*ColumnMap, primaryKey []*ColumnMap) {
primaryKey = make([]*ColumnMap, 0)
n := t.NumField()
@@ -189,6 +261,7 @@ func (m *DbMap) readStructColumns(t reflect.Type) (cols []*ColumnMap, primaryKey
var defaultValue string
var isAuto bool
var isPK bool
var isNotNull bool
for _, argString := range cArguments[1:] {
argString = strings.TrimSpace(argString)
arg := strings.SplitN(argString, ":", 2)
@@ -216,6 +289,8 @@ func (m *DbMap) readStructColumns(t reflect.Type) (cols []*ColumnMap, primaryKey
isPK = true
case "autoincrement":
isAuto = true
case "notnull":
isNotNull = true
default:
panic(fmt.Sprintf("Unrecognized tag option for field %v: %v", f.Name, arg))
}
@@ -259,6 +334,7 @@ func (m *DbMap) readStructColumns(t reflect.Type) (cols []*ColumnMap, primaryKey
gotype: gotype,
isPK: isPK,
isAutoIncr: isAuto,
isNotNull: isNotNull,
MaxSize: maxSize,
}
if isPK {
@@ -304,11 +380,20 @@ func (m *DbMap) createTables(ifNotExists bool) error {
for i := range m.tables {
table := m.tables[i]
sql := table.SqlForCreate(ifNotExists)
_, err = m.Exec(sql)
_, err = m.ExecNoTimeout(sql)
if err != nil {
break
return err
}
}
for _, tbl := range m.dynamicTableMap() {
sql := tbl.SqlForCreate(ifNotExists)
_, err = m.ExecNoTimeout(sql)
if err != nil {
return err
}
}
return err
}
@@ -316,13 +401,25 @@ func (m *DbMap) createTables(ifNotExists bool) error {
// Returns an error when the table does not exist.
func (m *DbMap) DropTable(table interface{}) error {
t := reflect.TypeOf(table)
return m.dropTable(t, false)
tableName := ""
if dyn, ok := table.(DynamicTable); ok {
tableName = dyn.TableName()
}
return m.dropTable(t, tableName, false)
}
// DropTableIfExists drops an individual table when the table exists.
func (m *DbMap) DropTableIfExists(table interface{}) error {
t := reflect.TypeOf(table)
return m.dropTable(t, true)
tableName := ""
if dyn, ok := table.(DynamicTable); ok {
tableName = dyn.TableName()
}
return m.dropTable(t, tableName, true)
}
// DropTables iterates through TableMaps registered to this DbMap and
@@ -347,12 +444,20 @@ func (m *DbMap) dropTables(addIfExists bool) (err error) {
return err
}
}
for _, table := range m.dynamicTableMap() {
err = m.dropTableImpl(table, addIfExists)
if err != nil {
return err
}
}
return err
}
// Implementation of dropping a single table.
func (m *DbMap) dropTable(t reflect.Type, addIfExists bool) error {
table := tableOrNil(m, t)
func (m *DbMap) dropTable(t reflect.Type, name string, addIfExists bool) error {
table := tableOrNil(m, t, name)
if table == nil {
return fmt.Errorf("table %s was not registered", table.TableName)
}
@@ -365,7 +470,7 @@ func (m *DbMap) dropTableImpl(table *TableMap, ifExists bool) (err error) {
if ifExists {
tableDrop = m.Dialect.IfTableExists(tableDrop, table.SchemaName, table.TableName)
}
_, err = m.Exec(fmt.Sprintf("%s %s;", tableDrop, m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
_, err = m.ExecNoTimeout(fmt.Sprintf("%s %s;", tableDrop, m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
return err
}
@@ -377,11 +482,19 @@ func (m *DbMap) TruncateTables() error {
var err error
for i := range m.tables {
table := m.tables[i]
_, e := m.Exec(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
_, e := m.ExecNoTimeout(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
if e != nil {
err = e
}
}
for _, table := range m.dynamicTableMap() {
_, e := m.ExecNoTimeout(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
if e != nil {
err = e
}
}
return err
}
@@ -487,12 +600,22 @@ func (m *DbMap) Select(i interface{}, query string, args ...interface{}) ([]inte
// Exec runs an arbitrary SQL statement. args represent the bind parameters.
// This is equivalent to running: Exec() using database/sql
// Times out based on the DbMap.QueryTimeout field
func (m *DbMap) Exec(query string, args ...interface{}) (sql.Result, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return exec(m, query, args...)
return exec(m, query, true, args...)
}
// ExecNoTimeout is the same as Exec except it will not time out
func (m *DbMap) ExecNoTimeout(query string, args ...interface{}) (sql.Result, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return exec(m, query, false, args...)
}
// SelectInt is a convenience wrapper around the gorp.SelectInt function
@@ -547,7 +670,7 @@ func (m *DbMap) Begin() (*Transaction, error) {
// If no table is mapped to that type an error is returned.
// If checkPK is true and the mapped table has no registered PKs, an error is returned.
func (m *DbMap) TableFor(t reflect.Type, checkPK bool) (*TableMap, error) {
table := tableOrNil(m, t)
table := tableOrNil(m, t, "")
if table == nil {
return nil, fmt.Errorf("no table found for type: %v", t.Name())
}
@@ -561,6 +684,25 @@ func (m *DbMap) TableFor(t reflect.Type, checkPK bool) (*TableMap, error) {
return table, nil
}
// DynamicTableFor returns the *TableMap for the dynamic table corresponding
// to the input tablename
// If no table is mapped to that tablename an error is returned.
// If checkPK is true and the mapped table has no registered PKs, an error is returned.
func (m *DbMap) DynamicTableFor(tableName string, checkPK bool) (*TableMap, error) {
table, found := m.dynamicTableFind(tableName)
if !found {
return nil, fmt.Errorf("gorp: no table found for name: %v", tableName)
}
if checkPK && len(table.keys) < 1 {
e := fmt.Sprintf("gorp: no keys defined for table: %s",
table.TableName)
return nil, errors.New(e)
}
return table, nil
}
// Prepare creates a prepared statement for later queries or executions.
// Multiple queries or executions may be run concurrently from the returned statement.
// This is equivalent to running: Prepare() using database/sql
@@ -572,7 +714,15 @@ func (m *DbMap) Prepare(query string) (*sql.Stmt, error) {
return m.Db.Prepare(query)
}
func tableOrNil(m *DbMap, t reflect.Type) *TableMap {
func tableOrNil(m *DbMap, t reflect.Type, name string) *TableMap {
if name != "" {
// Search by table name (dynamic tables)
if table, found := m.dynamicTableFind(name); found {
return table
}
return nil
}
for i := range m.tables {
table := m.tables[i]
if table.gotype == t {
@@ -590,8 +740,18 @@ func (m *DbMap) tableForPointer(ptr interface{}, checkPK bool) (*TableMap, refle
return nil, reflect.Value{}, errors.New(e)
}
elem := ptrv.Elem()
etype := reflect.TypeOf(elem.Interface())
t, err := m.TableFor(etype, checkPK)
ifc := elem.Interface()
var t *TableMap
var err error
tableName := ""
if dyn, isDyn := ptr.(DynamicTable); isDyn {
tableName = dyn.TableName()
t, err = m.DynamicTableFor(tableName, checkPK)
} else {
etype := reflect.TypeOf(ifc)
t, err = m.TableFor(etype, checkPK)
}
if err != nil {
return nil, reflect.Value{}, err
}
@@ -599,22 +759,42 @@ func (m *DbMap) tableForPointer(ptr interface{}, checkPK bool) (*TableMap, refle
return t, elem, nil
}
func (m *DbMap) queryRow(query string, args ...interface{}) *sql.Row {
func (m *DbMap) QueryRow(query string, args ...interface{}) *sql.Row {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return m.Db.QueryRow(query, args...)
}
func (m *DbMap) query(query string, args ...interface{}) (*sql.Rows, error) {
func (m *DbMap) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return m.Db.QueryRowContext(ctx, query, args...)
}
func (m *DbMap) Query(query string, args ...interface{}) (*sql.Rows, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return m.Db.Query(query, args...)
}
func (m *DbMap) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return m.Db.QueryContext(ctx, query, args...)
}
func (m *DbMap) trace(started time.Time, query string, args ...interface{}) {
if m.logger != nil {
var margs = argsString(args...)

View File

@@ -18,6 +18,9 @@ import "reflect"
// but this could change in the future
type Dialect interface {
// dialect name
Name() string
// adds a suffix to any query, usually ";"
QuerySuffix() string

View File

@@ -27,6 +27,8 @@ type MySQLDialect struct {
Encoding string
}
func (d MySQLDialect) Name() string { return "MySQLDialect" }
func (d MySQLDialect) QuerySuffix() string { return ";" }
func (d MySQLDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {

204
vendor/github.com/mattermost/gorp/dialect_mysql_test.go generated vendored Normal file
View File

@@ -0,0 +1,204 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
package gorp_test
import (
"database/sql"
"reflect"
"time"
// ginkgo/gomega functions read better as dot-imports.
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
"github.com/go-gorp/gorp"
)
var _ = Describe("MySQLDialect", func() {
var (
engine, encoding string
dialect gorp.MySQLDialect
)
JustBeforeEach(func() {
dialect = gorp.MySQLDialect{
Engine: engine,
Encoding: encoding,
}
})
DescribeTable("ToSqlType",
func(value interface{}, maxsize int, autoIncr bool, expected string) {
typ := reflect.TypeOf(value)
sqlType := dialect.ToSqlType(typ, maxsize, autoIncr)
Expect(sqlType).To(Equal(expected))
},
Entry("bool", true, 0, false, "boolean"),
Entry("int8", int8(1), 0, false, "tinyint"),
Entry("uint8", uint8(1), 0, false, "tinyint unsigned"),
Entry("int16", int16(1), 0, false, "smallint"),
Entry("uint16", uint16(1), 0, false, "smallint unsigned"),
Entry("int32", int32(1), 0, false, "int"),
Entry("int (treated as int32)", int(1), 0, false, "int"),
Entry("uint32", uint32(1), 0, false, "int unsigned"),
Entry("uint (treated as uint32)", uint(1), 0, false, "int unsigned"),
Entry("int64", int64(1), 0, false, "bigint"),
Entry("uint64", uint64(1), 0, false, "bigint unsigned"),
Entry("float32", float32(1), 0, false, "double"),
Entry("float64", float64(1), 0, false, "double"),
Entry("[]uint8", []uint8{1}, 0, false, "mediumblob"),
Entry("NullInt64", sql.NullInt64{}, 0, false, "bigint"),
Entry("NullFloat64", sql.NullFloat64{}, 0, false, "double"),
Entry("NullBool", sql.NullBool{}, 0, false, "tinyint"),
Entry("Time", time.Time{}, 0, false, "datetime"),
Entry("default-size string", "", 0, false, "varchar(255)"),
Entry("sized string", "", 50, false, "varchar(50)"),
Entry("large string", "", 1024, false, "text"),
)
Describe("AutoIncrStr", func() {
It("returns the auto increment string", func() {
Expect(dialect.AutoIncrStr()).To(Equal("auto_increment"))
})
})
Describe("AutoIncrBindValue", func() {
It("returns the value used to bind the auto-increment value", func() {
Expect(dialect.AutoIncrBindValue()).To(Equal("null"))
})
})
Describe("AutoIncrInsertSuffix", func() {
It("returns the suffix needed for auto-incrementing", func() {
Expect(dialect.AutoIncrInsertSuffix(nil)).To(BeEmpty())
})
})
Describe("CreateTableSuffix", func() {
Context("with an empty engine", func() {
BeforeEach(func() {
engine = ""
encoding = "foo"
})
It("panics", func() {
Expect(func() {
dialect.CreateTableSuffix()
}).To(Panic())
})
})
Context("with an empty encoding", func() {
BeforeEach(func() {
engine = "foo"
encoding = ""
})
It("panics", func() {
Expect(func() {
dialect.CreateTableSuffix()
}).To(Panic())
})
})
Context("with an engine and an encoding", func() {
BeforeEach(func() {
engine = "foo"
encoding = "bar"
})
It("returns a valid suffix", func() {
Expect(dialect.CreateTableSuffix()).To(Equal(" engine=foo charset=bar"))
})
})
})
Describe("CreateIndexSuffix", func() {
It("returns the suffix for creating indexes", func() {
Expect(dialect.CreateIndexSuffix()).To(Equal("using"))
})
})
Describe("DropIndexSuffix", func() {
It("returns the suffix for deleting indexes", func() {
Expect(dialect.DropIndexSuffix()).To(Equal("on"))
})
})
Describe("TruncateClause", func() {
It("returns the clause for truncating a table", func() {
Expect(dialect.TruncateClause()).To(Equal("truncate"))
})
})
Describe("BindVar", func() {
It("returns the variable binding sequence", func() {
Expect(dialect.BindVar(0)).To(Equal("?"))
})
})
PDescribe("InsertAutoIncr", func() {})
Describe("QuoteField", func() {
It("returns the argument quoted as a field", func() {
Expect(dialect.QuoteField("foo")).To(Equal("`foo`"))
})
})
Describe("QuotedTableForQuery", func() {
var (
schema, table string
quotedTable string
)
JustBeforeEach(func() {
quotedTable = dialect.QuotedTableForQuery(schema, table)
})
Context("using the default schema", func() {
BeforeEach(func() {
schema = ""
table = "foo"
})
It("returns just the table", func() {
Expect(quotedTable).To(Equal("`foo`"))
})
})
Context("with a supplied schema", func() {
BeforeEach(func() {
schema = "foo"
table = "bar"
})
It("returns the schema and table", func() {
Expect(quotedTable).To(Equal("foo.`bar`"))
})
})
})
Describe("IfSchemaNotExists", func() {
It("appends 'if not exists' to the command", func() {
Expect(dialect.IfSchemaNotExists("foo", "bar")).To(Equal("foo if not exists"))
})
})
Describe("IfTableExists", func() {
It("appends 'if exists' to the command", func() {
Expect(dialect.IfTableExists("foo", "bar", "baz")).To(Equal("foo if exists"))
})
})
Describe("IfTableNotExists", func() {
It("appends 'if not exists' to the command", func() {
Expect(dialect.IfTableNotExists("foo", "bar", "baz")).To(Equal("foo if not exists"))
})
})
})

View File

@@ -20,6 +20,8 @@ import (
// Implementation of Dialect for Oracle databases.
type OracleDialect struct{}
func (d OracleDialect) Name() string { return "OracleDialect" }
func (d OracleDialect) QuerySuffix() string { return "" }
func (d OracleDialect) CreateIndexSuffix() string { return "" }

View File

@@ -21,6 +21,8 @@ type PostgresDialect struct {
suffix string
}
func (d PostgresDialect) Name() string { return "PostgresDialect" }
func (d PostgresDialect) QuerySuffix() string { return ";" }
func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
@@ -78,7 +80,7 @@ func (d PostgresDialect) AutoIncrBindValue() string {
}
func (d PostgresDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return " returning " + col.ColumnName
return " returning " + d.QuoteField(col.ColumnName)
}
// Returns suffix
@@ -104,7 +106,7 @@ func (d PostgresDialect) BindVar(i int) string {
}
func (d PostgresDialect) InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error {
rows, err := exec.query(insertSql, params...)
rows, err := exec.Query(insertSql, params...)
if err != nil {
return err
}

View File

@@ -20,6 +20,8 @@ type SqliteDialect struct {
suffix string
}
func (d SqliteDialect) Name() string { return "SQLiteDialect" }
func (d SqliteDialect) QuerySuffix() string { return ";" }
func (d SqliteDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {

View File

@@ -27,6 +27,8 @@ type SqlServerDialect struct {
Version string
}
func (d SqlServerDialect) Name() string { return "SQLServerDialect" }
func (d SqlServerDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:

View File

@@ -12,6 +12,7 @@
package gorp
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
@@ -87,6 +88,7 @@ type TypeConverter interface {
// on internal functions that convert named parameters for the Exec function.
type executor interface {
Exec(query string, args ...interface{}) (sql.Result, error)
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}
// SqlExecutor exposes gorp operations that can be run from Pre/Post
@@ -101,6 +103,7 @@ type SqlExecutor interface {
Update(list ...interface{}) (int64, error)
Delete(list ...interface{}) (int64, error)
Exec(query string, args ...interface{}) (sql.Result, error)
ExecNoTimeout(query string, args ...interface{}) (sql.Result, error)
Select(i interface{}, query string,
args ...interface{}) ([]interface{}, error)
SelectInt(query string, args ...interface{}) (int64, error)
@@ -110,8 +113,18 @@ type SqlExecutor interface {
SelectStr(query string, args ...interface{}) (string, error)
SelectNullStr(query string, args ...interface{}) (sql.NullString, error)
SelectOne(holder interface{}, query string, args ...interface{}) error
query(query string, args ...interface{}) (*sql.Rows, error)
queryRow(query string, args ...interface{}) *sql.Row
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
// DynamicTable allows the users of gorp to dynamically
// use different database table names during runtime
// while sharing the same golang struct for in-memory data
type DynamicTable interface {
TableName() string
SetTableName(string)
}
// Compile-time check that DbMap and Transaction implement the SqlExecutor
@@ -144,7 +157,7 @@ func argsString(args ...interface{}) string {
// Calls the Exec function on the executor, but attempts to expand any eligible named
// query arguments first.
func exec(e SqlExecutor, query string, args ...interface{}) (sql.Result, error) {
func exec(e SqlExecutor, query string, doTimeout bool, args ...interface{}) (sql.Result, error) {
var dbMap *DbMap
var executor executor
switch m := e.(type) {
@@ -160,7 +173,13 @@ func exec(e SqlExecutor, query string, args ...interface{}) (sql.Result, error)
query, args = maybeExpandNamedQuery(dbMap, query, args)
}
return executor.Exec(query, args...)
if doTimeout && dbMap.Dialect.Name() != "PostgresDialect" {
ctx, cancel := context.WithTimeout(context.Background(), dbMap.QueryTimeout)
defer cancel()
return executor.ExecContext(ctx, query, args...)
} else {
return executor.Exec(query, args...)
}
}
// maybeExpandNamedQuery checks the given arg to see if it's eligible to be used
@@ -220,13 +239,13 @@ func expandNamedQuery(m *DbMap, query string, keyGetter func(key string) reflect
}), args
}
func columnToFieldIndex(m *DbMap, t reflect.Type, cols []string) ([][]int, error) {
func columnToFieldIndex(m *DbMap, t reflect.Type, name string, cols []string) ([][]int, error) {
colToFieldIndex := make([][]int, len(cols))
// check if type t is a mapped table - if so we'll
// check the table for column aliasing below
tableMapped := false
table := tableOrNil(m, t)
table := tableOrNil(m, t, name)
if table != nil {
tableMapped = true
}
@@ -327,6 +346,30 @@ func toType(i interface{}) (reflect.Type, error) {
return t, nil
}
type foundTable struct {
table *TableMap
dynName *string
}
func tableFor(m *DbMap, t reflect.Type, i interface{}) (*foundTable, error) {
if dyn, isDynamic := i.(DynamicTable); isDynamic {
tableName := dyn.TableName()
table, err := m.DynamicTableFor(tableName, true)
if err != nil {
return nil, err
}
return &foundTable{
table: table,
dynName: &tableName,
}, nil
}
table, err := m.TableFor(t, true)
if err != nil {
return nil, err
}
return &foundTable{table: table}, nil
}
func get(m *DbMap, exec SqlExecutor, i interface{},
keys ...interface{}) (interface{}, error) {
@@ -335,14 +378,20 @@ func get(m *DbMap, exec SqlExecutor, i interface{},
return nil, err
}
table, err := m.TableFor(t, true)
foundTable, err := tableFor(m, t, i)
if err != nil {
return nil, err
}
table := foundTable.table
plan := table.bindGet()
v := reflect.New(t)
if foundTable.dynName != nil {
retDyn := v.Interface().(DynamicTable)
retDyn.SetTableName(*foundTable.dynName)
}
dest := make([]interface{}, len(plan.argFields))
conv := m.TypeConverter
@@ -361,7 +410,15 @@ func get(m *DbMap, exec SqlExecutor, i interface{},
dest[x] = target
}
row := exec.queryRow(plan.query, keys...)
var row *sql.Row
if m.Dialect.Name() != "PostgresDialect" {
ctx, cancel := context.WithTimeout(context.Background(), m.QueryTimeout)
defer cancel()
row = exec.QueryRowContext(ctx, plan.query, keys...)
} else {
row = exec.QueryRow(plan.query, keys...)
}
err = row.Scan(dest...)
if err != nil {
if err == sql.ErrNoRows {

13
vendor/github.com/mattermost/gorp/gorp_suite_test.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package gorp_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestGorp(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Gorp Suite")
}

View File

@@ -12,6 +12,7 @@
package gorp
import (
"context"
"database/sql"
"fmt"
"reflect"
@@ -154,15 +155,28 @@ func SelectOne(m *DbMap, e SqlExecutor, holder interface{}, query string, args .
}
func selectVal(e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
if len(args) == 1 {
switch m := e.(type) {
case *DbMap:
query, args = maybeExpandNamedQuery(m, query, args)
case *Transaction:
query, args = maybeExpandNamedQuery(m.dbmap, query, args)
}
var dbMap *DbMap
switch m := e.(type) {
case *DbMap:
dbMap = m
case *Transaction:
dbMap = m.dbmap
}
rows, err := e.query(query, args...)
if len(args) == 1 {
query, args = maybeExpandNamedQuery(dbMap, query, args)
}
var rows *sql.Rows
var err error
if dbMap.Dialect.Name() != "PostgresDialect" {
ctx, cancel := context.WithTimeout(context.Background(), dbMap.QueryTimeout)
defer cancel()
rows, err = e.QueryContext(ctx, query, args...)
} else {
rows, err = e.Query(query, args...)
}
if err != nil {
return err
}
@@ -178,14 +192,11 @@ func selectVal(e SqlExecutor, holder interface{}, query string, args ...interfac
func hookedselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
args ...interface{}) ([]interface{}, error) {
var nonFatalErr error
list, err := rawselect(m, exec, i, query, args...)
if err != nil {
if !NonFatalError(err) {
return nil, err
}
nonFatalErr = err
}
// Determine where the results are: written to i, or returned in list
@@ -209,7 +220,8 @@ func hookedselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
}
}
}
return list, nonFatalErr
return list, nil
}
func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
@@ -222,6 +234,13 @@ func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
var nonFatalErr error
tableName := ""
var dynObj DynamicTable
isDynamic := false
if dynObj, isDynamic = i.(DynamicTable); isDynamic {
tableName = dynObj.TableName()
}
// get type for i, verifying it's a supported destination
t, err := toType(i)
if err != nil {
@@ -248,7 +267,15 @@ func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
}
// Run the query
rows, err := exec.query(query, args...)
var rows *sql.Rows
if m.Dialect.Name() != "PostgresDialect" {
ctx, cancel := context.WithTimeout(context.Background(), m.QueryTimeout)
defer cancel()
rows, err = exec.QueryContext(ctx, query, args...)
} else {
rows, err = exec.Query(query, args...)
}
if err != nil {
return nil, err
}
@@ -266,7 +293,7 @@ func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
var colToFieldIndex [][]int
if intoStruct {
colToFieldIndex, err = columnToFieldIndex(m, t, cols)
colToFieldIndex, err = columnToFieldIndex(m, t, tableName, cols)
if err != nil {
if !NonFatalError(err) {
return nil, err
@@ -293,6 +320,11 @@ func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
break
}
v := reflect.New(t)
if isDynamic {
v.Interface().(DynamicTable).SetTableName(tableName)
}
dest := make([]interface{}, len(cols))
custScan := make([]CustomScanner, 0)

View File

@@ -5,6 +5,9 @@
coveralls_testflags="-v -covermode=count -coverprofile=coverage.out"
echo "Running unit tests"
ginkgo -r -race -randomizeAllSpecs -keepGoing -- -test.run TestGorp
echo "Testing against mysql"
export GORP_TEST_DSN=gorptest/gorptest/gorptest
export GORP_TEST_DIALECT=mysql

View File

@@ -12,6 +12,7 @@
package gorp
import (
"context"
"database/sql"
"time"
)
@@ -62,7 +63,16 @@ func (t *Transaction) Exec(query string, args ...interface{}) (sql.Result, error
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return exec(t, query, args...)
return exec(t, query, true, args...)
}
// ExecNoTimeout has the same behavior as DbMap.ExecNoTimeout(), but runs in a transaction.
func (t *Transaction) ExecNoTimeout(query string, args ...interface{}) (sql.Result, error) {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return exec(t, query, false, args...)
}
// SelectInt is a convenience wrapper around the gorp.SelectInt function.
@@ -176,7 +186,7 @@ func (t *Transaction) Prepare(query string) (*sql.Stmt, error) {
return t.tx.Prepare(query)
}
func (t *Transaction) queryRow(query string, args ...interface{}) *sql.Row {
func (t *Transaction) QueryRow(query string, args ...interface{}) *sql.Row {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
@@ -184,10 +194,26 @@ func (t *Transaction) queryRow(query string, args ...interface{}) *sql.Row {
return t.tx.QueryRow(query, args...)
}
func (t *Transaction) query(query string, args ...interface{}) (*sql.Rows, error) {
func (t *Transaction) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return t.tx.QueryRowContext(ctx, query, args...)
}
func (t *Transaction) Query(query string, args ...interface{}) (*sql.Rows, error) {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return t.tx.Query(query, args...)
}
func (t *Transaction) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return t.tx.QueryContext(ctx, query, args...)
}