sqldb

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

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

Go to latest
Published: Apr 21, 2024 License: MIT Imports: 16 Imported by: 5

README

go-sqldb

Go Reference license

This package started out as an extension wrapper of github.com/jmoiron/sqlx but turned into a complete rewrite using the same philosophy of representing table rows as Go structs.

It has been used and refined for years in production by domonda using the database driver github.com/lib/pq.

The design patterns evolved mostly through discovery led by the desire to minimize boilerplate code while maintaining the full power of SQL.

Philosophy

  • Use reflection to map db rows to structs, but not as full blown ORM that replaces SQL queries (just as much ORM to increase productivity but not alienate developers who like the full power of SQL)
  • Transactions are run in callback functions that can be nested
  • Option to store the db connection and transactions in the context argument to pass it down into nested functions

Database drivers

Usage

Creating a connection

The connection is pinged with the passed context and only returned when there was no error from the ping:

config := &sqldb.Config{
    Driver:   "postgres",
    Host:     "localhost",
    User:     "postgres",
    Database: "demo",
    Extra:    map[string]string{"sslmode": "disable"},
}

fmt.Println("Connecting to:", config.ConnectURL())

conn, err := pqconn.New(context.Background(), config)
Struct field mapping

Every new connection initially uses DefaultStructFieldTagNaming

package sqldb

// DefaultStructFieldTagNaming provides the default StructFieldTagNaming
// using "db" as NameTag and IgnoreStructField as UntaggedNameFunc.
// Implements StructFieldNamer.
var DefaultStructFieldTagNaming = StructFieldTagNaming{
	NameTag:          "db",
	IgnoreName:       "-",
	UntaggedNameFunc: IgnoreStructField,
}

Use a different mapping:

conn = conn.WithStructFieldNamer(sqldb.StructFieldTagNaming{
    NameTag:          "col",
    IgnoreName:       "_ignore_",
    UntaggedNameFunc: sqldb.ToSnakeCase,
})
Exec SQL without reading rows
err = conn.Exec(`delete from public.user where id = $1`, userID)
Single row query
type User struct {
	ID    uu.ID  `db:"id,pk"`
	Email string `db:"email"`
	Name  string `db:"name"`
}

var user User
err = conn.QueryRow(`select * from public.user where id = $1`, userID).ScanStruct(&user)

var userExists bool
err = conn.QueryRow(`select exists(select from public.user where email = $1)`, userEmail).Scan(&userExists)
Multi rows query
var users []*User
err = conn.QueryRows(`select * from public.user`).ScanStructSlice(&users)

var userEmails []string
err = conn.QueryRows(`select email from public.user`).ScanSlice(&userEmails)

// Use reflection for callback function arguments
err = conn.QueryRows(`select name, email from public.user`).ForEachRowCall(
    func(name, email string) {
        fmt.Printf("%q <%s>\n", name, email)
    },
)

err = conn.QueryRows(`select name, email from public.user`).ForEachRow(
    func(row sqldb.RowScanner) error {
        var name, email string
        err := row.Scan(&name, &email)
        if err != nil {
            return err
        }
        _, err = fmt.Printf("%q <%s>\n", name, email)
        return err
    },
)
Insert rows
newUser := &User{ /* ... */ }

err = conn.InsertStruct("public.user", newUser)

// Use column defaults for insert instead of struct fields
err = conn.InsertStructIgnoreColumns("public.user", newUser, "id", "created_at")

// Upsert uses columns marked as primary key like `db:"id,pk"`
err = conn.UpsertStructIgnoreColumns("public.user", newUser, "created_at")

// Without structs
err = conn.Insert("public.user", sqldb.Values{
    "name":  "Erik Unger",
    "email": "erik@domonda.com",
})
Transactions
txOpts := &sql.TxOptions{Isolation: sql.LevelWriteCommitted}

err = sqldb.Transaction(conn, txOpts, func(tx sqldb.Connection) error {
    err := tx.Exec("...")
    if err != nil {
        return err // roll back tx
    }
    return tx.Exec("...")
})
Using the context

Saving a context in a struct is an antipattern in Go but it turns out that it allows neat call chaining pattern.

ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()

// Note that this timout is a deadline and does not restart for every query
err = conn.WithContext(ctx).Exec("...")

// Usually the context comes from some top-level handler
_ = http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
    // Pass request cancellation through to db query
    err := conn.WithContext(request.Context()).Exec("...")
    if err != nil {
        http.Error(response, err.Error(), http.StatusInternalServerError)
        return
    }
    response.Write([]byte("OK"))
})
Putting it all together with the db package

The github.com/domonda/go-sqldb/db package enables a design pattern where a "current" db connection or transaction can be stored in the context and then retrieved by nested functions from the context without having to know if this connection is a transaction or not. This allows re-using the same functions within transactions or standalone.

// Configure the global parent connection
db.SetConn(conn)

// db.Conn(ctx) is the standard pattern
// to retrieve a connection anywhere in the code base
err = db.Conn(ctx).Exec("...")

Here if GetUserOrNil will use the global db connection if no other connection is stored in the context.

But when called from within the function passed to db.Transaction it will re-use the transaction saved in the context.

func GetUserOrNil(ctx context.Context, userID uu.ID) (user *User, err error) {
	err = db.Conn(ctx).QueryRow(
		`select * from public.user where id = $1`,
		userID,
	).ScanStruct(&user)
	if err != nil {
		return nil, db.ReplaceErrNoRows(err, nil)
	}
	return user, nil
}

func DoStuffWithinTransation(ctx context.Context, userID uu.ID) error {
	return db.Transaction(ctx, func(ctx context.Context) error {
		user, err := GetUserOrNil(ctx, userID)
		if err != nil {
			return err
		}
		if user == nil {
			return db.Conn(ctx).Exec("...")
		}
		return db.Conn(ctx).Exec("...")
	})
}

Small helpers:

err = db.TransactionOpts(ctx, &sql.TxOptions{ReadOnly: true}, func(context.Context) error { ... })

err = db.TransactionReadOnly(ctx, func(context.Context) error { ... })

// Execute the passed function without transaction
err = db.DebugNoTransaction(ctx, func(context.Context) error { ... })

More sophisticated transactions:

Serialized transactions are typically necessary when an insert depends on a previous select within the transaction, but that pre-insert select can't lock the table like it's possible with SELECT FOR UPDATE.

err = db.SerializedTransaction(ctx, func(context.Context) error { ... })

TransactionSavepoint executes txFunc within a database transaction or uses savepoints for rollback. If the passed context already has a database transaction connection, then a savepoint with a random name is created before the execution of txFunc. If txFunc returns an error, then the transaction is rolled back to the savepoint but the transaction from the context is not rolled back. If the passed context does not have a database transaction connection, then Transaction(ctx, txFunc) is called without savepoints.

err = db.TransactionSavepoint(ctx, func(context.Context) error { ... })

Documentation

Index

Constants

View Source
const (
	// ErrWithinTransaction is returned by methods
	// that are not allowed within DB transactions
	// when the DB connection is a transaction.
	ErrWithinTransaction sentinelError = "within a transaction"

	// ErrNotWithinTransaction is returned by methods
	// that are are only allowed within DB transactions
	// when the DB connection is not a transaction.
	ErrNotWithinTransaction sentinelError = "not within a transaction"

	ErrNullValueNotAllowed sentinelError = "null value not allowed"
)

Variables

View Source
var DefaultStructFieldMapping = NewTaggedStructFieldMapping()

DefaultStructFieldMapping provides the default StructFieldTagNaming using "db" as NameTag and IgnoreStructField as UntaggedNameFunc. Implements StructFieldMapper.

View Source
var ToSnakeCase = strutil.ToSnakeCase

ToSnakeCase converts s to snake case by lower casing everything and inserting '_' before every new upper case character in s. Whitespace, symbol, and punctuation characters will be replace by '_'.

Functions

func CheckTxOptionsCompatibility

func CheckTxOptionsCompatibility(parent, child *sql.TxOptions, defaultIsolation sql.IsolationLevel) error

CheckTxOptionsCompatibility returns an error if the parent transaction options are less strict than the child options.

func IgnoreStructField

func IgnoreStructField(string) string

IgnoreStructField can be used as TaggedStructFieldMapping.UntaggedNameFunc to ignore fields that don't have TaggedStructFieldMapping.NameTag.

func IsNull

func IsNull(val any) bool

IsNull returns if val would be interpreted as NULL by a SQL driver. It checks if val is nil, implements driver.Valuer or is a nil pointer, slice, or map.

func IsNullOrZero

func IsNullOrZero(val any) bool

IsNullOrZero returns if val would be interpreted as NULL by a SQL driver or if it is the types zero value or if it implements interface{ IsZero() bool } returning true.

func IsOtherThanErrNoRows

func IsOtherThanErrNoRows(err error) bool

IsOtherThanErrNoRows returns true if the passed error is not nil and does not unwrap to, or is sql.ErrNoRows.

func IsolatedTransaction

func IsolatedTransaction(parentConn Connection, opts *sql.TxOptions, txFunc func(tx Connection) error) (err error)

IsolatedTransaction executes txFunc within a database transaction that is passed in to txFunc as tx Connection. IsolatedTransaction returns all errors from txFunc or transaction commit errors happening after txFunc. If parentConn is already a transaction, a brand new transaction will begin on the parent's connection. Errors and panics from txFunc will rollback the transaction. Recovered panics are re-paniced and rollback errors after a panic are logged with ErrLogger.

func NextTransactionNo

func NextTransactionNo() uint64

NextTransactionNo returns the next globally unique number for a new transaction in a threadsafe way.

Use Connection.TransactionNo() to get the number from a transaction connection.

func ReplaceErrNoRows

func ReplaceErrNoRows(err, replacement error) error

ReplaceErrNoRows returns the passed replacement error if errors.Is(err, sql.ErrNoRows), else the passed err is returned unchanged.

func SanitizeString

func SanitizeString(s string) string

SanitizeString returns valid UTF-8 only with printable characters.

func SanitizeStringTrimSpace

func SanitizeStringTrimSpace(s string) string

SanitizeStringTrimSpace returns valid UTF-8 only with printable characters with leading and trailing whitespace trimmed away.

func ScanDriverValue

func ScanDriverValue(destPtr any, value driver.Value) error

ScanDriverValue scans a driver.Value into destPtr.

func Transaction

func Transaction(parentConn Connection, opts *sql.TxOptions, txFunc func(tx Connection) error) (err error)

Transaction executes txFunc within a database transaction that is passed in to txFunc as tx Connection. Transaction returns all errors from txFunc or transaction commit errors happening after txFunc. If parentConn is already a transaction, then it is passed through to txFunc unchanged as tx Connection and no parentConn.Begin, Commit, or Rollback calls will occour within this Transaction call. An error is returned, if the requested transaction options passed via opts are stricter than the options of the parent transaction. Errors and panics from txFunc will rollback the transaction if parentConn was not already a transaction. Recovered panics are re-paniced and rollback errors after a panic are logged with ErrLogger.

Types

type AnyValue

type AnyValue struct {
	Val any
}

AnyValue wraps a driver.Value and is useful for generic code that can handle unknown column types.

AnyValue implements the following interfaces:

database/sql.Scanner
database/sql/driver.Valuer
fmt.Stringer
fmt.GoStringer

When scanned, Val can have one of the following underlying types:

int64
float64
bool
[]byte
string
time.Time
nil - for NULL values

func (AnyValue) GoString

func (any AnyValue) GoString() string

GoString returns a Go representation of the wrapped value.

func (*AnyValue) Scan

func (any *AnyValue) Scan(val any) error

Scan implements the database/sql.Scanner interface.

func (AnyValue) String

func (any AnyValue) String() string

String returns the value formatted as string using fmt.Sprint except when it's of type []byte and valid UTF-8, then it is directly converted into a string.

func (AnyValue) Value

func (any AnyValue) Value() (driver.Value, error)

Value implements the driver database/sql/driver.Valuer interface.

type ColumnFilter

type ColumnFilter interface {
	IgnoreColumn(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool
}
var IgnoreDefault ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
	return flags.Default()
})
var IgnoreNull ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
	return IsNull(fieldValue)
})
var IgnoreNullOrZero ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
	return IsNullOrZero(fieldValue)
})
var IgnoreNullOrZeroDefault ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
	return flags.Default() && IsNullOrZero(fieldValue)
})
var IgnorePrimaryKey ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
	return flags.PrimaryKey()
})
var IgnoreReadOnly ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
	return flags.ReadOnly()
})

func IgnoreColumns

func IgnoreColumns(names ...string) ColumnFilter

func IgnoreFlags

func IgnoreFlags(ignore FieldFlag) ColumnFilter

func IgnoreStructFields

func IgnoreStructFields(names ...string) ColumnFilter

func OnlyColumns

func OnlyColumns(names ...string) ColumnFilter

func OnlyStructFields

func OnlyStructFields(names ...string) ColumnFilter

type ColumnFilterFunc

type ColumnFilterFunc func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool

func (ColumnFilterFunc) IgnoreColumn

func (f ColumnFilterFunc) IgnoreColumn(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool

type Config

type Config struct {
	Driver   string            `json:"driver"`
	Host     string            `json:"host"`
	Port     uint16            `json:"port,omitempty"`
	User     string            `json:"user,omitempty"`
	Password string            `json:"password,omitempty"`
	Database string            `json:"database"`
	Extra    map[string]string `json:"misc,omitempty"`

	// MaxOpenConns sets the maximum number of open connections to the database.
	//
	// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than
	// MaxIdleConns, then MaxIdleConns will be reduced to match the new
	// MaxOpenConns limit.
	//
	// If MaxOpenConns <= 0, then there is no limit on the number of open connections.
	// The default is 0 (unlimited).
	MaxOpenConns int `json:"maxOpenConns,omitempty"`

	// MaxIdleConns sets the maximum number of connections in the idle
	// connection pool.
	//
	// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns,
	// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit.
	//
	// If MaxIdleConns <= 0, no idle connections are retained.
	//
	// The default max idle connections is currently 2. This may change in
	// a future release.
	MaxIdleConns int `json:"maxIdleConns,omitempty"`

	// ConnMaxLifetime sets the maximum amount of time a connection may be reused.
	//
	// Expired connections may be closed lazily before reuse.
	//
	// If ConnMaxLifetime <= 0, connections are not closed due to a connection's age.
	ConnMaxLifetime time.Duration `json:"connMaxLifetime,omitempty"`

	DefaultIsolationLevel sql.IsolationLevel `json:"-"`
	Err                   error              `json:"-"`
}

Config for a connection. For tips see https://www.alexedwards.net/blog/configuring-sqldb

func (*Config) Connect

func (c *Config) Connect(ctx context.Context) (*sql.DB, error)

Connect opens a new sql.DB connection, sets all Config values and performs a ping with ctx. The sql.DB will be returned if the ping was successful.

func (*Config) ConnectURL

func (c *Config) ConnectURL() string

ConnectURL for connecting to a database

func (*Config) Validate

func (c *Config) Validate() error

Validate returns Config.Err if it is not nil or an error if the Config does not have a Driver, Host, or Database.

type Connection

type Connection interface {
	// Context that all connection operations use.
	// See also WithContext.
	Context() context.Context

	// WithContext returns a connection that uses the passed
	// context for its operations.
	WithContext(ctx context.Context) Connection

	// WithStructFieldMapper returns a copy of the connection
	// that will use the passed StructFieldMapper.
	WithStructFieldMapper(StructFieldMapper) Connection

	// StructFieldMapper used by methods of this Connection.
	StructFieldMapper() StructFieldMapper

	// Ping returns an error if the database
	// does not answer on this connection
	// with an optional timeout.
	// The passed timeout has to be greater zero
	// to be considered.
	Ping(timeout time.Duration) error

	// Stats returns the sql.DBStats of this connection.
	Stats() sql.DBStats

	// Config returns the configuration used
	// to create this connection.
	Config() *Config

	// ValidateColumnName returns an error
	// if the passed name is not valid for a
	// column of the connection's database.
	ValidateColumnName(name string) error

	// Now returns the result of the SQL now()
	// function for the current connection.
	// Useful for getting the timestamp of a
	// SQL transaction for use in Go code.
	Now() (time.Time, error)

	// Exec executes a query with optional args.
	Exec(query string, args ...any) error

	// Insert a new row into table using the values.
	Insert(table string, values Values) error

	// InsertUnique inserts a new row into table using the passed values
	// or does nothing if the onConflict statement applies.
	// Returns if a row was inserted.
	InsertUnique(table string, values Values, onConflict string) (inserted bool, err error)

	// InsertReturning inserts a new row into table using values
	// and returns values from the inserted row listed in returning.
	InsertReturning(table string, values Values, returning string) RowScanner

	// InsertStruct inserts a new row into table using the connection's
	// StructFieldMapper to map struct fields to column names.
	// Optional ColumnFilter can be passed to ignore mapped columns.
	InsertStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error

	// InsertStructs inserts a slice or array of structs
	// as new rows into table using the connection's
	// StructFieldMapper to map struct fields to column names.
	// Optional ColumnFilter can be passed to ignore mapped columns.
	//
	// TODO optimized version with single query if possible
	// split into multiple queries depending or maxArgs for query
	InsertStructs(table string, rowStructs any, ignoreColumns ...ColumnFilter) error

	// InsertUniqueStruct inserts a new row into table using the connection's
	// StructFieldMapper to map struct fields to column names.
	// Optional ColumnFilter can be passed to ignore mapped columns.
	// Does nothing if the onConflict statement applies
	// and returns if a row was inserted.
	InsertUniqueStruct(table string, rowStruct any, onConflict string, ignoreColumns ...ColumnFilter) (inserted bool, err error)

	// Update table rows(s) with values using the where statement with passed in args starting at $1.
	Update(table string, values Values, where string, args ...any) error

	// UpdateReturningRow updates a table row with values using the where statement with passed in args starting at $1
	// and returning a single row with the columns specified in returning argument.
	UpdateReturningRow(table string, values Values, returning, where string, args ...any) RowScanner

	// UpdateReturningRows updates table rows with values using the where statement with passed in args starting at $1
	// and returning multiple rows with the columns specified in returning argument.
	UpdateReturningRows(table string, values Values, returning, where string, args ...any) RowsScanner

	// UpdateStruct updates a row in a table using the exported fields
	// of rowStruct which have a `db` tag that is not "-".
	// If restrictToColumns are provided, then only struct fields with a `db` tag
	// matching any of the passed column names will be used.
	// The struct must have at least one field with a `db` tag value having a ",pk" suffix
	// to mark primary key column(s).
	UpdateStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error

	// UpsertStruct upserts a row to table using the exported fields
	// of rowStruct which have a `db` tag that is not "-".
	// If restrictToColumns are provided, then only struct fields with a `db` tag
	// matching any of the passed column names will be used.
	// The struct must have at least one field with a `db` tag value having a ",pk" suffix
	// to mark primary key column(s).
	// If inserting conflicts on the primary key column(s), then an update is performed.
	UpsertStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error

	// QueryRow queries a single row and returns a RowScanner for the results.
	QueryRow(query string, args ...any) RowScanner

	// QueryRows queries multiple rows and returns a RowsScanner for the results.
	QueryRows(query string, args ...any) RowsScanner

	// IsTransaction returns if the connection is a transaction
	IsTransaction() bool

	// TransactionNo returns the globally unique number of the transaction
	// or zero if the connection is not a transaction.
	// Implementations should use the package function NextTransactionNo
	// to aquire a new number in a threadsafe way.
	TransactionNo() uint64

	// TransactionOptions returns the sql.TxOptions of the
	// current transaction and true as second result value,
	// or false if the connection is not a transaction.
	TransactionOptions() (*sql.TxOptions, bool)

	// Begin a new transaction.
	// If the connection is already a transaction then a brand
	// new transaction will begin on the parent's connection.
	// The passed no will be returnd from the transaction's
	// Connection.TransactionNo method.
	// Implementations should use the package function NextTransactionNo
	// to aquire a new number in a threadsafe way.
	Begin(opts *sql.TxOptions, no uint64) (Connection, error)

	// Commit the current transaction.
	// Returns ErrNotWithinTransaction if the connection
	// is not within a transaction.
	Commit() error

	// Rollback the current transaction.
	// Returns ErrNotWithinTransaction if the connection
	// is not within a transaction.
	Rollback() error

	// ListenOnChannel will call onNotify for every channel notification
	// and onUnlisten if the channel gets unlistened
	// or the listener connection gets closed for some reason.
	// It is valid to pass nil for onNotify or onUnlisten to not get those callbacks.
	// Note that the callbacks are called in sequence from a single go routine,
	// so callbacks should offload long running or potentially blocking code to other go routines.
	// Panics from callbacks will be recovered and logged.
	ListenOnChannel(channel string, onNotify OnNotifyFunc, onUnlisten OnUnlistenFunc) error

	// UnlistenChannel will stop listening on the channel.
	// An error is returned, when the channel was not listened to
	// or the listener connection is closed.
	UnlistenChannel(channel string) error

	// IsListeningOnChannel returns if a channel is listened to.
	IsListeningOnChannel(channel string) bool

	// Close the connection.
	// Transactions will be rolled back.
	Close() error
}

Connection represents a database connection or transaction

func ConnectionWithError

func ConnectionWithError(ctx context.Context, err error) Connection

ConnectionWithError returns a dummy Connection where all methods return the passed error.

type ErrCheckViolation

type ErrCheckViolation struct {
	Constraint string
}

func (ErrCheckViolation) Error

func (e ErrCheckViolation) Error() string

func (ErrCheckViolation) Unwrap

func (e ErrCheckViolation) Unwrap() error

type ErrExclusionViolation

type ErrExclusionViolation struct {
	Constraint string
}

func (ErrExclusionViolation) Error

func (e ErrExclusionViolation) Error() string

func (ErrExclusionViolation) Unwrap

func (e ErrExclusionViolation) Unwrap() error

type ErrForeignKeyViolation

type ErrForeignKeyViolation struct {
	Constraint string
}

func (ErrForeignKeyViolation) Error

func (e ErrForeignKeyViolation) Error() string

func (ErrForeignKeyViolation) Unwrap

func (e ErrForeignKeyViolation) Unwrap() error

type ErrIntegrityConstraintViolation

type ErrIntegrityConstraintViolation struct {
	Constraint string
}

func (ErrIntegrityConstraintViolation) Error

type ErrNotNullViolation

type ErrNotNullViolation struct {
	Constraint string
}

func (ErrNotNullViolation) Error

func (e ErrNotNullViolation) Error() string

func (ErrNotNullViolation) Unwrap

func (e ErrNotNullViolation) Unwrap() error

type ErrRaisedException

type ErrRaisedException struct {
	Message string
}

func (ErrRaisedException) Error

func (e ErrRaisedException) Error() string

type ErrRestrictViolation

type ErrRestrictViolation struct {
	Constraint string
}

func (ErrRestrictViolation) Error

func (e ErrRestrictViolation) Error() string

func (ErrRestrictViolation) Unwrap

func (e ErrRestrictViolation) Unwrap() error

type ErrUniqueViolation

type ErrUniqueViolation struct {
	Constraint string
}

func (ErrUniqueViolation) Error

func (e ErrUniqueViolation) Error() string

func (ErrUniqueViolation) Unwrap

func (e ErrUniqueViolation) Unwrap() error

type FieldFlag

type FieldFlag uint

FieldFlag is a bitmask for special properties of how struct fields relate to database columns.

const (
	// FieldFlagPrimaryKey marks a field as primary key
	FieldFlagPrimaryKey FieldFlag = 1 << iota

	// FieldFlagReadOnly marks a field as read-only
	FieldFlagReadOnly

	// FieldFlagDefault marks a field as having a column default value
	FieldFlagDefault
)

func (FieldFlag) Default

func (f FieldFlag) Default() bool

Default indicates if FieldFlagDefault is set

func (FieldFlag) PrimaryKey

func (f FieldFlag) PrimaryKey() bool

PrimaryKey indicates if FieldFlagPrimaryKey is set

func (FieldFlag) ReadOnly

func (f FieldFlag) ReadOnly() bool

ReadOnly indicates if FieldFlagReadOnly is set

type Logger

type Logger interface {
	Printf(format string, v ...any)
}

Logger has a Printf method used for logging information that could not be returned by any of the package functions directly.

var ErrLogger Logger = log.New(os.Stderr, "sqldb", log.LstdFlags)

ErrLogger will be used to log errors that could not be returned by any of the package functions directly.

type Nullable

type Nullable[T any] struct {
	Val   T
	Valid bool
}

func (*Nullable[T]) Scan

func (n *Nullable[T]) Scan(value any) error

Scan implements the sql.Scanner interface.

func (Nullable[T]) Value

func (n Nullable[T]) Value() (driver.Value, error)

Value implements the driver sql/driver.Valuer interface.

type OnNotifyFunc

type OnNotifyFunc func(channel, payload string)

OnNotifyFunc is a callback type passed to Connection.ListenOnChannel

type OnUnlistenFunc

type OnUnlistenFunc func(channel string)

OnUnlistenFunc is a callback type passed to Connection.ListenOnChannel

type RowScanner

type RowScanner interface {
	// Scan values of a row into dest variables, which must be passed as pointers.
	Scan(dest ...any) error

	// ScanStruct scans values of a row into a dest struct which must be passed as pointer.
	ScanStruct(dest any) error

	// ScanValues returns the values of a row exactly how they are
	// passed from the database driver to an sql.Scanner.
	// Byte slices will be copied.
	ScanValues() ([]any, error)

	// ScanStrings scans the values of a row as strings.
	// Byte slices will be interpreted as strings,
	// nil (SQL NULL) will be converted to an empty string,
	// all other types are converted with fmt.Sprint(src).
	ScanStrings() ([]string, error)

	// Columns returns the column names.
	Columns() ([]string, error)
}

RowScanner scans the values from a single row.

func RowScannerWithError

func RowScannerWithError(err error) RowScanner

RowScannerWithError returns a dummy RowScanner where all methods return the passed error.

type RowsScanner

type RowsScanner interface {
	// ScanSlice scans one value per row into one slice element of dest.
	// dest must be a pointer to a slice with a row value compatible element type.
	// In case of zero rows, dest will be set to nil and no error will be returned.
	// In case of an error, dest will not be modified.
	// It is an error to query more than one column.
	ScanSlice(dest any) error

	// ScanStructSlice scans every row into the struct fields of dest slice elements.
	// dest must be a pointer to a slice of structs or struct pointers.
	// In case of zero rows, dest will be set to nil and no error will be returned.
	// In case of an error, dest will not be modified.
	// Every mapped struct field must have a corresponding column in the query results.
	ScanStructSlice(dest any) error

	// ScanAllRowsAsStrings scans the values of all rows as strings.
	// Byte slices will be interpreted as strings,
	// nil (SQL NULL) will be converted to an empty string,
	// all other types are converted with fmt.Sprint.
	// If true is passed for headerRow, then a row
	// with the column names will be prepended.
	ScanAllRowsAsStrings(headerRow bool) (rows [][]string, err error)

	// Columns returns the column names.
	Columns() ([]string, error)

	// ForEachRow will call the passed callback with a RowScanner for every row.
	// In case of zero rows, no error will be returned.
	ForEachRow(callback func(RowScanner) error) error

	// ForEachRowCall will call the passed callback with scanned values or a struct for every row.
	// If the callback function has a single struct or struct pointer argument,
	// then RowScanner.ScanStruct will be used per row,
	// else RowScanner.Scan will be used for all arguments of the callback.
	// If the function has a context.Context as first argument,
	// then the context of the query call will be passed on.
	// The callback can have no result or a single error result value.
	// If a non nil error is returned from the callback, then this error
	// is returned immediately by this function without scanning further rows.
	// In case of zero rows, no error will be returned.
	ForEachRowCall(callback any) error
}

RowsScanner scans the values from multiple rows.

func RowsScannerWithError

func RowsScannerWithError(err error) RowsScanner

RowsScannerWithError returns a dummy RowsScanner where all methods return the passed error.

type StringScannable

type StringScannable string

StringScannable implements the sql.Scanner interface and converts all scanned values to string. Byte slices will be interpreted as strings, nil (SQL NULL) will be converted to an empty string, all other types are converted with fmt.Sprint(src).

func (*StringScannable) Scan

func (s *StringScannable) Scan(src any) error

Scan implements implements the sql.Scanner interface and converts all scanned values to string. Byte slices will be interpreted as strings, nil (SQL NULL) will be converted to an empty string, all other types are converted with fmt.Sprint(src).

type StructFieldMapper

type StructFieldMapper interface {
	// MapStructField returns the column name for a reflected struct field
	// and flags for special column properies.
	// If false is returned for use then the field is not mapped.
	// An empty name and true for use indicates an embedded struct
	// field whose fields should be recursively mapped.
	MapStructField(field reflect.StructField) (table, column string, flags FieldFlag, use bool)
}

StructFieldMapper is used to map struct type fields to column names and indicate special column properies via flags.

type TaggedStructFieldMapping

type TaggedStructFieldMapping struct {

	// NameTag is the struct field tag to be used as column name
	NameTag string

	// Ignore will cause a struct field to be ignored if it has that name
	Ignore string

	PrimaryKey string
	ReadOnly   string
	Default    string

	// UntaggedNameFunc will be called with the struct field name to
	// return a column name in case the struct field has no tag named NameTag.
	UntaggedNameFunc func(fieldName string) string
	// contains filtered or unexported fields
}

TaggedStructFieldMapping implements StructFieldMapper with a struct field NameTag to be used for naming and a UntaggedNameFunc in case the NameTag is not set.

func NewTaggedStructFieldMapping

func NewTaggedStructFieldMapping() *TaggedStructFieldMapping

NewTaggedStructFieldMapping returns a default mapping.

func (*TaggedStructFieldMapping) MapStructField

func (m *TaggedStructFieldMapping) MapStructField(field reflect.StructField) (table, column string, flags FieldFlag, use bool)

func (TaggedStructFieldMapping) String

func (n TaggedStructFieldMapping) String() string

type Values

type Values map[string]any

Values is a map from column names to values

func (Values) Sorted

func (v Values) Sorted() (names []string, values []any)

Sorted returns the names and values from the Values map as separated slices sorted by name.

Directories

Path Synopsis
cmd
sqldb-dump Module
examples
user_demo Module
Package information contains structs and functions to query the information_schema.
Package information contains structs and functions to query the information_schema.
mssqlconn module
mysqlconn module

Jump to

Keyboard shortcuts

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