Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggested changes to implement issues #157 and #158 #159

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,13 @@ dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
// SetKeys(true) means we have a auto increment primary key, which
// will get automatically bound to your struct post-insert
//
t1 := dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id")
t2 := dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id")
t1 := dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id")
t2 := dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id")
t3 := dbmap.AddTableWithName(Product{}, "product_test").SetKeys(true, "Id")

// SetForeignKey will declare that Invoice.PersonId is a foreign key for
// Person.Id, and delete/update actions are to be treated as specified.
t2.ColMap("PersonId").SetForeignKey(gorp.NewForeignKey("Person", "Id").OnDelete(gorp.Restrict).OnUpdate(gorp.Cascade))
```

### Struct Embedding ###
Expand Down Expand Up @@ -450,6 +454,15 @@ func InsertInv(dbmap *DbMap, inv *Invoice, per *Person) error {
}
```

### Foreign Keys ###

You can define the foreign-key relationships when you create the table schema.
The `ColumnMap` has a `SetForeignKey` method to do this, shown in Examples above.

Gorp only uses this when `CreateTables` or `CreateTablesIfNotExists` are invoked.
Thereafter, Gorp plays no further part because the database itself enforces the
consistency of keys, returning an error any time that a constraint would be violated.

### Hooks ###

Use hooks to update data before/after saving to the db. Good for timestamps:
Expand Down Expand Up @@ -644,3 +657,4 @@ Thanks!
* matthias-margush - column aliasing via tags
* Rob Figueiredo - @robfig
* Quinn Slack - @sqs
* Rick Beton - @rickb777 - foreign keys
280 changes: 280 additions & 0 deletions dbmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
// 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 (
"database/sql"
"errors"
"fmt"
"reflect"
)

// 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 = ""
}

// 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) {
err := m.initialise()
if err != nil {
return nil, err
}
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
}

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
}

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 {
err := m.initialise()
if err != nil {
panic(err)
}
m.initialise()
m.trace(query, args...)
return m.Db.QueryRow(query, args...)
}

func (m *DbMap) Query(query string, args ...interface{}) (*sql.Rows, error) {
err := m.initialise()
if err != nil {
return nil, err
}
m.trace(query, args...)
return m.Db.Query(query, args...)
}

func (m *DbMap) trace(query string, args ...interface{}) {
if m.logger != nil {
m.logger.Printf("%s%s %v", m.logPrefix, query, args)
}
}

func (m *DbMap) initialise() (err error) {
if !m.initialised {
m.initialised = true
if m.Dialect.InitString() != "" {
m.trace(m.Dialect.InitString())
_, err = m.Db.Exec(m.Dialect.InitString())
}
}
return
}

Loading