reform

package module
v1.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2021 License: MIT Imports: 7 Imported by: 204

README

reform

Release PkgGoDev CI AppVeyor Build status Coverage Report Go Report Card

Reform gopher logo

A better ORM for Go and database/sql.

It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as opposed to interface{}, type system sidestepping, and runtime reflection. It will be kept simple.

Supported SQL dialects:

RDBMS Library and drivers Status
PostgreSQL github.com/lib/pq (postgres) Stable. Tested with all supported versions.
github.com/jackc/pgx/stdlib (pgx v3) Stable. Tested with all supported versions.
MySQL github.com/go-sql-driver/mysql (mysql) Stable. Tested with all supported versions.
SQLite3 github.com/mattn/go-sqlite3 (sqlite3) Stable.
Microsoft SQL Server github.com/denisenkom/go-mssqldb (sqlserver, mssql) Stable.
Tested on Windows with: SQL2008R2SP2, SQL2012SP1, SQL2014, SQL2016.
On Linux with: mcr.microsoft.com/mssql/server:2017-latest and mcr.microsoft.com/mssql/server:2019-latest Docker images.

Notes:

Quickstart

  1. Make sure you are using Go 1.13+, and Go modules support is enabled. Install or update reform package, reform and reform-db commands with:

    go get -v gopkg.in/reform.v1/...
    

    If you are not using Go modules yet, you can use dep to vendor desired version of reform, and then install commands with:

    go install -v ./vendor/gopkg.in/reform.v1/...
    

    You can also install the latest stable version of reform without using Go modules thanks to gopkg.in redirection, but please note that this will not use the stable versions of the database drivers:

    env GO111MODULE=off go get -u -v gopkg.in/reform.v1/...
    

    Canonical import path is gopkg.in/reform.v1; using github.com/go-reform/reform will not work.

    See note about versioning and branches below.

  2. Use reform-db command to generate models for your existing database schema. For example:

    reform-db -db-driver=sqlite3 -db-source=example.sqlite3 init
    
  3. Update generated models or write your own – struct representing a table or view row. For example, store this in file person.go:

    //go:generate reform
    
    //reform:people
    type Person struct {
    	ID        int32      `reform:"id,pk"`
    	Name      string     `reform:"name"`
    	Email     *string    `reform:"email"`
    	CreatedAt time.Time  `reform:"created_at"`
    	UpdatedAt *time.Time `reform:"updated_at"`
    }
    

    Magic comment //reform:people links this model to people table or view in SQL database. The first value in field's reform tag is a column name. pk marks primary key. Use value - or omit tag completely to skip a field. Use pointers (recommended) or sql.NullXXX types for nullable fields.

  4. Run reform [package or directory] or go generate [package or file]. This will create person_reform.go in the same package with type PersonTable and methods on Person.

  5. See documentation how to use it. Simple example:

    // Get *sql.DB as usual. PostgreSQL example:
    sqlDB, err := sql.Open("postgres", "postgres://127.0.0.1:5432/database")
    if err != nil {
    	log.Fatal(err)
    }
    defer sqlDB.Close()
    
    // Use new *log.Logger for logging.
    logger := log.New(os.Stderr, "SQL: ", log.Flags())
    
    // Create *reform.DB instance with simple logger.
    // Any Printf-like function (fmt.Printf, log.Printf, testing.T.Logf, etc) can be used with NewPrintfLogger.
    // Change dialect for other databases.
    db := reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(logger.Printf))
    
    // Save record (performs INSERT or UPDATE).
    person := &Person{
    	Name:  "Alexey Palazhchenko",
    	Email: pointer.ToString("alexey.palazhchenko@gmail.com"),
    }
    if err := db.Save(person); err != nil {
    	log.Fatal(err)
    }
    
    // ID is filled by Save.
    person2, err := db.FindByPrimaryKeyFrom(PersonTable, person.ID)
    if err != nil {
    	log.Fatal(err)
    }
    fmt.Println(person2.(*Person).Name)
    
    // Delete record.
    if err = db.Delete(person); err != nil {
    	log.Fatal(err)
    }
    
    // Find records by IDs.
    persons, err := db.FindAllFrom(PersonTable, "id", 1, 2)
    if err != nil {
    	log.Fatal(err)
    }
    for _, p := range persons {
    	fmt.Println(p)
    }
    

Background

reform was born during summer 2014 out of frustrations with existing Go ORMs. All of them have a method Save(record interface{}) which can be used like this:

orm.Save(User{Name: "gopher"})
orm.Save(&User{Name: "gopher"})
orm.Save(nil)
orm.Save("Batman!!")

Now you can say that last invocation is obviously invalid, and that it's not hard to make an ORM to accept both first and second versions. But there are two problems:

  1. Compiler can't check it. Method's signature in godoc will not tell us how to use it. We are essentially working against those tools by sidestepping type system.
  2. First version is still invalid, since one would expect Save() method to set record's primary key after INSERT, but this change will be lost due to passing by value.

First proprietary version of reform was used in production even before go generate announcement. This free and open-source version is the fourth milestone on the road to better and idiomatic API.

Versioning and branching policy

We are following Semantic Versioning, using gopkg.in and filling a changelog. All v1 releases are SemVer-compatible; breaking changes will not be applied.

We use tags v1.M.m for releases, branch main (default on GitHub) for the next minor release development, and release/1.M branches for patch release development. (It was more complicated before 1.4.0 release.)

Major version 2 is currently not planned.

Additional packages

Caveats and limitations

  • There should be zero pk fields for Struct and exactly one pk field for Record. Composite primary keys are not supported (#114).
  • pk field can't be a pointer (== nil doesn't work).
  • Database row can't have a Go's zero value (0, empty string, etc.) in primary key column.

License

Code is covered by standard MIT-style license. Copyright (c) 2016-2020 Alexey Palazhchenko. See LICENSE for details. Note that generated code is covered by the terms of your choice.

The reform gopher was drawn by Natalya Glebova. Please use it only as reform logo. It is based on the original design by Renée French, released under Creative Commons Attribution 3.0 USA license.

Contributing

See Contributing Guidelines.

Documentation

Overview

Package reform is a better ORM for Go, based on non-empty interfaces and code generation.

See README (https://github.com/go-reform/reform/blob/main/README.md) for quickstart information.

Context

Querier object, embedded into DB and TX types, contains context which is used by all its methods. It defaults to context.Background() and can be changed with WithContext method:

// for a single call
projects, err := DB.WithContext(ctx).SelectAllFrom(ProjectTable, "")

// for several calls
q := DB.WithContext(ctx)
projects, err := q.SelectAllFrom(ProjectTable, "")
persons, err := q.SelectAllFrom(PersonTable, "")

Methods Exec, Query, and QueryRow use the same context. Methods ExecContext, QueryContext, and QueryRowContext are just compatibility wrappers for Querier.WithContext(ctx).Exec/Query/QuyeryRow to satisfy various standard interfaces.

DB object methods Begin and InTransaction start transaction with the same context. Methods BeginTx and InTransactionContext start transaction with a given context without changing DB's context:

var projects, persons []Struct
err := DB.InTransactionContext(ctx, nil, func(tx *reform.TX) error {
    var e error

    // uses ctx
    if projects, e = tx.SelectAllFrom(ProjectTable, ""); e != nil {
        return e
    }

    // uses ctx too
    if persons, e = tx.SelectAllFrom(PersonTable, ""); e != nil {
        return e
    }

    return nil
}

Note that several different contexts can be used:

DB.InTransactionContext(ctx1, nil, func(tx *reform.TX) error {
    _, _ = tx.SelectAllFrom(PersonTable, "")                    // uses ctx1
    _, _ = tx.WithContext(ctx2).SelectAllFrom(PersonTable, "")  // uses ctx2
    ...
})

In theory, ctx1 and ctx2 can be entirely unrelated. Although that construct is occasionally useful, the behavior on context cancelation is entirely driver-defined; some drivers may just close the whole connection, effectively canceling unrelated ctx2 on ctx1 cancelation. For that reason mixing several contexts is not recommended.

Tagging

reform allows one to add tags (comments) to generated queries with WithTag Querier method. They can be used to track queries from RDBMS logs and tools back to application code. For example, this code:

id := "baron"
project, err := DB.WithTag("GetProject:%v", id).FindByPrimaryKeyFrom(ProjectTable, id)

will generate the following query:

SELECT /* GetProject:baron */ "projects"."name", "projects"."id", "projects"."start", "projects"."end" FROM "projects" WHERE "projects"."id" = ? LIMIT 1

Please keep in mind that dynamic tags can affect RDBMS query cache. Consult your RDBMS documentation for details. Some known links:

MySQL / Percona Server: https://www.percona.com/doc/percona-server/5.7/performance/query_cache_enhance.html#ignoring-comments
Microsoft SQL Server: https://msdn.microsoft.com/en-us/library/cc293623.aspx

Short example

This example shows some reform features. It uses https://github.com/AlekSi/pointer to get pointers to values of build-in types.

Example
// Use reform.NewDB to create DB.

// Save record (performs INSERT or UPDATE).
person := &Person{
	Name:  "Alexey Palazhchenko",
	Email: pointer.ToString("alexey.palazhchenko@gmail.com"),
}
if err := DB.Save(person); err != nil {
	log.Fatal(err)
}

// ID is filled by Save.
person2, err := DB.FindByPrimaryKeyFrom(PersonTable, person.ID)
if err != nil {
	log.Fatal(err)
}
fmt.Println(person2.(*Person).Name)

// Delete record.
if err = DB.Delete(person); err != nil {
	log.Fatal(err)
}

// Find records by IDs.
persons, err := DB.FindAllFrom(PersonTable, "id", 1, 2)
if err != nil {
	log.Fatal(err)
}
for _, p := range persons {
	fmt.Println(p)
}
Output:

Alexey Palazhchenko
ID: 1 (int32), GroupID: 65534 (*int32), Name: `Denis Mills` (string), Email: <nil> (*string), CreatedAt: 2009-11-10 23:00:00 +0000 UTC (time.Time), UpdatedAt: <nil> (*time.Time)
ID: 2 (int32), GroupID: 65534 (*int32), Name: `Garrick Muller` (string), Email: `muller_garrick@example.com` (*string), CreatedAt: 2009-12-12 12:34:56 +0000 UTC (time.Time), UpdatedAt: <nil> (*time.Time)

Index

Examples

Constants

View Source
const Version = "v1.5.1"

Version defines reform version.

Variables

View Source
var (
	// ErrNoRows is returned from various methods when query produced no rows.
	ErrNoRows = sql.ErrNoRows

	// ErrTxDone is returned from Commit() and Rollback() TX methods when transaction is already
	// committed or rolled back.
	ErrTxDone = sql.ErrTxDone

	// ErrNoPK is returned from various methods when primary key is required and not set.
	ErrNoPK = errors.New("reform: no primary key")
)

Functions

func Inspect

func Inspect(arg interface{}, addType bool) string

Inspect returns suitable for logging representation of a query argument.

func SetPK deprecated added in v1.5.0

func SetPK(r Record, pk interface{})

SetPK sets record's primary key, if possible.

Deprecated: prefer direct field assignment where possible.

Types

type AfterFinder

type AfterFinder interface {
	AfterFind() error
}

AfterFinder is an optional interface for Record which is used by Querier's finders and selectors. It can be used to convert timezones, change data precision, etc. Returning error aborts operation.

type BeforeInserter

type BeforeInserter interface {
	BeforeInsert() error
}

BeforeInserter is an optional interface for Record which is used by Querier.Insert. It can be used to set record's timestamp fields, convert timezones, change data precision, etc. Returning error aborts operation.

type BeforeUpdater

type BeforeUpdater interface {
	BeforeUpdate() error
}

BeforeUpdater is an optional interface for Record which is used by Querier.Update and Querier.UpdateColumns. It can be used to set record's timestamp fields, convert timezones, change data precision, etc. Returning error aborts operation.

type DB

type DB struct {
	*Querier
	// contains filtered or unexported fields
}

DB represents a connection to SQL database.

func NewDB

func NewDB(db *sql.DB, dialect Dialect, logger Logger) *DB

NewDB creates new DB object for given SQL database connection. Logger can be nil.

Example
// Get *sql.DB as usual. PostgreSQL example:
sqlDB, err := sql.Open("postgres", "postgres://127.0.0.1:5432/database")
if err != nil {
	log.Fatal(err)
}
defer sqlDB.Close()

// Use new *log.Logger for logging.
logger := log.New(os.Stderr, "SQL: ", log.Flags())

// Create *reform.DB instance with simple logger.
// Any Printf-like function (fmt.Printf, log.Printf, testing.T.Logf, etc) can be used with NewPrintfLogger.
// Change dialect for other databases.
_ = reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(logger.Printf))
Output:

func NewDBFromInterface added in v1.1.0

func NewDBFromInterface(db DBInterface, dialect Dialect, logger Logger) *DB

NewDBFromInterface creates new DB object for given DBInterface. Can be used for easier integration with existing code or for passing test doubles. Logger can be nil.

func (*DB) Begin

func (db *DB) Begin() (*TX, error)

Begin starts transaction with Querier's context and default options.

func (*DB) BeginTx added in v1.4.0

func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*TX, error)

BeginTx starts transaction with given context and options (can be nil).

func (*DB) DBInterface added in v1.3.0

func (db *DB) DBInterface() DBInterface

DBInterface returns DBInterface associated with a given DB object.

func (*DB) InTransaction

func (db *DB) InTransaction(f func(t *TX) error) error

InTransaction wraps function execution in transaction with Querier's context and default options, rolling back it in case of error or panic, committing otherwise.

func (*DB) InTransactionContext added in v1.4.0

func (db *DB) InTransactionContext(ctx context.Context, opts *sql.TxOptions, f func(t *TX) error) error

InTransactionContext wraps function execution in transaction with given context and options (can be nil), rolling back it in case of error or panic, committing otherwise.

type DBInterface added in v1.1.0

type DBInterface interface {
	DBTXContext
	BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)

	// Deprecated: do not use, it will be removed in v1.6.
	DBTX
	// Deprecated: do not use, it will be removed in v1.6.
	Begin() (*sql.Tx, error)
}

DBInterface is a subset of *sql.DB used by reform. Can be used together with NewDBFromInterface for easier integration with existing code or for passing test doubles.

It may grow and shrink over time to include only needed *sql.DB methods, and is excluded from SemVer compatibility guarantees.

type DBTX

type DBTX interface {
	// Exec executes a query without returning any rows.
	// The args are for any placeholder parameters in the query.
	Exec(query string, args ...interface{}) (sql.Result, error)

	// Query executes a query that returns rows, typically a SELECT.
	// The args are for any placeholder parameters in the query.
	Query(query string, args ...interface{}) (*sql.Rows, error)

	// QueryRow executes a query that is expected to return at most one row.
	// QueryRow always returns a non-nil value. Errors are deferred until Row's Scan method is called.
	// If the query selects no rows, the *Row's Scan will return ErrNoRows.
	// Otherwise, the *Row's Scan scans the first selected row and discards the rest.
	QueryRow(query string, args ...interface{}) *sql.Row
}

DBTX is an interface for database connection or transaction. It's implemented by *sql.DB, *sql.Tx, *DB, *TX, and *Querier.

type DBTXContext added in v1.4.0

type DBTXContext interface {
	// ExecContext executes a query without returning any rows.
	// The args are for any placeholder parameters in the query.
	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)

	// QueryContext executes a query that returns rows, typically a SELECT.
	// The args are for any placeholder parameters in the query.
	QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)

	// QueryRowContext executes a query that is expected to return at most one row.
	// QueryRowContext always returns a non-nil value. Errors are deferred until Row's Scan method is called.
	// If the query selects no rows, the *Row's Scan will return ErrNoRows.
	// Otherwise, the *Row's Scan scans the first selected row and discards the rest.
	QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}

DBTXContext is an interface for database connection or transaction with context support. It's implemented by *sql.DB, *sql.Tx, *sql.Conn, *DB, *TX, and *Querier.

type DefaultValuesMethod added in v1.2.0

type DefaultValuesMethod int

DefaultValuesMethod is a method of inserting of row with all default values.

const (
	// DefaultValues is a method using "DEFAULT VALUES"
	DefaultValues DefaultValuesMethod = iota

	// EmptyLists is a method using "() VALUES ()"
	EmptyLists
)

type Dialect

type Dialect interface {
	// String returns dialect name.
	String() string

	// Placeholder returns representation of placeholder parameter for given index,
	// typically "?" or "$1".
	Placeholder(index int) string

	// Placeholders returns representation of placeholder parameters for given start index and count,
	// typically []{"?", "?"} or []{"$1", "$2"}.
	Placeholders(start, count int) []string

	// QuoteIdentifier returns quoted database identifier,
	// typically "identifier" or `identifier`.
	QuoteIdentifier(identifier string) string

	// LastInsertIdMethod returns a method of receiving primary key of last inserted row.
	LastInsertIdMethod() LastInsertIdMethod

	// SelectLimitMethod returns a method of limiting the number of rows in a query result.
	SelectLimitMethod() SelectLimitMethod

	// DefaultValuesMethod returns a method of inserting of row with all default values.
	DefaultValuesMethod() DefaultValuesMethod
}

Dialect represents differences in various SQL dialects.

type LastInsertIdMethod

type LastInsertIdMethod int

LastInsertIdMethod is a method of receiving primary key of last inserted row.

const (
	// LastInsertId is method using sql.Result.LastInsertId().
	LastInsertId LastInsertIdMethod = iota

	// Returning is method using "RETURNING id" SQL syntax.
	Returning

	// OutputInserted is method using "OUTPUT INSERTED.id" SQL syntax.
	OutputInserted
)

type Logger

type Logger interface {
	// Before logs query before execution.
	Before(query string, args []interface{})

	// After logs query after execution.
	After(query string, args []interface{}, d time.Duration, err error)
}

Logger is responsible to log queries before and after their execution.

type Printf

type Printf func(format string, args ...interface{})

Printf is a (fmt.Printf|log.Printf|testing.T.Logf)-like function.

type PrintfLogger

type PrintfLogger struct {
	LogTypes bool
	// contains filtered or unexported fields
}

PrintfLogger is a simple query logger.

func NewPrintfLogger

func NewPrintfLogger(printf Printf) *PrintfLogger

NewPrintfLogger creates a new simple query logger for any Printf-like function.

func (*PrintfLogger) After

func (pl *PrintfLogger) After(query string, args []interface{}, d time.Duration, err error)

After logs query after execution.

func (*PrintfLogger) Before

func (pl *PrintfLogger) Before(query string, args []interface{})

Before logs query before execution.

type Querier

type Querier struct {
	Dialect
	Logger Logger
	// contains filtered or unexported fields
}

Querier performs queries and commands.

func (*Querier) Context added in v1.4.0

func (q *Querier) Context() context.Context

Context returns Querier's context. Default context is context.Background().

func (*Querier) Count added in v1.4.0

func (q *Querier) Count(view View, tail string, args ...interface{}) (int, error)

Count queries view with tail and args and returns a number (COUNT(*)) of matching rows.

func (*Querier) Delete

func (q *Querier) Delete(record Record) error

Delete deletes record from SQL database table by primary key.

Method returns ErrNoRows if no rows were deleted. Method returns ErrNoPK if primary key is not set.

func (*Querier) DeleteFrom

func (q *Querier) DeleteFrom(view View, tail string, args ...interface{}) (uint, error)

DeleteFrom deletes rows from view with tail and args and returns a number of deleted rows.

Method never returns ErrNoRows.

func (*Querier) Exec

func (q *Querier) Exec(query string, args ...interface{}) (sql.Result, error)

Exec executes a query without returning any rows. The args are for any placeholder parameters in the query.

func (*Querier) ExecContext added in v1.4.0

func (q *Querier) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)

ExecContext just calls q.WithContext(ctx).Exec(query, args...), and that form should be used instead. This method exists to satisfy various standard interfaces for advanced use-cases.

func (*Querier) FindAllFrom

func (q *Querier) FindAllFrom(view View, column string, args ...interface{}) ([]Struct, error)

FindAllFrom queries view with column and args and returns a slice of new Structs. If view's Struct implements AfterFinder, it also calls AfterFind().

In case of query error slice will be nil. If error is encountered during iteration, partial result and error will be returned. Error is never ErrNoRows.

func (*Querier) FindByPrimaryKeyFrom

func (q *Querier) FindByPrimaryKeyFrom(table Table, pk interface{}) (Record, error)

FindByPrimaryKeyFrom queries table with primary key and scans first result to new Record. If record implements AfterFinder, it also calls AfterFind().

If there are no rows in result, it returns nil, ErrNoRows. It also may return QueryRow(), Scan() and AfterFinder errors.

func (*Querier) FindByPrimaryKeyTo

func (q *Querier) FindByPrimaryKeyTo(record Record, pk interface{}) error

FindByPrimaryKeyTo queries record's Table with primary key and scans first result to record. If record implements AfterFinder, it also calls AfterFind().

If there are no rows in result, it returns ErrNoRows. It also may return QueryRow(), Scan() and AfterFinder errors.

func (*Querier) FindOneFrom

func (q *Querier) FindOneFrom(view View, column string, arg interface{}) (Struct, error)

FindOneFrom queries view with column and arg and scans first result to new Struct str. If str implements AfterFinder, it also calls AfterFind().

If there are no rows in result, it returns nil, ErrNoRows. It also may return QueryRow(), Scan() and AfterFinder errors.

func (*Querier) FindOneTo

func (q *Querier) FindOneTo(str Struct, column string, arg interface{}) error

FindOneTo queries str's View with column and arg and scans first result to str. If str implements AfterFinder, it also calls AfterFind().

If there are no rows in result, it returns ErrNoRows. It also may return QueryRow(), Scan() and AfterFinder errors.

func (*Querier) FindRows

func (q *Querier) FindRows(view View, column string, arg interface{}) (*sql.Rows, error)

FindRows queries view with column and arg and returns rows. They can then be iterated with NextRow(). It is caller's responsibility to call rows.Close().

In case of error rows will be nil. Error is never ErrNoRows.

See SelectRows example for idiomatic usage.

func (*Querier) Insert

func (q *Querier) Insert(str Struct) error

Insert inserts a struct into SQL database table. If str implements BeforeInserter, it calls BeforeInsert() before doing so.

It fills record's primary key field.

func (*Querier) InsertColumns added in v1.2.0

func (q *Querier) InsertColumns(str Struct, columns ...string) error

InsertColumns inserts a struct into SQL database table with specified columns. Other columns are omitted from generated INSERT statement. If str implements BeforeInserter, it calls BeforeInsert() before doing so.

It fills record's primary key field.

func (*Querier) InsertMulti added in v1.1.0

func (q *Querier) InsertMulti(structs ...Struct) error

InsertMulti inserts several structs into SQL database table with single query. If they implement BeforeInserter, it calls BeforeInsert() before doing so.

All structs should belong to the same view/table. All records should either have or not have primary key set. It doesn't fill primary key fields. Given all these limitations, most users should use Querier.Insert in a loop, not this method.

Example
// insert up to 3 structs at once
const batchSize = 3
for i := 0; i < len(persons)/batchSize+1; i++ {
	low := i * batchSize
	high := (i + 1) * batchSize
	if high > len(persons) {
		high = len(persons)
	}
	batch := persons[low:high]

	if err := DB.InsertMulti(batch...); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Inserted %d persons\n", len(batch))
}

// note that ID is not filled
fmt.Println(persons[0].(*Person).ID, persons[0].(*Person).Name)
Output:

Inserted 3 persons
Inserted 2 persons
0 Alexey Palazhchenko

func (*Querier) NextRow

func (q *Querier) NextRow(str Struct, rows *sql.Rows) error

NextRow scans next result row from rows to str. If str implements AfterFinder, it also calls AfterFind(). It is caller's responsibility to call rows.Close().

If there is no next result row, it returns ErrNoRows. It also may return rows.Err(), rows.Scan() and AfterFinder errors.

See SelectRows example for idiomatic usage.

func (*Querier) QualifiedColumns

func (q *Querier) QualifiedColumns(view View) []string

QualifiedColumns returns a slice of quoted qualified column names for given view.

func (*Querier) QualifiedView

func (q *Querier) QualifiedView(view View) string

QualifiedView returns quoted qualified view name.

func (*Querier) Query

func (q *Querier) Query(query string, args ...interface{}) (*sql.Rows, error)

Query executes a query that returns rows, typically a SELECT. The args are for any placeholder parameters in the query.

Example
columns := DB.QualifiedColumns(PersonTable)
columns = append(columns, DB.QualifiedColumns(PersonProjectView)...)
columns = append(columns, DB.QualifiedColumns(ProjectTable)...)
query := fmt.Sprintf(`
		SELECT %s
			FROM people
			INNER JOIN person_project ON people.id = person_project.person_id
			INNER JOIN projects ON person_project.project_id = projects.id
			ORDER BY person_id, project_id;
	`, strings.Join(columns, ", "))
rows, err := DB.Query(query)
if err != nil {
	log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
	var person Person
	var personProject PersonProject
	var project Project
	pointers := person.Pointers()
	pointers = append(pointers, personProject.Pointers()...)
	pointers = append(pointers, project.Pointers()...)
	if err = rows.Scan(pointers...); err != nil {
		log.Print(err)
	}
	if err = person.AfterFind(); err != nil {
		log.Fatal(err)
	}
	if err = project.AfterFind(); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s - %s\n", person.Name, project.Name)
}
if err = rows.Err(); err != nil {
	log.Fatal(err)
}
Output:

Noble Schumm - Vicious Baron
Elfrieda Abbott - Vicious Baron
Elfrieda Abbott - Thirsty Queen
Elfrieda Abbott - Vicious Baron
Elfrieda Abbott - Thirsty Queen
Elfrieda Abbott - Kosher Traveler

func (*Querier) QueryContext added in v1.4.0

func (q *Querier) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)

QueryContext just calls q.WithContext(ctx).Query(query, args...), and that form should be used instead. This method exists to satisfy various standard interfaces for advanced use-cases.

func (*Querier) QueryRow

func (q *Querier) QueryRow(query string, args ...interface{}) *sql.Row

QueryRow executes a query that is expected to return at most one row. QueryRow always returns a non-nil value. Errors are deferred until Row's Scan method is called.

func (*Querier) QueryRowContext added in v1.4.0

func (q *Querier) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row

QueryRowContext just calls q.WithContext(ctx).QueryRow(query, args...), and that form should be used instead. This method exists to satisfy various standard interfaces for advanced use-cases.

func (*Querier) Reload

func (q *Querier) Reload(record Record) error

Reload is a shortcut for FindByPrimaryKeyTo for given record.

func (*Querier) Save

func (q *Querier) Save(record Record) error

Save saves record in SQL database table. If primary key is set, it first calls Update and checks if row was affected (matched). If primary key is absent or no row was affected, it calls Insert. This allows to call Save with Record with primary key set.

func (*Querier) SelectAllFrom

func (q *Querier) SelectAllFrom(view View, tail string, args ...interface{}) (structs []Struct, err error)

SelectAllFrom queries view with tail and args and returns a slice of new Structs. If view's Struct implements AfterFinder, it also calls AfterFind().

In case of query error slice will be nil. If error is encountered during iteration, partial result and error will be returned. Error is never ErrNoRows.

func (*Querier) SelectOneFrom

func (q *Querier) SelectOneFrom(view View, tail string, args ...interface{}) (Struct, error)

SelectOneFrom queries view with tail and args and scans first result to new Struct str. If str implements AfterFinder, it also calls AfterFind().

If there are no rows in result, it returns nil, ErrNoRows. It also may return QueryRow(), Scan() and AfterFinder errors.

func (*Querier) SelectOneTo

func (q *Querier) SelectOneTo(str Struct, tail string, args ...interface{}) error

SelectOneTo queries str's View with tail and args and scans first result to str. If str implements AfterFinder, it also calls AfterFind().

If there are no rows in result, it returns ErrNoRows. It also may return QueryRow(), Scan() and AfterFinder errors.

Example
var person Person
tail := fmt.Sprintf("WHERE created_at < %s ORDER BY id", DB.Placeholder(1))
y2010 := time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC)
if err := DB.SelectOneTo(&person, tail, y2010); err != nil {
	log.Fatal(err)
}
fmt.Println(person)
Output:

ID: 1 (int32), GroupID: 65534 (*int32), Name: `Denis Mills` (string), Email: <nil> (*string), CreatedAt: 2009-11-10 23:00:00 +0000 UTC (time.Time), UpdatedAt: <nil> (*time.Time)

func (*Querier) SelectRows

func (q *Querier) SelectRows(view View, tail string, args ...interface{}) (*sql.Rows, error)

SelectRows queries view with tail and args and returns rows. They can then be iterated with NextRow(). It is caller's responsibility to call rows.Close().

In case of error rows will be nil. Error is never ErrNoRows.

See example for idiomatic usage.

Example
tail := fmt.Sprintf("WHERE created_at < %s ORDER BY id", DB.Placeholder(1))
y2010 := time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC)
rows, err := DB.SelectRows(PersonTable, tail, y2010)
if err != nil {
	log.Fatal(err)
}
defer rows.Close()

for {
	var person Person
	if err = DB.NextRow(&person, rows); err != nil {
		break
	}
	fmt.Println(person)
}
if err != reform.ErrNoRows {
	log.Fatal(err)
}
Output:

ID: 1 (int32), GroupID: 65534 (*int32), Name: `Denis Mills` (string), Email: <nil> (*string), CreatedAt: 2009-11-10 23:00:00 +0000 UTC (time.Time), UpdatedAt: <nil> (*time.Time)
ID: 2 (int32), GroupID: 65534 (*int32), Name: `Garrick Muller` (string), Email: `muller_garrick@example.com` (*string), CreatedAt: 2009-12-12 12:34:56 +0000 UTC (time.Time), UpdatedAt: <nil> (*time.Time)

func (*Querier) Tag added in v1.4.0

func (q *Querier) Tag() string

Tag returns Querier's tag. Default tag is empty.

func (*Querier) Update

func (q *Querier) Update(record Record) error

Update updates all columns of row specified by primary key in SQL database table with given record. If record implements BeforeUpdater, it calls BeforeUpdate() before doing so.

Method returns ErrNoRows if no rows were updated. Method returns ErrNoPK if primary key is not set.

func (*Querier) UpdateColumns

func (q *Querier) UpdateColumns(record Record, columns ...string) error

UpdateColumns updates specified columns of row specified by primary key in SQL database table with given record. Other columns are omitted from generated UPDATE statement. If record implements BeforeUpdater, it calls BeforeUpdate() before doing so.

Method returns ErrNoRows if no rows were updated. Method returns ErrNoPK if primary key is not set.

func (*Querier) UpdateView added in v1.3.0

func (q *Querier) UpdateView(str Struct, columns []string, tail string, args ...interface{}) (uint, error)

UpdateView updates specified columns of rows specified by tail and args in SQL database table with given struct, and returns a number of updated rows. Other columns are omitted from generated UPDATE statement. If struct implements BeforeUpdater, it calls BeforeUpdate() before doing so.

Method never returns ErrNoRows.

func (*Querier) WithContext added in v1.4.0

func (q *Querier) WithContext(ctx context.Context) *Querier

WithContext returns a copy of Querier with set context. Returned Querier is tied to the same DB or TX. See Context section in documentation for details.

Example
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, _ = DB.WithContext(ctx).SelectAllFrom(ProjectTable, "")
Output:

func (*Querier) WithTag added in v1.3.0

func (q *Querier) WithTag(format string, args ...interface{}) *Querier

WithTag returns a copy of Querier with set tag. Returned Querier is tied to the same DB or TX. See Tagging section in documentation for details.

Example
id := "baron"
_, _ = DB.WithTag("GetProject:%v", id).FindByPrimaryKeyFrom(ProjectTable, id)
Output:

type Record

type Record interface {
	Struct

	// Table returns Table object for that record.
	Table() Table

	// PKValue returns a value of primary key for that record.
	// Returned interface{} value is never untyped nil.
	PKValue() interface{}

	// PKPointer returns a pointer to primary key field for that record.
	// Returned interface{} value is never untyped nil.
	PKPointer() interface{}

	// HasPK returns true if record has non-zero primary key set, false otherwise.
	HasPK() bool

	// SetPK sets record primary key, if possible.
	//
	// Deprecated: prefer direct field assignment where possible.
	SetPK(pk interface{})
}

Record represents a row in SQL database table with single-column primary key.

type SelectLimitMethod added in v1.2.0

type SelectLimitMethod int

SelectLimitMethod is a method of limiting the number of rows in a query result.

const (
	// Limit is a method using "LIMIT N" SQL syntax.
	Limit SelectLimitMethod = iota

	// SelectTop is a method using "SELECT TOP N" SQL syntax.
	SelectTop
)

type Struct

type Struct interface {
	// String returns a string representation of this struct or record.
	String() string

	// Values returns a slice of struct or record field values.
	// Returned interface{} values are never untyped nils.
	Values() []interface{}

	// Pointers returns a slice of pointers to struct or record fields.
	// Returned interface{} values are never untyped nils.
	Pointers() []interface{}

	// View returns View object for that struct.
	View() View
}

Struct represents a row in SQL database view or table.

type TX

type TX struct {
	*Querier
	// contains filtered or unexported fields
}

TX represents a SQL database transaction.

func NewTX

func NewTX(tx *sql.Tx, dialect Dialect, logger Logger) *TX

NewTX creates new TX object for given SQL database transaction. Logger can be nil.

func NewTXFromInterface added in v1.1.0

func NewTXFromInterface(tx TXInterface, dialect Dialect, logger Logger) *TX

NewTXFromInterface creates new TX object for given TXInterface. Can be used for easier integration with existing code or for passing test doubles. Logger can be nil.

func (*TX) Commit

func (tx *TX) Commit() error

Commit commits the transaction.

func (*TX) Rollback

func (tx *TX) Rollback() error

Rollback aborts the transaction.

type TXInterface added in v1.1.0

type TXInterface interface {
	DBTXContext
	Commit() error
	Rollback() error

	// Deprecated: do not use, it will be removed in v1.6.
	DBTX
}

TXInterface is a subset of *sql.Tx used by reform. Can be used together with NewTXFromInterface for easier integration with existing code or for passing test doubles.

It may grow and shrink over time to include only needed *sql.Tx methods, and is excluded from SemVer compatibility guarantees.

type Table

type Table interface {
	View

	// NewRecord makes a new record for that table.
	NewRecord() Record

	// PKColumnIndex returns an index of primary key column for that table in SQL database.
	PKColumnIndex() uint
}

Table represents SQL database table with single-column primary key. It extends View.

type View

type View interface {
	// Schema returns a schema name in SQL database.
	Schema() string

	// Name returns a view or table name in SQL database.
	Name() string

	// Columns returns a new slice of column names for that view or table in SQL database.
	Columns() []string

	// NewStruct makes a new struct for that view or table.
	NewStruct() Struct
}

View represents SQL database view or table.

Directories

Path Synopsis
Package dialects implements reform.Dialect selector.
Package dialects implements reform.Dialect selector.
mssql
Package mssql implements reform.Dialect for Microsoft SQL Server (mssql driver).
Package mssql implements reform.Dialect for Microsoft SQL Server (mssql driver).
mysql
Package mysql implements reform.Dialect for MySQL.
Package mysql implements reform.Dialect for MySQL.
postgresql
Package postgresql implements reform.Dialect for PostgreSQL.
Package postgresql implements reform.Dialect for PostgreSQL.
sqlite3
Package sqlite3 implements reform.Dialect for SQLite3.
Package sqlite3 implements reform.Dialect for SQLite3.
sqlserver
Package sqlserver implements reform.Dialect for Microsoft SQL Server (sqlserver driver).
Package sqlserver implements reform.Dialect for Microsoft SQL Server (sqlserver driver).
test
Package test provides shared testing utilities.
Package test provides shared testing utilities.
Package parse implements parsing of Go structs in files and runtime.
Package parse implements parsing of Go structs in files and runtime.
Package reform implements reform command.
Package reform implements reform command.
Package reform-db implements reform-db command.
Package reform-db implements reform-db command.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL