// 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/coopernurse/gorp // package gorp import ( "bytes" "database/sql" "database/sql/driver" "errors" "fmt" "reflect" "regexp" "strings" "time" "log" "os" ) // Oracle String (empty string is null) type OracleString struct { sql.NullString } // Scan implements the Scanner interface. func (os *OracleString) Scan(value interface{}) error { if value == nil { os.String, os.Valid = "", false return nil } os.Valid = true return os.NullString.Scan(value) } // Value implements the driver Valuer interface. func (os OracleString) Value() (driver.Value, error) { if !os.Valid || os.String == "" { return nil, nil } return os.String, nil } // A nullable Time value type NullTime struct { Time time.Time Valid bool // Valid is true if Time is not NULL } // Scan implements the Scanner interface. func (nt *NullTime) Scan(value interface{}) error { nt.Time, nt.Valid = value.(time.Time) return nil } // Value implements the driver Valuer interface. func (nt NullTime) Value() (driver.Value, error) { if !nt.Valid { return nil, nil } return nt.Time, nil } var zeroVal reflect.Value var versFieldConst = "[gorp_ver_field]" // OptimisticLockError is returned by Update() or Delete() if the // struct being modified has a Version field and the value is not equal to // the current value in the database type OptimisticLockError struct { // Table name where the lock error occurred TableName string // Primary key values of the row being updated/deleted Keys []interface{} // true if a row was found with those keys, indicating the // LocalVersion is stale. false if no value was found with those // keys, suggesting the row has been deleted since loaded, or // was never inserted to begin with RowExists bool // Version value on the struct passed to Update/Delete. This value is // out of sync with the database. LocalVersion int64 } // Error returns a description of the cause of the lock error func (e OptimisticLockError) Error() string { if e.RowExists { return fmt.Sprintf("gorp: OptimisticLockError table=%s keys=%v out of date version=%d", e.TableName, e.Keys, e.LocalVersion) } return fmt.Sprintf("gorp: OptimisticLockError no row found for table=%s keys=%v", e.TableName, e.Keys) } // The TypeConverter interface provides a way to map a value of one // type to another type when persisting to, or loading from, a database. // // Example use cases: Implement type converter to convert bool types to "y"/"n" strings, // or serialize a struct member as a JSON blob. type TypeConverter interface { // ToDb converts val to another type. Called before INSERT/UPDATE operations ToDb(val interface{}) (interface{}, error) // FromDb returns a CustomScanner appropriate for this type. This will be used // to hold values returned from SELECT queries. // // In particular the CustomScanner returned should implement a Binder // function appropriate for the Go type you wish to convert the db value to // // If bool==false, then no custom scanner will be used for this field. FromDb(target interface{}) (CustomScanner, bool) } // CustomScanner binds a database column value to a Go type type CustomScanner struct { // After a row is scanned, Holder will contain the value from the database column. // Initialize the CustomScanner with the concrete Go type you wish the database // driver to scan the raw column into. Holder interface{} // Target typically holds a pointer to the target struct field to bind the Holder // value to. Target interface{} // Binder is a custom function that converts the holder value to the target type // and sets target accordingly. This function should return error if a problem // occurs converting the holder to the target. Binder func(holder interface{}, target interface{}) error } // Bind is called automatically by gorp after Scan() func (me CustomScanner) Bind() error { return me.Binder(me.Holder, me.Target) } // DbMap is the root gorp mapping object. Create one of these for each // database schema you wish to map. Each DbMap contains a list of // mapped tables. // // Example: // // dialect := gorp.MySQLDialect{"InnoDB", "UTF8"} // dbmap := &gorp.DbMap{Db: db, Dialect: dialect} // type DbMap struct { // Db handle to use with this map Db *sql.DB // Dialect implementation to use with this map Dialect Dialect TypeConverter TypeConverter tables []*TableMap logger GorpLogger logPrefix string } // TableMap represents a mapping between a Go struct and a database table // Use dbmap.AddTable() or dbmap.AddTableWithName() to create these type TableMap struct { // Name of database table. TableName string SchemaName string gotype reflect.Type Columns []*ColumnMap keys []*ColumnMap uniqueTogether [][]string version *ColumnMap insertPlan bindPlan updatePlan bindPlan deletePlan bindPlan getPlan bindPlan dbmap *DbMap } // ResetSql removes cached insert/update/select/delete SQL strings // associated with this TableMap. Call this if you've modified // any column names or the table name itself. func (t *TableMap) ResetSql() { t.insertPlan = bindPlan{} t.updatePlan = bindPlan{} t.deletePlan = bindPlan{} t.getPlan = bindPlan{} } // SetKeys lets you specify the fields on a struct that map to primary // key columns on the table. If isAutoIncr is set, result.LastInsertId() // will be used after INSERT to bind the generated id to the Go struct. // // Automatically calls ResetSql() to ensure SQL statements are regenerated. // // Panics if isAutoIncr is true, and fieldNames length != 1 // func (t *TableMap) SetKeys(isAutoIncr bool, fieldNames ...string) *TableMap { if isAutoIncr && len(fieldNames) != 1 { panic(fmt.Sprintf( "gorp: SetKeys: fieldNames length must be 1 if key is auto-increment. (Saw %v fieldNames)", len(fieldNames))) } t.keys = make([]*ColumnMap, 0) for _, name := range fieldNames { colmap := t.ColMap(name) colmap.isPK = true colmap.isAutoIncr = isAutoIncr t.keys = append(t.keys, colmap) } t.ResetSql() return t } // SetUniqueTogether lets you specify uniqueness constraints across multiple // columns on the table. Each call adds an additional constraint for the // specified columns. // // Automatically calls ResetSql() to ensure SQL statements are regenerated. // // Panics if fieldNames length < 2. // func (t *TableMap) SetUniqueTogether(fieldNames ...string) *TableMap { if len(fieldNames) < 2 { panic(fmt.Sprintf( "gorp: SetUniqueTogether: must provide at least two fieldNames to set uniqueness constraint.")) } columns := make([]string, 0) for _, name := range fieldNames { columns = append(columns, name) } t.uniqueTogether = append(t.uniqueTogether, columns) t.ResetSql() return t } // ColMap returns the ColumnMap pointer matching the given struct field // name. It panics if the struct does not contain a field matching this // name. func (t *TableMap) ColMap(field string) *ColumnMap { col := colMapOrNil(t, field) if col == nil { e := fmt.Sprintf("No ColumnMap in table %s type %s with field %s", t.TableName, t.gotype.Name(), field) panic(e) } return col } func colMapOrNil(t *TableMap, field string) *ColumnMap { for _, col := range t.Columns { if col.fieldName == field || col.ColumnName == field { return col } } return nil } // SetVersionCol sets the column to use as the Version field. By default // the "Version" field is used. Returns the column found, or panics // if the struct does not contain a field matching this name. // // Automatically calls ResetSql() to ensure SQL statements are regenerated. func (t *TableMap) SetVersionCol(field string) *ColumnMap { c := t.ColMap(field) t.version = c t.ResetSql() return c } type bindPlan struct { query string argFields []string keyFields []string versField string autoIncrIdx int autoIncrFieldName string } func (plan bindPlan) createBindInstance(elem reflect.Value, conv TypeConverter) (bindInstance, error) { bi := bindInstance{query: plan.query, autoIncrIdx: plan.autoIncrIdx, autoIncrFieldName: plan.autoIncrFieldName, versField: plan.versField} if plan.versField != "" { bi.existingVersion = elem.FieldByName(plan.versField).Int() } var err error for i := 0; i < len(plan.argFields); i++ { k := plan.argFields[i] if k == versFieldConst { newVer := bi.existingVersion + 1 bi.args = append(bi.args, newVer) if bi.existingVersion == 0 { elem.FieldByName(plan.versField).SetInt(int64(newVer)) } } else { val := elem.FieldByName(k).Interface() if conv != nil { val, err = conv.ToDb(val) if err != nil { return bindInstance{}, err } } bi.args = append(bi.args, val) } } for i := 0; i < len(plan.keyFields); i++ { k := plan.keyFields[i] val := elem.FieldByName(k).Interface() if conv != nil { val, err = conv.ToDb(val) if err != nil { return bindInstance{}, err } } bi.keys = append(bi.keys, val) } return bi, nil } type bindInstance struct { query string args []interface{} keys []interface{} existingVersion int64 versField string autoIncrIdx int autoIncrFieldName string } func (t *TableMap) bindInsert(elem reflect.Value) (bindInstance, error) { plan := t.insertPlan if plan.query == "" { plan.autoIncrIdx = -1 s := bytes.Buffer{} s2 := bytes.Buffer{} s.WriteString(fmt.Sprintf("insert into %s (", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName))) x := 0 first := true for y := range t.Columns { col := t.Columns[y] if !(col.isAutoIncr && t.dbmap.Dialect.AutoIncrBindValue() == "") { if !col.Transient { if !first { s.WriteString(",") s2.WriteString(",") } s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) if col.isAutoIncr { s2.WriteString(t.dbmap.Dialect.AutoIncrBindValue()) plan.autoIncrIdx = y plan.autoIncrFieldName = col.fieldName } else { s2.WriteString(t.dbmap.Dialect.BindVar(x)) if col == t.version { plan.versField = col.fieldName plan.argFields = append(plan.argFields, versFieldConst) } else { plan.argFields = append(plan.argFields, col.fieldName) } x++ } first = false } } else { plan.autoIncrIdx = y plan.autoIncrFieldName = col.fieldName } } s.WriteString(") values (") s.WriteString(s2.String()) s.WriteString(")") if plan.autoIncrIdx > -1 { s.WriteString(t.dbmap.Dialect.AutoIncrInsertSuffix(t.Columns[plan.autoIncrIdx])) } s.WriteString(t.dbmap.Dialect.QuerySuffix()) plan.query = s.String() t.insertPlan = plan } return plan.createBindInstance(elem, t.dbmap.TypeConverter) } func (t *TableMap) bindUpdate(elem reflect.Value) (bindInstance, error) { plan := t.updatePlan if plan.query == "" { s := bytes.Buffer{} s.WriteString(fmt.Sprintf("update %s set ", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName))) x := 0 for y := range t.Columns { col := t.Columns[y] if !col.isAutoIncr && !col.Transient { if x > 0 { s.WriteString(", ") } s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) s.WriteString("=") s.WriteString(t.dbmap.Dialect.BindVar(x)) if col == t.version { plan.versField = col.fieldName plan.argFields = append(plan.argFields, versFieldConst) } else { plan.argFields = append(plan.argFields, col.fieldName) } x++ } } s.WriteString(" where ") for y := range t.keys { col := t.keys[y] if y > 0 { s.WriteString(" and ") } s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) s.WriteString("=") s.WriteString(t.dbmap.Dialect.BindVar(x)) plan.argFields = append(plan.argFields, col.fieldName) plan.keyFields = append(plan.keyFields, col.fieldName) x++ } if plan.versField != "" { s.WriteString(" and ") s.WriteString(t.dbmap.Dialect.QuoteField(t.version.ColumnName)) s.WriteString("=") s.WriteString(t.dbmap.Dialect.BindVar(x)) plan.argFields = append(plan.argFields, plan.versField) } s.WriteString(t.dbmap.Dialect.QuerySuffix()) plan.query = s.String() t.updatePlan = plan } return plan.createBindInstance(elem, t.dbmap.TypeConverter) } func (t *TableMap) bindDelete(elem reflect.Value) (bindInstance, error) { plan := t.deletePlan if plan.query == "" { s := bytes.Buffer{} s.WriteString(fmt.Sprintf("delete from %s", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName))) for y := range t.Columns { col := t.Columns[y] if !col.Transient { if col == t.version { plan.versField = col.fieldName } } } s.WriteString(" where ") for x := range t.keys { k := t.keys[x] if x > 0 { s.WriteString(" and ") } s.WriteString(t.dbmap.Dialect.QuoteField(k.ColumnName)) s.WriteString("=") s.WriteString(t.dbmap.Dialect.BindVar(x)) plan.keyFields = append(plan.keyFields, k.fieldName) plan.argFields = append(plan.argFields, k.fieldName) } if plan.versField != "" { s.WriteString(" and ") s.WriteString(t.dbmap.Dialect.QuoteField(t.version.ColumnName)) s.WriteString("=") s.WriteString(t.dbmap.Dialect.BindVar(len(plan.argFields))) plan.argFields = append(plan.argFields, plan.versField) } s.WriteString(t.dbmap.Dialect.QuerySuffix()) plan.query = s.String() t.deletePlan = plan } return plan.createBindInstance(elem, t.dbmap.TypeConverter) } func (t *TableMap) bindGet() bindPlan { plan := t.getPlan if plan.query == "" { s := bytes.Buffer{} s.WriteString("select ") x := 0 for _, col := range t.Columns { if !col.Transient { if x > 0 { s.WriteString(",") } s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) plan.argFields = append(plan.argFields, col.fieldName) x++ } } s.WriteString(" from ") s.WriteString(t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)) s.WriteString(" where ") for x := range t.keys { col := t.keys[x] if x > 0 { s.WriteString(" and ") } s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) s.WriteString("=") s.WriteString(t.dbmap.Dialect.BindVar(x)) plan.keyFields = append(plan.keyFields, col.fieldName) } s.WriteString(t.dbmap.Dialect.QuerySuffix()) plan.query = s.String() t.getPlan = plan } return plan } // ColumnMap represents a mapping between a Go struct field and a single // column in a table. // Unique and MaxSize only inform the // CreateTables() function and are not used by Insert/Update/Delete/Get. type ColumnMap struct { // Column name in db table ColumnName string // If true, this column is skipped in generated SQL statements Transient bool // If true, " unique" is added to create table statements. // Not used elsewhere Unique bool // Passed to Dialect.ToSqlType() to assist in informing the // correct column type to map to in CreateTables() // Not used elsewhere MaxSize int fieldName string gotype reflect.Type isPK bool isAutoIncr bool isNotNull bool } // Rename allows you to specify the column name in the table // // Example: table.ColMap("Updated").Rename("date_updated") // func (c *ColumnMap) Rename(colname string) *ColumnMap { c.ColumnName = colname return c } // SetTransient allows you to mark the column as transient. If true // this column will be skipped when SQL statements are generated func (c *ColumnMap) SetTransient(b bool) *ColumnMap { c.Transient = b return c } // SetUnique adds "unique" to the create table statements for this // column, if b is true. func (c *ColumnMap) SetUnique(b bool) *ColumnMap { c.Unique = b return c } // SetNotNull adds "not null" to the create table statements for this // column, if nn is true. func (c *ColumnMap) SetNotNull(nn bool) *ColumnMap { c.isNotNull = nn return c } // SetMaxSize specifies the max length of values of this column. This is // passed to the dialect.ToSqlType() function, which can use the value // to alter the generated type for "create table" statements func (c *ColumnMap) SetMaxSize(size int) *ColumnMap { c.MaxSize = size return c } // Transaction represents a database transaction. // Insert/Update/Delete/Get/Exec operations will be run in the context // of that transaction. Transactions should be terminated with // a call to Commit() or Rollback() type Transaction struct { dbmap *DbMap tx *sql.Tx closed bool } // SqlExecutor exposes gorp operations that can be run from Pre/Post // hooks. This hides whether the current operation that triggered the // hook is in a transaction. // // See the DbMap function docs for each of the functions below for more // information. type SqlExecutor interface { Get(i interface{}, keys ...interface{}) (interface{}, error) Insert(list ...interface{}) error Update(list ...interface{}) (int64, error) Delete(list ...interface{}) (int64, error) Exec(query string, args ...interface{}) (sql.Result, error) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) SelectInt(query string, args ...interface{}) (int64, error) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) SelectFloat(query string, args ...interface{}) (float64, error) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) 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 } // Compile-time check that DbMap and Transaction implement the SqlExecutor // interface. var _, _ SqlExecutor = &DbMap{}, &Transaction{} type GorpLogger interface { Printf(format string, v ...interface{}) } // TraceOn turns on SQL statement logging for this DbMap. After this is // called, all SQL statements will be sent to the logger. If prefix is // a non-empty string, it will be written to the front of all logged // strings, which can aid in filtering log lines. // // Use TraceOn if you want to spy on the SQL statements that gorp // generates. // // Note that the base log.Logger type satisfies GorpLogger, but adapters can // easily be written for other logging packages (e.g., the golang-sanctioned // glog framework). func (m *DbMap) TraceOn(prefix string, logger GorpLogger) { m.logger = logger if prefix == "" { m.logPrefix = prefix } else { m.logPrefix = fmt.Sprintf("%s ", prefix) } } // TraceOff turns off tracing. It is idempotent. func (m *DbMap) TraceOff() { m.logger = nil m.logPrefix = "" } // AddTable registers the given interface type with gorp. The table name // will be given the name of the TypeOf(i). You must call this function, // or AddTableWithName, for any struct type you wish to persist with // the given DbMap. // // This operation is idempotent. If i's type is already mapped, the // existing *TableMap is returned func (m *DbMap) AddTable(i interface{}) *TableMap { return m.AddTableWithName(i, "") } // AddTableWithName has the same behavior as AddTable, but sets // table.TableName to name. func (m *DbMap) AddTableWithName(i interface{}, name string) *TableMap { return m.AddTableWithNameAndSchema(i, "", name) } // AddTableWithNameAndSchema has the same behavior as AddTable, but sets // table.TableName to name. func (m *DbMap) AddTableWithNameAndSchema(i interface{}, schema string, name string) *TableMap { t := reflect.TypeOf(i) if name == "" { name = t.Name() } // check if we have a table for this type already // if so, update the name and return the existing pointer for i := range m.tables { table := m.tables[i] if table.gotype == t { table.TableName = name return table } } tmap := &TableMap{gotype: t, TableName: name, SchemaName: schema, dbmap: m} tmap.Columns, tmap.version = m.readStructColumns(t) m.tables = append(m.tables, tmap) return tmap } func (m *DbMap) readStructColumns(t reflect.Type) (cols []*ColumnMap, version *ColumnMap) { n := t.NumField() for i := 0; i < n; i++ { f := t.Field(i) if f.Anonymous && f.Type.Kind() == reflect.Struct { // Recursively add nested fields in embedded structs. subcols, subversion := m.readStructColumns(f.Type) // Don't append nested fields that have the same field // name as an already-mapped field. for _, subcol := range subcols { shouldAppend := true for _, col := range cols { if !subcol.Transient && subcol.fieldName == col.fieldName { shouldAppend = false break } } if shouldAppend { cols = append(cols, subcol) } } if subversion != nil { version = subversion } } else { columnName := f.Tag.Get("db") if columnName == "" { columnName = f.Name } gotype := f.Type if m.TypeConverter != nil { // Make a new pointer to a value of type gotype and // pass it to the TypeConverter's FromDb method to see // if a different type should be used for the column // type during table creation. value := reflect.New(gotype).Interface() scanner, useHolder := m.TypeConverter.FromDb(value) if useHolder { gotype = reflect.TypeOf(scanner.Holder) } } cm := &ColumnMap{ ColumnName: columnName, Transient: columnName == "-", fieldName: f.Name, gotype: gotype, } // Check for nested fields of the same field name and // override them. shouldAppend := true for index, col := range cols { if !col.Transient && col.fieldName == cm.fieldName { cols[index] = cm shouldAppend = false break } } if shouldAppend { cols = append(cols, cm) } if cm.fieldName == "Version" { log.New(os.Stderr, "", log.LstdFlags).Println("Warning: Automatic mapping of Version struct members to version columns (see optimistic locking) will be deprecated in next version (V2) See: https://github.com/go-gorp/gorp/pull/214") version = cm } } } return } // CreateTables iterates through TableMaps registered to this DbMap and // executes "create table" statements against the database for each. // // This is particularly useful in unit tests where you want to create // and destroy the schema automatically. func (m *DbMap) CreateTables() error { return m.createTables(false) } // CreateTablesIfNotExists is similar to CreateTables, but starts // each statement with "create table if not exists" so that existing // tables do not raise errors func (m *DbMap) CreateTablesIfNotExists() error { return m.createTables(true) } func (m *DbMap) createTables(ifNotExists bool) error { var err error for i := range m.tables { table := m.tables[i] s := bytes.Buffer{} if strings.TrimSpace(table.SchemaName) != "" { schemaCreate := "create schema" if ifNotExists { s.WriteString(m.Dialect.IfSchemaNotExists(schemaCreate, table.SchemaName)) } else { s.WriteString(schemaCreate) } s.WriteString(fmt.Sprintf(" %s;", table.SchemaName)) } tableCreate := "create table" if ifNotExists { s.WriteString(m.Dialect.IfTableNotExists(tableCreate, table.SchemaName, table.TableName)) } else { s.WriteString(tableCreate) } s.WriteString(fmt.Sprintf(" %s (", m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName))) x := 0 for _, col := range table.Columns { if !col.Transient { if x > 0 { s.WriteString(", ") } stype := m.Dialect.ToSqlType(col.gotype, col.MaxSize, col.isAutoIncr) s.WriteString(fmt.Sprintf("%s %s", m.Dialect.QuoteField(col.ColumnName), stype)) if col.isPK || col.isNotNull { s.WriteString(" not null") } if col.isPK && len(table.keys) == 1 { s.WriteString(" primary key") } if col.Unique { s.WriteString(" unique") } if col.isAutoIncr { s.WriteString(fmt.Sprintf(" %s", m.Dialect.AutoIncrStr())) } x++ } } if len(table.keys) > 1 { s.WriteString(", primary key (") for x := range table.keys { if x > 0 { s.WriteString(", ") } s.WriteString(m.Dialect.QuoteField(table.keys[x].ColumnName)) } s.WriteString(")") } if len(table.uniqueTogether) > 0 { for _, columns := range table.uniqueTogether { s.WriteString(", unique (") for i, column := range columns { if i > 0 { s.WriteString(", ") } s.WriteString(m.Dialect.QuoteField(column)) } s.WriteString(")") } } s.WriteString(") ") s.WriteString(m.Dialect.CreateTableSuffix()) s.WriteString(m.Dialect.QuerySuffix()) _, err = m.Exec(s.String()) if err != nil { break } } return err } // DropTable drops an individual table. Will throw an error // if the table does not exist. func (m *DbMap) DropTable(table interface{}) error { t := reflect.TypeOf(table) return m.dropTable(t, false) } // DropTable drops an individual table. Will NOT throw an error // if the table does not exist. func (m *DbMap) DropTableIfExists(table interface{}) error { t := reflect.TypeOf(table) return m.dropTable(t, true) } // DropTables iterates through TableMaps registered to this DbMap and // executes "drop table" statements against the database for each. func (m *DbMap) DropTables() error { return m.dropTables(false) } // DropTablesIfExists is the same as DropTables, but uses the "if exists" clause to // avoid errors for tables that do not exist. func (m *DbMap) DropTablesIfExists() error { return m.dropTables(true) } // Goes through all the registered tables, dropping them one by one. // If an error is encountered, then it is returned and the rest of // the tables are not dropped. func (m *DbMap) dropTables(addIfExists bool) (err error) { for _, table := range m.tables { err = m.dropTableImpl(table, addIfExists) if err != nil { return } } return err } // Implementation of dropping a single table. func (m *DbMap) dropTable(t reflect.Type, addIfExists bool) error { table := tableOrNil(m, t) if table == nil { return errors.New(fmt.Sprintf("table %s was not registered!", table.TableName)) } return m.dropTableImpl(table, addIfExists) } func (m *DbMap) dropTableImpl(table *TableMap, ifExists bool) (err error) { tableDrop := "drop table" 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))) return err } // TruncateTables iterates through TableMaps registered to this DbMap and // executes "truncate table" statements against the database for each, or in the case of // sqlite, a "delete from" with no "where" clause, which uses the truncate optimization // (http://www.sqlite.org/lang_delete.html) 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))) if e != nil { err = e } } return err } // Insert runs a SQL INSERT statement for each element in list. List // items must be pointers. // // Any interface whose TableMap has an auto-increment primary key will // have its last insert id bound to the PK field on the struct. // // The hook functions PreInsert() and/or PostInsert() will be executed // before/after the INSERT statement if the interface defines them. // // Panics if any interface in the list has not been registered with AddTable func (m *DbMap) Insert(list ...interface{}) error { return insert(m, m, list...) } // Update runs a SQL UPDATE statement for each element in list. List // items must be pointers. // // The hook functions PreUpdate() and/or PostUpdate() will be executed // before/after the UPDATE statement if the interface defines them. // // Returns the number of rows updated. // // Returns an error if SetKeys has not been called on the TableMap // Panics if any interface in the list has not been registered with AddTable func (m *DbMap) Update(list ...interface{}) (int64, error) { return update(m, m, list...) } // Delete runs a SQL DELETE statement for each element in list. List // items must be pointers. // // The hook functions PreDelete() and/or PostDelete() will be executed // before/after the DELETE statement if the interface defines them. // // Returns the number of rows deleted. // // Returns an error if SetKeys has not been called on the TableMap // Panics if any interface in the list has not been registered with AddTable func (m *DbMap) Delete(list ...interface{}) (int64, error) { return delete(m, m, list...) } // Get runs a SQL SELECT to fetch a single row from the table based on the // primary key(s) // // i should be an empty value for the struct to load. keys should be // the primary key value(s) for the row to load. If multiple keys // exist on the table, the order should match the column order // specified in SetKeys() when the table mapping was defined. // // The hook function PostGet() will be executed after the SELECT // statement if the interface defines them. // // Returns a pointer to a struct that matches or nil if no row is found. // // Returns an error if SetKeys has not been called on the TableMap // Panics if any interface in the list has not been registered with AddTable func (m *DbMap) Get(i interface{}, keys ...interface{}) (interface{}, error) { return get(m, m, i, keys...) } // Select runs an arbitrary SQL query, binding the columns in the result // to fields on the struct specified by i. args represent the bind // parameters for the SQL statement. // // Column names on the SELECT statement should be aliased to the field names // on the struct i. Returns an error if one or more columns in the result // do not match. It is OK if fields on i are not part of the SQL // statement. // // The hook function PostGet() will be executed after the SELECT // statement if the interface defines them. // // Values are returned in one of two ways: // 1. If i is a struct or a pointer to a struct, returns a slice of pointers to // matching rows of type i. // 2. If i is a pointer to a slice, the results will be appended to that slice // and nil returned. // // i does NOT need to be registered with AddTable() func (m *DbMap) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) { return hookedselect(m, m, i, query, args...) } // Exec runs an arbitrary SQL statement. args represent the bind parameters. // This is equivalent to running: Exec() using database/sql func (m *DbMap) Exec(query string, args ...interface{}) (sql.Result, error) { m.trace(query, args...) return m.Db.Exec(query, args...) } // SelectInt is a convenience wrapper around the gorp.SelectInt function func (m *DbMap) SelectInt(query string, args ...interface{}) (int64, error) { return SelectInt(m, query, args...) } // SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function func (m *DbMap) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) { return SelectNullInt(m, query, args...) } // SelectFloat is a convenience wrapper around the gorp.SelectFlot function func (m *DbMap) SelectFloat(query string, args ...interface{}) (float64, error) { return SelectFloat(m, query, args...) } // SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function func (m *DbMap) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) { return SelectNullFloat(m, query, args...) } // SelectStr is a convenience wrapper around the gorp.SelectStr function func (m *DbMap) SelectStr(query string, args ...interface{}) (string, error) { return SelectStr(m, query, args...) } // SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function func (m *DbMap) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) { return SelectNullStr(m, query, args...) } // SelectOne is a convenience wrapper around the gorp.SelectOne function func (m *DbMap) SelectOne(holder interface{}, query string, args ...interface{}) error { return SelectOne(m, m, holder, query, args...) } // Begin starts a gorp Transaction func (m *DbMap) Begin() (*Transaction, error) { m.trace("begin;") tx, err := m.Db.Begin() if err != nil { return nil, err } return &Transaction{m, tx, false}, nil } // TableFor returns the *TableMap corresponding to the given Go Type // 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) if table == nil { return nil, errors.New(fmt.Sprintf("No table found for type: %v", t.Name())) } 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 func (m *DbMap) Prepare(query string) (*sql.Stmt, error) { m.trace(query, nil) return m.Db.Prepare(query) } func tableOrNil(m *DbMap, t reflect.Type) *TableMap { for i := range m.tables { table := m.tables[i] if table.gotype == t { return table } } return nil } func (m *DbMap) tableForPointer(ptr interface{}, checkPK bool) (*TableMap, reflect.Value, error) { ptrv := reflect.ValueOf(ptr) if ptrv.Kind() != reflect.Ptr { e := fmt.Sprintf("gorp: passed non-pointer: %v (kind=%v)", ptr, ptrv.Kind()) return nil, reflect.Value{}, errors.New(e) } elem := ptrv.Elem() etype := reflect.TypeOf(elem.Interface()) t, err := m.TableFor(etype, checkPK) if err != nil { return nil, reflect.Value{}, err } return t, elem, nil } func (m *DbMap) queryRow(query string, args ...interface{}) *sql.Row { m.trace(query, args...) return m.Db.QueryRow(query, args...) } func (m *DbMap) query(query string, args ...interface{}) (*sql.Rows, error) { m.trace(query, args...) return m.Db.Query(query, args...) } func (m *DbMap) trace(query string, args ...interface{}) { if m.logger != nil { var margs = argsString(args...) m.logger.Printf("%s%s [%s]", m.logPrefix, query, margs) } } func argsString(args ...interface{}) string { var margs string for i, a := range args { var v interface{} = a if x, ok := v.(driver.Valuer); ok { y, err := x.Value() if err == nil { v = y } } switch v.(type) { case string: v = fmt.Sprintf("%q", v) default: v = fmt.Sprintf("%v", v) } margs += fmt.Sprintf("%d:%s", i+1, v) if i+1 < len(args) { margs += " " } } return margs } /////////////// // Insert has the same behavior as DbMap.Insert(), but runs in a transaction. func (t *Transaction) Insert(list ...interface{}) error { return insert(t.dbmap, t, list...) } // Update had the same behavior as DbMap.Update(), but runs in a transaction. func (t *Transaction) Update(list ...interface{}) (int64, error) { return update(t.dbmap, t, list...) } // Delete has the same behavior as DbMap.Delete(), but runs in a transaction. func (t *Transaction) Delete(list ...interface{}) (int64, error) { return delete(t.dbmap, t, list...) } // Get has the same behavior as DbMap.Get(), but runs in a transaction. func (t *Transaction) Get(i interface{}, keys ...interface{}) (interface{}, error) { return get(t.dbmap, t, i, keys...) } // Select has the same behavior as DbMap.Select(), but runs in a transaction. func (t *Transaction) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) { return hookedselect(t.dbmap, t, i, query, args...) } // Exec has the same behavior as DbMap.Exec(), but runs in a transaction. func (t *Transaction) Exec(query string, args ...interface{}) (sql.Result, error) { t.dbmap.trace(query, args...) return t.tx.Exec(query, args...) } // SelectInt is a convenience wrapper around the gorp.SelectInt function. func (t *Transaction) SelectInt(query string, args ...interface{}) (int64, error) { return SelectInt(t, query, args...) } // SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function. func (t *Transaction) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) { return SelectNullInt(t, query, args...) } // SelectFloat is a convenience wrapper around the gorp.SelectFloat function. func (t *Transaction) SelectFloat(query string, args ...interface{}) (float64, error) { return SelectFloat(t, query, args...) } // SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function. func (t *Transaction) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) { return SelectNullFloat(t, query, args...) } // SelectStr is a convenience wrapper around the gorp.SelectStr function. func (t *Transaction) SelectStr(query string, args ...interface{}) (string, error) { return SelectStr(t, query, args...) } // SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function. func (t *Transaction) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) { return SelectNullStr(t, query, args...) } // SelectOne is a convenience wrapper around the gorp.SelectOne function. func (t *Transaction) SelectOne(holder interface{}, query string, args ...interface{}) error { return SelectOne(t.dbmap, t, holder, query, args...) } // Commit commits the underlying database transaction. func (t *Transaction) Commit() error { if !t.closed { t.closed = true t.dbmap.trace("commit;") return t.tx.Commit() } return sql.ErrTxDone } // Rollback rolls back the underlying database transaction. func (t *Transaction) Rollback() error { if !t.closed { t.closed = true t.dbmap.trace("rollback;") return t.tx.Rollback() } return sql.ErrTxDone } // Savepoint creates a savepoint with the given name. The name is interpolated // directly into the SQL SAVEPOINT statement, so you must sanitize it if it is // derived from user input. func (t *Transaction) Savepoint(name string) error { query := "savepoint " + t.dbmap.Dialect.QuoteField(name) t.dbmap.trace(query, nil) _, err := t.tx.Exec(query) return err } // RollbackToSavepoint rolls back to the savepoint with the given name. The // name is interpolated directly into the SQL SAVEPOINT statement, so you must // sanitize it if it is derived from user input. func (t *Transaction) RollbackToSavepoint(savepoint string) error { query := "rollback to savepoint " + t.dbmap.Dialect.QuoteField(savepoint) t.dbmap.trace(query, nil) _, err := t.tx.Exec(query) return err } // ReleaseSavepint releases the savepoint with the given name. The name is // interpolated directly into the SQL SAVEPOINT statement, so you must sanitize // it if it is derived from user input. func (t *Transaction) ReleaseSavepoint(savepoint string) error { query := "release savepoint " + t.dbmap.Dialect.QuoteField(savepoint) t.dbmap.trace(query, nil) _, err := t.tx.Exec(query) return err } // Prepare has the same behavior as DbMap.Prepare(), but runs in a transaction. func (t *Transaction) Prepare(query string) (*sql.Stmt, error) { t.dbmap.trace(query, nil) return t.tx.Prepare(query) } func (t *Transaction) queryRow(query string, args ...interface{}) *sql.Row { t.dbmap.trace(query, args...) return t.tx.QueryRow(query, args...) } func (t *Transaction) query(query string, args ...interface{}) (*sql.Rows, error) { t.dbmap.trace(query, args...) return t.tx.Query(query, args...) } /////////////// // SelectInt executes the given query, which should be a SELECT statement for a single // integer column, and returns the value of the first row returned. If no rows are // found, zero is returned. func SelectInt(e SqlExecutor, query string, args ...interface{}) (int64, error) { var h int64 err := selectVal(e, &h, query, args...) if err != nil && err != sql.ErrNoRows { return 0, err } return h, nil } // SelectNullInt executes the given query, which should be a SELECT statement for a single // integer column, and returns the value of the first row returned. If no rows are // found, the empty sql.NullInt64 value is returned. func SelectNullInt(e SqlExecutor, query string, args ...interface{}) (sql.NullInt64, error) { var h sql.NullInt64 err := selectVal(e, &h, query, args...) if err != nil && err != sql.ErrNoRows { return h, err } return h, nil } // SelectFloat executes the given query, which should be a SELECT statement for a single // float column, and returns the value of the first row returned. If no rows are // found, zero is returned. func SelectFloat(e SqlExecutor, query string, args ...interface{}) (float64, error) { var h float64 err := selectVal(e, &h, query, args...) if err != nil && err != sql.ErrNoRows { return 0, err } return h, nil } // SelectNullFloat executes the given query, which should be a SELECT statement for a single // float column, and returns the value of the first row returned. If no rows are // found, the empty sql.NullInt64 value is returned. func SelectNullFloat(e SqlExecutor, query string, args ...interface{}) (sql.NullFloat64, error) { var h sql.NullFloat64 err := selectVal(e, &h, query, args...) if err != nil && err != sql.ErrNoRows { return h, err } return h, nil } // SelectStr executes the given query, which should be a SELECT statement for a single // char/varchar column, and returns the value of the first row returned. If no rows are // found, an empty string is returned. func SelectStr(e SqlExecutor, query string, args ...interface{}) (string, error) { var h string err := selectVal(e, &h, query, args...) if err != nil && err != sql.ErrNoRows { return "", err } return h, nil } // SelectNullStr executes the given query, which should be a SELECT // statement for a single char/varchar column, and returns the value // of the first row returned. If no rows are found, the empty // sql.NullString is returned. func SelectNullStr(e SqlExecutor, query string, args ...interface{}) (sql.NullString, error) { var h sql.NullString err := selectVal(e, &h, query, args...) if err != nil && err != sql.ErrNoRows { return h, err } return h, nil } // SelectOne executes the given query (which should be a SELECT statement) // and binds the result to holder, which must be a pointer. // // If no row is found, an error (sql.ErrNoRows specifically) will be returned // // If more than one row is found, an error will be returned. // func SelectOne(m *DbMap, e SqlExecutor, holder interface{}, query string, args ...interface{}) error { t := reflect.TypeOf(holder) if t.Kind() == reflect.Ptr { t = t.Elem() } else { return fmt.Errorf("gorp: SelectOne holder must be a pointer, but got: %t", holder) } // Handle pointer to pointer isptr := false if t.Kind() == reflect.Ptr { isptr = true t = t.Elem() } if t.Kind() == reflect.Struct { var nonFatalErr error list, err := hookedselect(m, e, holder, query, args...) if err != nil { if !NonFatalError(err) { return err } nonFatalErr = err } dest := reflect.ValueOf(holder) if isptr { dest = dest.Elem() } if list != nil && len(list) > 0 { // check for multiple rows if len(list) > 1 { return fmt.Errorf("gorp: multiple rows returned for: %s - %v", query, args) } // Initialize if nil if dest.IsNil() { dest.Set(reflect.New(t)) } // only one row found src := reflect.ValueOf(list[0]) dest.Elem().Set(src.Elem()) } else { // No rows found, return a proper error. return sql.ErrNoRows } return nonFatalErr } return selectVal(e, holder, query, 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) } } rows, err := e.query(query, args...) if err != nil { return err } defer rows.Close() if !rows.Next() { return sql.ErrNoRows } return rows.Scan(holder) } /////////////// 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 if t, _ := toSliceType(i); t == nil { for _, v := range list { if v, ok := v.(HasPostGet); ok { err := v.PostGet(exec) if err != nil { return nil, err } } } } else { resultsValue := reflect.Indirect(reflect.ValueOf(i)) for i := 0; i < resultsValue.Len(); i++ { if v, ok := resultsValue.Index(i).Interface().(HasPostGet); ok { err := v.PostGet(exec) if err != nil { return nil, err } } } } return list, nonFatalErr } func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string, args ...interface{}) ([]interface{}, error) { var ( appendToSlice = false // Write results to i directly? intoStruct = true // Selecting into a struct? pointerElements = true // Are the slice elements pointers (vs values)? ) var nonFatalErr error // get type for i, verifying it's a supported destination t, err := toType(i) if err != nil { var err2 error if t, err2 = toSliceType(i); t == nil { if err2 != nil { return nil, err2 } return nil, err } pointerElements = t.Kind() == reflect.Ptr if pointerElements { t = t.Elem() } appendToSlice = true intoStruct = t.Kind() == reflect.Struct } // If the caller supplied a single struct/map argument, assume a "named // parameter" query. Extract the named arguments from the struct/map, create // the flat arg slice, and rewrite the query to use the dialect's placeholder. if len(args) == 1 { query, args = maybeExpandNamedQuery(m, query, args) } // Run the query rows, err := exec.query(query, args...) if err != nil { return nil, err } defer rows.Close() // Fetch the column names as returned from db cols, err := rows.Columns() if err != nil { return nil, err } if !intoStruct && len(cols) > 1 { return nil, fmt.Errorf("gorp: select into non-struct slice requires 1 column, got %d", len(cols)) } var colToFieldIndex [][]int if intoStruct { if colToFieldIndex, err = columnToFieldIndex(m, t, cols); err != nil { if !NonFatalError(err) { return nil, err } nonFatalErr = err } } conv := m.TypeConverter // Add results to one of these two slices. var ( list = make([]interface{}, 0) sliceValue = reflect.Indirect(reflect.ValueOf(i)) ) for { if !rows.Next() { // if error occured return rawselect if rows.Err() != nil { return nil, rows.Err() } // time to exit from outer "for" loop break } v := reflect.New(t) dest := make([]interface{}, len(cols)) custScan := make([]CustomScanner, 0) for x := range cols { f := v.Elem() if intoStruct { index := colToFieldIndex[x] if index == nil { // this field is not present in the struct, so create a dummy // value for rows.Scan to scan into var dummy sql.RawBytes dest[x] = &dummy continue } f = f.FieldByIndex(index) } target := f.Addr().Interface() if conv != nil { scanner, ok := conv.FromDb(target) if ok { target = scanner.Holder custScan = append(custScan, scanner) } } dest[x] = target } err = rows.Scan(dest...) if err != nil { return nil, err } for _, c := range custScan { err = c.Bind() if err != nil { return nil, err } } if appendToSlice { if !pointerElements { v = v.Elem() } sliceValue.Set(reflect.Append(sliceValue, v)) } else { list = append(list, v.Interface()) } } if appendToSlice && sliceValue.IsNil() { sliceValue.Set(reflect.MakeSlice(sliceValue.Type(), 0, 0)) } return list, nonFatalErr } // maybeExpandNamedQuery checks the given arg to see if it's eligible to be used // as input to a named query. If so, it rewrites the query to use // dialect-dependent bindvars and instantiates the corresponding slice of // parameters by extracting data from the map / struct. // If not, returns the input values unchanged. func maybeExpandNamedQuery(m *DbMap, query string, args []interface{}) (string, []interface{}) { arg := reflect.ValueOf(args[0]) for arg.Kind() == reflect.Ptr { arg = arg.Elem() } switch { case arg.Kind() == reflect.Map && arg.Type().Key().Kind() == reflect.String: return expandNamedQuery(m, query, func(key string) reflect.Value { return arg.MapIndex(reflect.ValueOf(key)) }) // #84 - ignore time.Time structs here - there may be a cleaner way to do this case arg.Kind() == reflect.Struct && !(arg.Type().PkgPath() == "time" && arg.Type().Name() == "Time"): return expandNamedQuery(m, query, arg.FieldByName) } return query, args } var keyRegexp = regexp.MustCompile(`:[[:word:]]+`) // expandNamedQuery accepts a query with placeholders of the form ":key", and a // single arg of Kind Struct or Map[string]. It returns the query with the // dialect's placeholders, and a slice of args ready for positional insertion // into the query. func expandNamedQuery(m *DbMap, query string, keyGetter func(key string) reflect.Value) (string, []interface{}) { var ( n int args []interface{} ) return keyRegexp.ReplaceAllStringFunc(query, func(key string) string { val := keyGetter(key[1:]) if !val.IsValid() { return key } args = append(args, val.Interface()) newVar := m.Dialect.BindVar(n) n++ return newVar }), args } func columnToFieldIndex(m *DbMap, t reflect.Type, 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) if table != nil { tableMapped = true } // Loop over column names and find field in i to bind to // based on column name. all returned columns must match // a field in the i struct missingColNames := []string{} for x := range cols { colName := strings.ToLower(cols[x]) field, found := t.FieldByNameFunc(func(fieldName string) bool { field, _ := t.FieldByName(fieldName) fieldName = field.Tag.Get("db") if fieldName == "-" { return false } else if fieldName == "" { fieldName = field.Name } if tableMapped { colMap := colMapOrNil(table, fieldName) if colMap != nil { fieldName = colMap.ColumnName } } return colName == strings.ToLower(fieldName) }) if found { colToFieldIndex[x] = field.Index } if colToFieldIndex[x] == nil { missingColNames = append(missingColNames, colName) } } if len(missingColNames) > 0 { return colToFieldIndex, &NoFieldInTypeError{ TypeName: t.Name(), MissingColNames: missingColNames, } } return colToFieldIndex, nil } func fieldByName(val reflect.Value, fieldName string) *reflect.Value { // try to find field by exact match f := val.FieldByName(fieldName) if f != zeroVal { return &f } // try to find by case insensitive match - only the Postgres driver // seems to require this - in the case where columns are aliased in the sql fieldNameL := strings.ToLower(fieldName) fieldCount := val.NumField() t := val.Type() for i := 0; i < fieldCount; i++ { sf := t.Field(i) if strings.ToLower(sf.Name) == fieldNameL { f := val.Field(i) return &f } } return nil } // toSliceType returns the element type of the given object, if the object is a // "*[]*Element" or "*[]Element". If not, returns nil. // err is returned if the user was trying to pass a pointer-to-slice but failed. func toSliceType(i interface{}) (reflect.Type, error) { t := reflect.TypeOf(i) if t.Kind() != reflect.Ptr { // If it's a slice, return a more helpful error message if t.Kind() == reflect.Slice { return nil, fmt.Errorf("gorp: Cannot SELECT into a non-pointer slice: %v", t) } return nil, nil } if t = t.Elem(); t.Kind() != reflect.Slice { return nil, nil } return t.Elem(), nil } func toType(i interface{}) (reflect.Type, error) { t := reflect.TypeOf(i) // If a Pointer to a type, follow for t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() != reflect.Struct { return nil, fmt.Errorf("gorp: Cannot SELECT into this type: %v", reflect.TypeOf(i)) } return t, nil } func get(m *DbMap, exec SqlExecutor, i interface{}, keys ...interface{}) (interface{}, error) { t, err := toType(i) if err != nil { return nil, err } table, err := m.TableFor(t, true) if err != nil { return nil, err } plan := table.bindGet() v := reflect.New(t) dest := make([]interface{}, len(plan.argFields)) conv := m.TypeConverter custScan := make([]CustomScanner, 0) for x, fieldName := range plan.argFields { f := v.Elem().FieldByName(fieldName) target := f.Addr().Interface() if conv != nil { scanner, ok := conv.FromDb(target) if ok { target = scanner.Holder custScan = append(custScan, scanner) } } dest[x] = target } row := exec.queryRow(plan.query, keys...) err = row.Scan(dest...) if err != nil { if err == sql.ErrNoRows { err = nil } return nil, err } for _, c := range custScan { err = c.Bind() if err != nil { return nil, err } } if v, ok := v.Interface().(HasPostGet); ok { err := v.PostGet(exec) if err != nil { return nil, err } } return v.Interface(), nil } func delete(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) { count := int64(0) for _, ptr := range list { table, elem, err := m.tableForPointer(ptr, true) if err != nil { return -1, err } eval := elem.Addr().Interface() if v, ok := eval.(HasPreDelete); ok { err = v.PreDelete(exec) if err != nil { return -1, err } } bi, err := table.bindDelete(elem) if err != nil { return -1, err } res, err := exec.Exec(bi.query, bi.args...) if err != nil { return -1, err } rows, err := res.RowsAffected() if err != nil { return -1, err } if rows == 0 && bi.existingVersion > 0 { return lockError(m, exec, table.TableName, bi.existingVersion, elem, bi.keys...) } count += rows if v, ok := eval.(HasPostDelete); ok { err := v.PostDelete(exec) if err != nil { return -1, err } } } return count, nil } func update(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) { count := int64(0) for _, ptr := range list { table, elem, err := m.tableForPointer(ptr, true) if err != nil { return -1, err } eval := elem.Addr().Interface() if v, ok := eval.(HasPreUpdate); ok { err = v.PreUpdate(exec) if err != nil { return -1, err } } bi, err := table.bindUpdate(elem) if err != nil { return -1, err } res, err := exec.Exec(bi.query, bi.args...) if err != nil { return -1, err } rows, err := res.RowsAffected() if err != nil { return -1, err } if rows == 0 && bi.existingVersion > 0 { return lockError(m, exec, table.TableName, bi.existingVersion, elem, bi.keys...) } if bi.versField != "" { elem.FieldByName(bi.versField).SetInt(bi.existingVersion + 1) } count += rows if v, ok := eval.(HasPostUpdate); ok { err = v.PostUpdate(exec) if err != nil { return -1, err } } } return count, nil } func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error { for _, ptr := range list { table, elem, err := m.tableForPointer(ptr, false) if err != nil { return err } eval := elem.Addr().Interface() if v, ok := eval.(HasPreInsert); ok { err := v.PreInsert(exec) if err != nil { return err } } bi, err := table.bindInsert(elem) if err != nil { return err } if bi.autoIncrIdx > -1 { f := elem.FieldByName(bi.autoIncrFieldName) switch inserter := m.Dialect.(type) { case IntegerAutoIncrInserter: id, err := inserter.InsertAutoIncr(exec, bi.query, bi.args...) if err != nil { return err } k := f.Kind() if (k == reflect.Int) || (k == reflect.Int16) || (k == reflect.Int32) || (k == reflect.Int64) { f.SetInt(id) } else if (k == reflect.Uint) || (k == reflect.Uint16) || (k == reflect.Uint32) || (k == reflect.Uint64) { f.SetUint(uint64(id)) } else { return fmt.Errorf("gorp: Cannot set autoincrement value on non-Int field. SQL=%s autoIncrIdx=%d autoIncrFieldName=%s", bi.query, bi.autoIncrIdx, bi.autoIncrFieldName) } case TargetedAutoIncrInserter: err := inserter.InsertAutoIncrToTarget(exec, bi.query, f.Addr().Interface(), bi.args...) if err != nil { return err } default: return fmt.Errorf("gorp: Cannot use autoincrement fields on dialects that do not implement an autoincrementing interface") } } else { _, err := exec.Exec(bi.query, bi.args...) if err != nil { return err } } if v, ok := eval.(HasPostInsert); ok { err := v.PostInsert(exec) if err != nil { return err } } } return nil } func lockError(m *DbMap, exec SqlExecutor, tableName string, existingVer int64, elem reflect.Value, keys ...interface{}) (int64, error) { existing, err := get(m, exec, elem.Interface(), keys...) if err != nil { return -1, err } ole := OptimisticLockError{tableName, keys, true, existingVer} if existing == nil { ole.RowExists = false } return -1, ole } // PostUpdate() will be executed after the GET statement. type HasPostGet interface { PostGet(SqlExecutor) error } // PostUpdate() will be executed after the DELETE statement type HasPostDelete interface { PostDelete(SqlExecutor) error } // PostUpdate() will be executed after the UPDATE statement type HasPostUpdate interface { PostUpdate(SqlExecutor) error } // PostInsert() will be executed after the INSERT statement type HasPostInsert interface { PostInsert(SqlExecutor) error } // PreDelete() will be executed before the DELETE statement. type HasPreDelete interface { PreDelete(SqlExecutor) error } // PreUpdate() will be executed before UPDATE statement. type HasPreUpdate interface { PreUpdate(SqlExecutor) error } // PreInsert() will be executed before INSERT statement. type HasPreInsert interface { PreInsert(SqlExecutor) error }