pegi

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Feb 10, 2022 License: MIT Imports: 7 Imported by: 0

README

Pegi

PkgGoDev Build Status

A sqlx wrapper mainly for PostgreSQL.

go get -u -v github.com/kakilangit/pegi

Example:

package main

import (
	"context"
	"database/sql"
	"log"

	"github.com/jmoiron/sqlx"
	"github.com/kakilangit/pegi"
)

func main() {
	db := pegi.NewDB(&sqlx.DB{}, &sql.TxOptions{})
	if err := db.RunInTransaction(context.Background(), func(ctx context.Context) error {
		var id string
		if err := db.GetAccess(ctx).Get(&id, "SELECT id FROM users WHERE name = ?", "name"); err != nil {
			return err
		}

		_, err := db.QueryBuilder(ctx).Update("users").Set("address", "new address").Where("id = ?", id).Exec()
		return err
	}); err != nil {
		log.Fatal(err)
	}
}


Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoTransactionCtx is error when no transaction in context.
	ErrNoTransactionCtx = errors.New("no transaction found in context")

	// ErrTransactionAlreadyStarted is error when you try to start already started transaction, to start sub transaction, see RunInSubTransaction.
	ErrTransactionAlreadyStarted = errors.New("transaction is already started")
)

Functions

func BeginTransaction

func BeginTransaction(ctx context.Context, db *sqlx.DB, opts *sql.TxOptions) (context.Context, error)

BeginTransaction from the context.

func CommitTransaction

func CommitTransaction(ctx context.Context) error

CommitTransaction from the context.

func ErrIgnoreNoRows

func ErrIgnoreNoRows(err error) error

ErrIgnoreNoRows ignores sql.ErrNoRows.

func GetTransaction

func GetTransaction(ctx context.Context) (*sqlx.Tx, error)

GetTransaction will get transaction from context.

func LowerColumn

func LowerColumn(column string) string

LowerColumn returns PostgreSQL column name wrapped with LOWER function. Unsafe if using it manually in query.

func MatchesAnyPrefixSuffix

func MatchesAnyPrefixSuffix(value string) string

MatchesAnyPrefixSuffix formats value wrapped with % % that will match any prefix or suffix. Unsafe if using it manually in query.

func RollbackTransaction

func RollbackTransaction(ctx context.Context) error

RollbackTransaction from the context.

func RunInTransaction

func RunInTransaction(ctx context.Context, db *sqlx.DB, opts *sql.TxOptions, f TxFunc) error

RunInTransaction run a function within a database transaction.

Types

type Access added in v0.1.1

type Access struct {
	Accessor
}

Access implements Accessor.

type Accessor

type Accessor interface {
	sqlx.Queryer
	sqlx.Execer
	sqlx.ExtContext
	sqlx.Preparer
	sqlx.PreparerContext
	Select(dest interface{}, query string, args ...interface{}) error
	SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
	Get(dest interface{}, query string, args ...interface{}) error
	GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
	NamedQuery(query string, arg interface{}) (*sqlx.Rows, error)
	NamedExec(query string, arg interface{}) (sql.Result, error)
}

Accessor interface is common interface of sqlx.DB and sqlx.Tx types to access the sqlx full capabilities.

type DB

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

DB will hold DB object.

func NewDB

func NewDB(db *sqlx.DB, opts *sql.TxOptions) *DB

NewDB construct new DB.

func (DB) A

func (db DB) A(ctx context.Context) Access

A is shorter method for GetAccess

func (DB) Begin

func (db DB) Begin(ctx context.Context) (context.Context, error)

Begin is abstraction of tx.Begin.

func (DB) Commit

func (db DB) Commit(ctx context.Context) error

Commit is abstraction of tx.Commit.

func (DB) GetAccess added in v0.1.1

func (db DB) GetAccess(ctx context.Context) Access

GetAccess will get Accessor interface from context.

func (DB) Q

Q is shorter method for QueryBuilder

func (DB) QueryBuilder

func (db DB) QueryBuilder(ctx context.Context) squirrel.StatementBuilderType

QueryBuilder is PostgreSQL wrapper for squirrel. Chaining will not have side effect, so you have to use the returning value as a new builder.

Example:

repo := NewDB(db)

id := "id"
qb := repo.QueryBuilder(context.Background()).Select("*").From("table")
// side effect free
qb = qb.Where(`r.id = ?`, id)
query, args, err := qb.ToSql()
if err != nil {
	panic(err)
}

var result string
if err := repo.QueryRowx(query, args...).Scan(&result); err != nil {
	panic(err)
}

fmt.Println(result)

func (DB) Rollback

func (db DB) Rollback(ctx context.Context) error

Rollback is abstraction of tx.Rollback.

func (DB) RunInSubTransaction

func (db DB) RunInSubTransaction(ctx context.Context, f TxFunc) error

RunInSubTransaction will spawn sub transaction.

func (DB) RunInTransaction

func (db DB) RunInTransaction(ctx context.Context, f TxFunc) error

RunInTransaction is wrapper around the transaction.

type TransactionRunner

type TransactionRunner interface {
	RunInTransaction(ctx context.Context, f TxFunc) error
}

TransactionRunner is transaction runner.

A transaction abstraction without exposing database or library implementation details and reducing cognitive load using closure.

This abstraction can be used to build deep modules.

See how:

- the repository hold data layer and service/core/business logic not knowing the concrete database implementation, pointer to db, or even specific library.

- the repository can be used inside or outside the transaction.

- changing repository function implementation will not affect repository signature.

Example:

type Repository interface {
	GetSegment(ctx context.Context, id int) (string, error)
	SaveRecipe(ctx context.Context, recipe string, segment string) error
}

type Publisher interface {
	Publish(ctx context.Context, eventType string, recipe ...string) error
}

type BusinessLogic struct {
	TransactionRunner
	repo      Repository
	publisher Publisher
}

s := BusinessLogic{}
id := 2
recipe := "recipe"
err := s.RunInTransaction(context.Background(), func(ctx context.Context) error {
	segment, err := s.repo.GetSegment(ctx, id)
	if err != nil {
		return err
	}

	if err := s.repo.SaveRecipe(ctx, segment, recipe);err != nil {
		return err
	}

	if err := s.publisher.Publish(ctx, "created", recipe); err != nil {
		return err
	}

	return nil
})

In the example:

If in the first iteration, we have repo.GetSegment making db calls, changing implementation using a hashmap (in-memory cache), or even using a different persistent layer technology, we don't need to change signatures or create other services.

Similar to publisher.Publish, the core logic doesn't care about the implementation of how publisher Publish events, with outbox workers or directly to Kafka the signature will be the same.

"The module should be deep." ~ John Ousterhout's A Philosophy of Software Design

type Transactioner

type Transactioner interface {
	Begin(ctx context.Context) (context.Context, error)
	Rollback(ctx context.Context) error
	Commit(ctx context.Context) error
}

Transactioner is abstraction of transaction without closure. with this abstraction the caller need to write transaction boilerplate in order to avoid closure.

type TxFunc

type TxFunc func(ctx context.Context) error

TxFunc is transaction signature function.

Jump to

Keyboard shortcuts

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