godb

package module
v0.0.0-...-a8bc1c0 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2020 License: BSD-3-Clause Imports: 15 Imported by: 0

README

GoDB is an ORM

The dilema with all ORMs is exactly what balance to strike between insulating the user from the particularities of the database and providing the user with flexibility.

GoDB skews towards flexibility, allowing the user to leverage the expressiveness of SQL and to explicitly control relationships while the ORM automates the mundane aspects of marshaling rows into structs.

We've been using and improving an internal version of GoDB in production at Hirepurpose for over a year and we have found it to be very effective for our needs.

Mapping Models

Mapping your application models to the database schema is done using struct field tags, similar to how other such mappings are defined in Go. The struct tag db is used to declare the column name of the field, and a few arguments are used to describe the use of that column.

type Related struct {
 	Id    	string  	`db:"id,pk"`
 	Value	int			`db:"value"`
}

type Example struct {
  	Id    		string  	`db:"id,pk"`
  	Name		string  	`db:"name"`
  	Related		[]*Related
  	CreatedAt 	time.Time	`db:"created_at,ro"`
}

The following tag arguments are supported:

  • pk The field is the table's primary key.
  • fk The field is a foreign key in a one-to-one relation. The relation must be explicitly stored and fetched, this argument indicates that the column should be provided to the persister in order to fetch the relationship.
  • inline The field is a struct that should be flattened inline into the table. The column name is used as a prefix to the column names in the inlined struct.
  • ro The field is read-only. This can be used for columns that are generated by the database and which you want to read on fetch, but never write.

You'll notice that the Related field, which is a one-to-many mapping, is not managed automatically by GoDB. In order to provide flexibility in how relationships are managed, they are stored and fetched explicitly by implementing specific interfaces in the Persister which abstracts ORM from the rest of the application and performs the low-level mapping.

ORM

The ORM interface implements persistence primitives used by Persisters to manage persistent structs. The Persister itself is an argument to most methods as GoDB uses it to delegate managing relationships.

type ORM interface {
  DefaultContext()(db.Context)
  
  StoreEntity(Persister, interface{}, StoreOptions, db.Context)(error)
  CountEntities(Persister, string, ...interface{})(int, error)
  FetchEntity(Persister, interface{}, FetchOptions, db.Context, string, ...interface{})(error)
  FetchEntities(Persister, interface{}, FetchOptions, db.Context, string, ...interface{})(error)
  DeleteEntity(Persister, interface{}, StoreOptions, db.Context)(error)
  
  StoreRelated(Persister, interface{}, StoreOptions, db.Context)(error)
  StoreReferences(Persister, interface{}, StoreOptions, db.Context)(error)
  FetchRelated(Persister, interface{}, Columns, FetchOptions, db.Context)(error)
  DeleteRelated(Persister, interface{}, StoreOptions, db.Context)(error)
  DeleteReferences(Persister, interface{}, StoreOptions, db.Context)(error)
}

Persisters

Each struct that can be persisted to the database has a counterpart Persister, which is implemented to manage relationships and abstract persistence details. Persisters use the persistence primitives provided by ORM to interact with the database.

Aside from Table, all the methods below are optional and can be implemented only when required. GoDB uses the the Persister to delegate relation management when operating on a persistent struct.

type ExamplePersister struct {
  godb.ORM
}

func (e ExamplePersister) Table() string {
  return "example" // the table name
}

func (e ExamplePersister) StoreRelated(v interface{}, opts persist.StoreOptions, cxt db.Context) error {
  // When implemented, GoDB will invoke to explicitly store related entities
}

func (e ExamplePersister) StoreReferences(v interface{}, opts persist.StoreOptions, cxt db.Context) error {
  // When implemented, GoDB will invoke to explicitly store references to related entities
}

func (e ExamplePersister) FetchRelated(v interface{}, opts godb.FetchOptions, cxt db.Context) error {
  // When implemented, GoDB will invoke to explicitly fetch related entities
}

func (e ExamplePersister) FetchRelated(v interface{}, extra godb.Columns, opts godb.FetchOptions, cxt db.Context) error {
  // When implemented, GoDB will invoke to explicitly fetch related entities
}

func (e ExamplePersister) DeleteRelated(v interface{}, opts godb.StoreOptions, cxt db.Context) error {
  // When implemented, GoDB will invoke to explicitly delete related entities
}

func (e ExamplePersister) DeleteReferences(v interface{}, opts godb.StoreOptions, cxt db.Context) error {
  // When implemented, GoDB will invoke to explicitly delete references to related entities
}

PQL

GoDB is facilitated in part by a lightweight templating extension to SQL, called PQL, which expands an expression to the columns supported by a persistent struct. The rest of the SQL statement is unmodified.

For example, in the context of the Example struct above, the following PQL statement:

SELECT {e.*} FROM example AS e
	INNER JOIN relating_table r ON r.example_id = e.id
	WHERE r.another_id = $1

Will be expanded to this SQL:

SELECT e.id, e.name, e.created_at FROM example AS e
	INNER JOIN relating_table r ON r.example_id = e.id
	WHERE r.another_id = $1

In this trivial example, it would be easy enough to just write the individual columns we need to fetch in the SELECT clause. Maybe we can even get away with just fetching * in many cases. When dealing with real-world data, however, making counterpart updates to every SQL statement that deals with a given struct every time that struct changes in any way becomes very tedious and error-prone.

This small extension to SQL allows us to more easily write queries from the perspective of the application model and GoDB deals with the details of mapping that to the database schema.

Documentation

Index

Constants

View Source
const (
	PG_ERROR_UNIQUE_VIOLATION      = "23505"
	PG_ERROR_FOREIGN_KEY_VIOLATION = "23503"
)

Postgres errors from: https://github.com/lib/pq/blob/master/error.go#L78

View Source
const (
	RESULT_COUNT_MAX       = 250
	RESULT_COUNT_DEFAULT   = 25
	CACHE_ELEMENTS_DEFAULT = 1024
)

Variables

View Source
var (
	ErrNotFound      = fmt.Errorf("Not found")
	ErrTransient     = fmt.Errorf("Transient")
	ErrImmutable     = fmt.Errorf("Immutable")
	ErrInconvertible = fmt.Errorf("Inconvertible")
	ErrForbidden     = fmt.Errorf("Forbidden")
	ErrInvalidEntity = fmt.Errorf("Invalid Entity")
)
View Source
var InvalidRange = Range{0, -1}
View Source
var ZeroRange = Range{0, 0}

Functions

func ErrNotFoundInTable

func ErrNotFoundInTable(table string) error

func IsForeignKeyViolation

func IsForeignKeyViolation(e error) bool

Is the error a Postgres foreign key violation?

func IsUniqueViolation

func IsUniqueViolation(e error) bool

Is the error a Postgres unique violation?

func Now

func Now() time.Time

Return the current time truncated to database precision (milliseconds)

Types

type Context

type Context interface {
	Exec(query string, args ...interface{}) (sql.Result, error)
	Query(query string, args ...interface{}) (*sql.Rows, error)
	QueryRow(query string, args ...interface{}) *sql.Row
}

An SQL executable context

type Database

type Database struct {
	// contains filtered or unexported fields
}

The store client

func New

func New(uri string, migrate bool, syncer sync.Service, opts ...Option) (*Database, error)

Create a new store

func (*Database) Atomic

func (d *Database) Atomic(cxt Context, h TransactionHandler) error

Execute in a new transaction and commit or roll-back as necessary on completion if the provided transation is nil. Otherwise, use the provided transaction and assume it is managed externally.

func (*Database) Begin

func (d *Database) Begin() (*sql.Tx, error)

Begin a transaction

func (*Database) Database

func (d *Database) Database() *sql.DB

Obtain the underlying database

func (*Database) Exec

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

Implement Context

func (*Database) Migrate

func (d *Database) Migrate() error

Migrate

func (*Database) MigrateToVersion

func (d *Database) MigrateToVersion(v int) error

Migrate to a specific version

func (*Database) MigrationVersion

func (d *Database) MigrationVersion() (int, error)

Determine the current migration version

func (*Database) Query

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

Implement Context

func (*Database) QueryRow

func (d *Database) QueryRow(query string, args ...interface{}) *sql.Row

Implement Context

func (*Database) Transaction

func (d *Database) Transaction(h TransactionHandler) error

Execute in a transaction. A transaction is created and the handler is invoked. If the handler returns a non-nil error the transaction is rolled back, otherwise the transaction is committed.

type DebugContext

type DebugContext struct {
	// contains filtered or unexported fields
}

A debug context which logs out statements

func NewDebugContext

func NewDebugContext(cxt Context) DebugContext

Create a debug context

func NewDebugContextWithPrefix

func NewDebugContextWithPrefix(prefix string, cxt Context) DebugContext

Create a debug context

func (DebugContext) Exec

func (d DebugContext) Exec(query string, args ...interface{}) (sql.Result, error)

func (DebugContext) Query

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

func (DebugContext) QueryRow

func (d DebugContext) QueryRow(query string, args ...interface{}) *sql.Row

type Option

type Option func(d *Database) *Database

func OptionMaxIdleConns

func OptionMaxIdleConns(v int) Option

func OptionMaxOpenConns

func OptionMaxOpenConns(v int) Option

type Range

type Range struct {
	Location int `json:"location"`
	Length   int `json:"length"`
}

A range

type TransactionHandler

type TransactionHandler func(cxt Context) error

A transaction handler

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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