sal

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Mar 5, 2020 License: MIT Imports: 6 Imported by: 1

README

sal Build Status GoDoc

Generator client to the database on the Golang based on interface.

Install

go get -u github.com/go-gad/sal/...

Usage

Read article https://medium.com/@zaurio/generator-the-client-to-sql-database-on-golang-dccfeb4641c3

 salgen -h
Usage:
    salgen [options...] <import_path> <interface_name>

Example:
    salgen -destination=./client.go -package=github.com/go-gad/sal/examples/profile/storage github.com/go-gad/sal/examples/profile/storage Store

  <import_path>
        describes the complete package path where the interface is located.
  <interface_name>
        indicates the interface name itself.

Options:
  -build_flags string
        Additional flags for go build.
  -destination string
        Output file; defaults to stdout.
  -package string
        The full import path of the library for the generated implementation

With go generate:

//go:generate salgen -destination=./client.go -package=github.com/go-gad/sal/examples/profile/storage github.com/go-gad/sal/examples/profile/storage Store
type Store interface {
	CreateUser(ctx context.Context, req CreateUserReq) (*CreateUserResp, error)
}

type CreateUserReq struct {
	Name  string `sql:"name"`
	Email string `sql:"email"`
}

func (r CreateUserReq) Query() string {
	return `INSERT INTO users(name, email, created_at) VALUES(@name, @email, now()) RETURNING id, created_at`
}

type CreateUserResp struct {
	ID        int64     `sql:"id"`
	CreatedAt time.Time `sql:"created_at"`
}

In your project run command

$ go generate ./...

File client.go will be generated.

Command line args and options

  • flag -destination determines in which file the generated code will be written.
  • flag -package is the full import path of the library for the generated implementation.
  • first arg describes the complete package path where the interface is located.
  • second indicates the interface name itself.

Possible definitions of methods

type Store interface {
	CreateAuthor(ctx context.Context, req CreateAuthorReq) (CreateAuthorResp, error)
	GetAuthors(ctx context.Context, req GetAuthorsReq) ([]*GetAuthorsResp, error)
	UpdateAuthor(ctx context.Context, req *UpdateAuthorReq) error
	DeleteAuthors(ctx context.Context, req *DeleteAuthorsReq) (sql.Result, error)
}
  • The number of arguments is always strictly two.
  • The first argument is the context.
  • The second argument contains the data to bind the variables and defines the query string.
  • The first output parameter can be an object, an array of objects, sql.Result or missing.
  • Last output parameter is always an error.

The second argument expects a parameter with a base type of struct (or a pointer to a struct). The parameter must satisfy the following interface:

type Queryer interface {
	Query() string
}

The string returned by method Query is used as a SQL query.

Prepared statements

The generated code supports prepared statements. Prepared statements are cached. After the first preparation of the statement, it is placed in the cache. The database/sql library itself ensures that prepared statements are transparently applied to the desired database connection, including the processing of closed connections. In turn, the go-gad/sal library cares about reusing the prepared statement in the context of a transaction. When the prepared statement is executed, the arguments are passed using variable binding, transparently to the developer.

Map structs to response messages

The go-gad/sal library cares about linking database response lines with response structures, table columns with structure fields:

type GetRubricsReq struct {}
func (r GetRubricReq) Query() string {
	return `SELECT * FROM rubrics`
}

type Rubric struct {
	ID       int64     `sql:"id"`
	CreateAt time.Time `sql:"created_at"`
	Title    string    `sql:"title"`
}
type GetRubricsResp []*Rubric

type Store interface {
	GetRubrics(ctx context.Context, req GetRubricsReq) (GetRubricsResp, error)
}

And if the database response is:

dev > SELECT * FROM rubrics;
 id |       created_at        | title
----+-------------------------+-------
  1 | 2012-03-13 11:17:23.609 | Tech
  2 | 2015-07-21 18:05:43.412 | Style
(2 rows)

Then the GetRubricsResp list will return to us, elements of which will be pointers to Rubric, where the fields are filled with values from the columns that correspond to the names of the tags.

Unmapped columns will be skipped if database response contains more fields than defined in the structure.

Value in list

type GetIDsReq struct {
	IDs  pq.StringArray `sql:"ids"`
}

func (r *GetIDsReq) Query() string {
	return `SELECT * FROM rubrics WHERE id = ANY(@ids)`
}

Multiple insert/update

type AddBooksToShelfReq struct {
	ShelfID     int64 `sql:"shelf_id"`
	BookID      pq.Int64Array `sql:"book_ids"`
}

func (c *AddBooksToShelfReq) Query() string {
	return `INSERT INTO shelf (shelf_id, book_id)
		SELECT @shelf_id, unnest(@book_ids);`
}

Non-standard data types

The database/sql package provides support for basic data types (strings, numbers). In order to handle data types such as an array or json in a request or response.

type DeleteAuthrosReq struct {
	Tags []int64 `sql:"tags"`
}

func (r *DeleteAuthorsReq) ProcessRow(rowMap sal.RowMap) {
	rowMap.Set("tags", pq.Array(r.Tags))
}

func (r *DeleteAuthorsReq) Query() string {
	return `DELETE FROM authors WHERE tags=ANY(@tags::UUID[])`
}

The same can be done with sql package predefined types

type DeleteAuthrosReq struct {
	Tags sql.Int64Array `sql:"tags"`
}

func (r *DeleteAuthorsReq) Query() string {
	return `DELETE FROM authors WHERE tags=ANY(@tags::UUID[])`
}

Nested types

Here we don't use struct tages because we map it in ProcessRow func to prevent misunderstanding for the same field names (id and name for Book and Author types)

type Author struct {
    ID   int64 
    Name string
}

type Book struct {
    ID   int64
    Name string
    Description string
    Author Author
}
type CreateBookReq struct {
    Book Book
}

func (r *CreateBookReq) ProcessRow(rowMap sal.RowMap) {
	rowMap.Set("author_id", r.Book.Author.ID)
	rowMap.Set("book_id",   r.Book.ID)
	rowMap.Set("book_name", r.Book.Name)
	rowMap.Set("book_descriprion", r.Book.Description)
}

func (r *CreateBookReq) Query() string {
	return `INSERT INTO books (id, author_id, name, description)
	VALUES (@book_id, @author_id, @book_name, @book_description)`
}

Transactions

To support transactions, the interface (Store) must be extended with the following methods:

type Store interface {
	BeginTx(ctx context.Context, opts *sql.TxOptions) (Store, error)
	sal.Txer
	...

The implementation of the methods will be generated. The BeginTx method uses the connection from the current sal.QueryHandler object and opens the transaction db.BeginTx(...); returns a new implementation object of the interface Store, but uses the resulting *sql.Tx object as a handle.

tx, err := client.BeginTx(ctx, nil)
_, err = tx.CreateAuthor(ctx, req1)
err = tx.UpdateAuthor(ctx, &req2)
err = tx.Tx().Commit(ctx)

Middleware

Hooks are provided for embedding tools.

When the hooks are executed, the context is filled with service keys with the following values:

  • ctx.Value(sal.ContextKeyTxOpened), boolean indicates whether the method is called in the context of a transaction or not.
  • ctx.Value(sal.ContextKeyOperationType), the string value of the operation type, "QueryRow", "Query", "Exec", "Commit", etc.
  • ctx.Value(sal.ContextKeyMethodName), the string value of the interface method, for example, "GetAuthors".

As arguments, the BeforeQueryFunc hook takes the sql string of the query and the argument req of the custom query method. The FinalizerFunc hook takes the variable err as an argument.

	beforeHook := func(ctx context.Context, query string, req interface{}) (context.Context, sal.FinalizerFunc) {
		start := time.Now()
		return ctx, func(ctx context.Context, err error) {
			log.Printf(
				"%q > Opeartion %q: %q with req %#v took [%v] inTx[%v] Error: %+v",
				ctx.Value(sal.ContextKeyMethodName),
				ctx.Value(sal.ContextKeyOperationType),
				query,
				req,
				time.Since(start),
				ctx.Value(sal.ContextKeyTxOpened),
				err,
			)
		}
	}

	client := NewStore(db, sal.BeforeQuery(beforeHook))

Limitations

Currently support only PostgreSQL.

Documentation

Index

Constants

View Source
const (
	// ContextKeyTxOpened is a key of bool value in context. If true it means that transaction is opened.
	ContextKeyTxOpened contextKey = iota
	// ContextKeyOperationType is a key of value that describes the operation type. See consts OperationType*.
	ContextKeyOperationType
	// ContextKeyMethodName contains the method name from user interface.
	ContextKeyMethodName
)

Variables

This section is empty.

Functions

func GetDests added in v0.12.0

func GetDests(cols []string, respMap RowMap) []interface{}

func ProcessQueryAndArgs

func ProcessQueryAndArgs(query string, reqMap RowMap) (string, []interface{})

ProcessQueryAndArgs process query with named args to driver specific query.

func QueryArgs

func QueryArgs(query string) (string, []string)

QueryArgs receives the query with named arguments and returns a query with posgtresql placeholders and a ordered slice named args.

Naive implementation.

Types

type BeforeQueryFunc added in v0.7.0

type BeforeQueryFunc func(ctx context.Context, query string, req interface{}) (context.Context, FinalizerFunc)

BeforeQueryFunc is called before the query execution but after the preparing stmts. Returns the FinalizerFunc.

type ClientOption added in v0.7.0

type ClientOption func(ctrl *Controller)

ClientOption sets to controller the optional parameters for clients.

func BeforeQuery added in v0.7.0

func BeforeQuery(before ...BeforeQueryFunc) ClientOption

BeforeQuery sets the BeforeQueryFunc that is executed before the query.

type Controller added in v0.6.0

type Controller struct {
	BeforeQuery []BeforeQueryFunc
	sync.RWMutex
	CacheStmts map[string]*sql.Stmt
}

Controller is a manager of query processing. Contains the stack of middlewares and cache of prepared statements.

func NewController added in v0.7.0

func NewController(options ...ClientOption) *Controller

NewController retunes a new object of Controller.

func (*Controller) PrepareStmt added in v0.8.0

func (ctrl *Controller) PrepareStmt(ctx context.Context, parent QueryHandler, qh QueryHandler, query string) (*sql.Stmt, error)

PrepareStmt returns the prepared statements. If stmt is presented in cache then it will be returned. if not, stmt will be prepared and put to cache.

type FinalizerFunc added in v0.7.0

type FinalizerFunc func(ctx context.Context, err error)

FinalizerFunc is executed after the query execution.

type OperationType added in v0.9.0

type OperationType int

OperationType is a datatype for operation types.

const (
	// OperationTypeQueryRow is a handler.Query operation and single row in response (like db.QueryRow).
	OperationTypeQueryRow OperationType = iota
	// OperationTypeQuery is a handler.Query operation.
	OperationTypeQuery
	// OperationTypeExec is a handler.Exec operation.
	OperationTypeExec
	// OperationTypeBegin is a start transaction operation, db.Begin().
	OperationTypeBegin
	// OperationTypeCommit is a commits the transaction operation, tx.Commit().
	OperationTypeCommit
	// OperationTypeRollback is a aborting the transaction operation, tx.Rollback().
	OperationTypeRollback
	// OperationTypePrepare is a prepare statements operation.
	OperationTypePrepare
	// OperationTypeStmt is a operation of prepare statements on transaction.
	OperationTypeStmt
)

func (OperationType) String added in v0.9.0

func (op OperationType) String() string

String returns the string name of operation.

type ProcessRower added in v0.5.0

type ProcessRower interface {
	ProcessRow(rowMap RowMap)
}

ProcessRower is an interface that defines the signature of method of request or response that can allow to write pre processor of RowMap values.

type GetAuthorsReq struct {
	ID   int64   `sql:"id"`
	Tags []int64 `sql:"tags"`
}

func (r GetAuthorsReq) ProcessRow(rowMap sal.RowMap) {
	rowMap.Set("tags", pq.Array(r.Tags))
}

As an argument method receives the RowMap object.

type QueryHandler added in v0.6.0

type QueryHandler interface {
	QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
	PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
}

QueryHandler describes the methods that are required to pass to constructor of the object implementation of user interface.

type RowMap added in v0.5.0

type RowMap map[string][]interface{}

RowMap contains mapping between column name in database and interface of value.

func (RowMap) AppendTo added in v0.12.0

func (rm RowMap) AppendTo(key string, val interface{})

func (RowMap) Get added in v0.12.0

func (rm RowMap) Get(key string) interface{}

func (RowMap) GetByIndex added in v0.12.0

func (rm RowMap) GetByIndex(key string, index int) interface{}

func (RowMap) Set added in v0.12.0

func (rm RowMap) Set(key string, val interface{})

type SqlTx added in v0.13.0

type SqlTx interface {
	QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
	PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
	StmtContext(ctx context.Context, stmt *sql.Stmt) *sql.Stmt
	Commit() error
	Rollback() error
}

type Transaction added in v0.7.0

type Transaction interface {
	QueryHandler
	StmtContext(ctx context.Context, stmt *sql.Stmt) *sql.Stmt
	Commit(ctx context.Context) error
	Rollback(ctx context.Context) error
}

Transaction is an interface that describes the method to work with transaction object. Signature is similar to sql.Tx. The difference is Commit and Rollback methods. Its methods work with context.

type TransactionBegin added in v0.6.0

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

TransactionBegin describes the signature of method of user interface to start transaction.

type Txer added in v0.10.0

type Txer interface {
	Tx() Transaction
}

Txer describes the method to return implementation of Transaction interface.

type WrappedTx added in v0.10.0

type WrappedTx struct {
	Tx SqlTx
	// contains filtered or unexported fields
}

WrappedTx is a struct that is an implementation of Transaction interface.

func NewWrappedTx added in v0.10.0

func NewWrappedTx(tx SqlTx, ctrl *Controller) *WrappedTx

NewWrappedTx returns the WrappedTx object.

func (*WrappedTx) Commit added in v0.10.0

func (wtx *WrappedTx) Commit(ctx context.Context) error

Commit commits the transaction.

func (*WrappedTx) ExecContext added in v0.10.0

func (wtx *WrappedTx) ExecContext(ctx context.Context, 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 (*WrappedTx) PrepareContext added in v0.10.0

func (wtx *WrappedTx) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)

PrepareContext creates a prepared statement for later queries or executions. Multiple queries or executions may be run concurrently from the returned statement. The caller must call the statement's Close method when the statement is no longer needed.

The provided context is used for the preparation of the statement, not for the execution of the statement.

func (*WrappedTx) QueryContext added in v0.10.0

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

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

func (*WrappedTx) Rollback added in v0.10.0

func (wtx *WrappedTx) Rollback(ctx context.Context) error

Rollback aborts the transaction.

func (*WrappedTx) StmtContext added in v0.10.0

func (wtx *WrappedTx) StmtContext(ctx context.Context, stmt *sql.Stmt) *sql.Stmt

Stmt returns a transaction-specific prepared statement from an existing statement.

Directories

Path Synopsis
examples
bookstore
Code generated by SalGen.
Code generated by SalGen.
profile/storage
Code generated by SalGen.
Code generated by SalGen.

Jump to

Keyboard shortcuts

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