sql

package
v0.0.0-...-a33ae49 Latest Latest
Warning

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

Go to latest
Published: Nov 25, 2024 License: 0BSD Imports: 13 Imported by: 0

Documentation

Overview

Package sql defines a data map type with support for type-safe structured queries.

Designing a Schema

Domain objects should typically be split into comparable index and value pairs.

// Domain type.
type Customer struct {
	ID int64
	Age uint
	Name string
}

// Internal Database Index Type.
type dbID int64

// Internal Database Value Type.
type dbCustomer struct {
	Name string
	Age uint
}

// Database representation.
var customers sql.Map[dbID, dbCustomer]

Insert a record into the table, overwriting it if the ID already exists.

err := customers.Insert(ctx, id, &dbCustomer{Name: "Bob", Age: 23})

Lookup a record from the table.

customer, exists, err := customers.Lookup(ctx, id)

Delete the record from the table.

err := customers.Delete(ctx, id)

It is possible to search for records in the table and iterate through the results. For example, to search for customers with the age "22":

query := func(index *dbID, value *dbCustomer) sql.Query {
	return sql.Query{
		sql.Index(&value.Age).Equals(22),
	}
}
for result := range customers.SearchFunc(ctx, query) {
	id, customer, err := result()
	if err != nil {
		return xray.Error(err)
	}
	fmt.Println(customer.Name)
}

Query Builder

Queries can be constructed using a series of operations:

sql.Index(&field).Equals(x)   // field == x
sql.Where(&field).AtLeast(x)  // field >= x
sql.Where(&field).AtMost(x)   // field <= x
sql.Where(&field).MoreThan(x) // field > x
sql.Where(&field).LessThan(x) // field < x

sql.Match(&field).Contains(x)  // strings.Contains(field, x)
sql.Match(&field).HasPrefix(x) // strings.HasPrefix(field, x)
sql.Match(&field).HasSuffix(x) // strings.HasSuffix(field, x)

sql.Order(&field).Increasing() // ORDER BY field ASC
sql.Order(&field).Decreasing() // ORDER BY field DESC

sql.Slice(from, upto) // data[from:upto]

sql.Empty(&field) // reflect.ValueOf(field).IsZero()
sql.Avoid(q)      // !q

// switch
sql.Cases(
	sql.Index(&field).Equals(x),
	sql.Index(&field).Equals(y),
	...
)

Conditional Writes

Each write operation supports conditionals, so that they can only be completed when the provided checks match the existing record (which may be a zero value).

ifBob := func(index *dbUUID, value *dbCustomer) sql.Query {
	return sql.Query{
		sql.Index(&index).Equals(1),
		sql.Where(&value.Name).Equals("Bob",),
	}
}
ok, err := customers.UpdateFunc(ctx, ifBob, func(value dbCustomer) sql.Patch {
	return sql.Patch{
		sql.Set(&value.Name, "Alice"),
	}
}))

In the example above, the Customer with ID 1 will only have their name updated to "Alice" if their name was "Bob". If the customer's names is not Bob, ok will be false.

Statistics

Statistics can be reported on a map in the database with sql.Counter.

var (
	count sql.Counter[uint]
	total sql.Counter[uint]
)
stats := func(index dbUUID, value dbCustomer) sql.Stats {
	return sql.Stats{
		count.Add(),
		total.Sum(&value.Age),
	}
}
err := customers.OutputFunc(ctx, stats, func(index *dbUUID, value *dbCustomer) sql.Query {
	return sql.Query{
		sql.Where(&value.Name).Equals("Bob"),
	}
})

Index

Constants

View Source
const (
	ErrDuplicate        = errorString("record already exists")
	ErrTransactionUsage = errorString("empty transaction level")
	ErrInvalidKey       = errorString("invalid key")
	ErrInsertOnly       = errorString("insert only")
)

Variables

This section is empty.

Functions

func Avoid

func Avoid(expr sodium.Expression) sodium.Expression

Avoid returns a new [Expression] that can be used inside a QueryFunc to filter for results that do not match the given expression.

func Cases

func Cases(exprs ...sodium.Expression) sodium.Expression

Cases returns a new [Expression] that can be used inside a QueryFunc to filter for results that match any of the given expressions.

func Empty

func Empty[V comparable](ptr *V) sodium.Expression

Empty returns a new [Expression] for the given ptr that can be used inside a QueryFunc to filter for results that have an empty value at the given column.

func Index

func Index[V comparable](ptr *V) struct {
	Equals func(V) sodium.Expression // matches values that are equal to the given value.
}

Index returns a new sodium.Expression that can be used inside a QueryFunc to refer to one of the columns in the table. The ptr must point inside the arguments passed to the QueryFunc.

func Match

func Match[V ~string](ptr *V) struct {
	Contains  func(V) sodium.Expression // matches values that contain the given string.
	HasPrefix func(V) sodium.Expression // matches values that start with the given string.
	HasSuffix func(V) sodium.Expression // matches values that end with the given string.
}

Match returns a new [MatchExpression] for the given pointer, it can be used inside a QueryFunc to refer to one of the columns in the table. The ptr must point inside the arguments passed to the QueryFunc.

func Merge

func Merge(exprs ...sodium.Expression) sodium.Expression

Merge returns a new [Expression] that can be used inside a QueryFunc to filter for results that match all of the given expressions.

func NewOutput

func NewOutput(calcs []sodium.Calculation, scanner func(...any) error) ([]sodium.Value, error)

NewOutput returns a slice of sodium values for the given sodium calculations. A scanner function must be provided, that behaves like a database/sql scanner.

func NewResult

func NewResult(table sodium.Table, scanner func(...any) error) ([]sodium.Value, error)

NewResult returns a slice of sodium values for the columns of the given table, a scanner function must be provided, that behaves like a database/sql scanner.

func Open

func Open[T any](db Database) *T

Open a database structure, by linking each Map inside the struct via the given Database. Each field should have a sql tag that will be interpreted as a Table in a call to OpenTable.

For each Map a 'sql' or else, a 'txt' tag controls the name of the column. If no tag is specified, the ToLower(name) of the field is used. If the key is not a struct, the column name is the Table default, otherwise it is treated as a composite key across each struct field. If the value is not a struct, the column name is 'value'.

Nested structures are named with an underscore used to seperate the field path unless the structure is embedded, in which case the nested fields are promoted. Arrays elements are suffixed by their index.

func Order

func Order[V orderable](ptr *V) struct {
	Increasing func() sodium.Expression // orders values in increasing order.
	Decreasing func() sodium.Expression // orders values in decreasing order.
}

Order returns a new [OrderExpression] for the given pointer, it can be used inside a QueryFunc to refer to one of the columns in the table. The ptr must point inside the arguments passed to the QueryFunc.

func Set

func Set[V any](ptr *V, val V) sodium.Modification

Set returns a new sodium.Modification that can be used inside a PatchFunc to refer to one of the columns in the table. The ptr must point inside the arguments passed to the PatchFunc.

func Slice

func Slice(from, upto int) sodium.Expression

Slice returns a new [RangeExpression] that can be used inside a QueryFunc to limit the affect of the query to a specific range of values. The from and upto values are zero based, and the range is half open, meaning that the value at the from index is included, but the value at the upto index is not.

func Test

func Test(ctx context.Context, db Database) error

Test the implementation of a Database against the SODIUM specification. This function creates new 'testing_' prefixed tables in the database. If the test passes, the testing records are cleaned up. If the test fails, the testing records are left in the database to assist with debugging.

func ValuesOf

func ValuesOf(val any) []sodium.Value

ValuesOf destructures a Go value into a set of [Value]s. Nested struct fields and array elements are flattened into a single sequential slice of values. Pointers, complex values maps, functions, and slices will raise a panic if they are encountered (except for []byte). Unexported fields are ignored.

func Where

func Where[V whereable](ptr *V) struct {
	Min func(V) sodium.Expression // matches values greater than or equal to the given value.
	Max func(V) sodium.Expression // matches values less than or equal to the given value.

	MoreThan func(V) sodium.Expression // matches values greater than the given value.
	LessThan func(V) sodium.Expression // matches values less than the given value.
}

Where returns a new [WhereExpression] for the given pointer, it can be used inside a QueryFunc to refer to one of the columns in the table. The ptr must point inside the arguments passed to the QueryFunc.

Types

type BatchFunc

type BatchFunc func(context.Context) error

BatchFunc that performs a collection of meaningfully grouped operations on a Map.

type Chan

type Chan[K comparable, V any] chan xyz.Trio[K, V, error]

Chan streams results from a Map.Search operation.

type Check

type Check []sodium.Expression

type CheckFunc

type CheckFunc[V any] func(*V) Check

CheckFunc used for atomic operations.

type Counter

type Counter interface {
	// contains filtered or unexported methods
}

Counter is a type that can be used inside a StatsFunc to calculate a sum values.

func Count

func Count[V countable](ptr *V) Counter

Count returns a new counter for the given pointer, it can be used inside a StatsFunc. The ptr will be incremented by the given value.

type Database

type Database = sodium.Database

Database represents a connection to a SQL database.

func New

func New() Database

type Flag

type Flag bool

Flag that determines the behaviour of an [Insert].

const (
	Create Flag = false // means the insert will fail if the value already exists.
	Upsert Flag = true  // means the insert will overwrite the existing value if it exists.
)

type IncompatibleTypeError

type IncompatibleTypeError struct {
	Column sodium.Column
	Type   xyz.TypeOf[sodium.Value]
}

func (IncompatibleTypeError) Error

func (e IncompatibleTypeError) Error() string

type Map

type Map[K comparable, V any] struct {
	// contains filtered or unexported fields
}

Map represents a distinct mapping of data stored in a Database.

func OpenTable

func OpenTable[K comparable, V any](db Database, table sodium.Table) Map[K, V]

OpenTable a new Map from the given Database and specified table.

func (*Map[K, V]) Add

func (m *Map[K, V]) Add(ctx context.Context, value V) (K, error)

Add the specified value to the Map, if possible, a key will be automatically selected. If a key is required in order to add to the Map, an ErrInsertOnly error will be returned.

func (*Map[K, V]) Delete

func (m *Map[K, V]) Delete(ctx context.Context, key K, check CheckFunc[V]) (bool, error)

Delete the value at the specified key in the map if the specified check passes. Boolean returned is true if a value was deleted this way.

func (*Map[K, V]) Get

func (m *Map[K, V]) Get(ctx context.Context, key K) (V, error)

Get returns the value at the given key from the Map. If the key does not exist, a zero value is returned.

func (*Map[K, V]) Insert

func (m *Map[K, V]) Insert(ctx context.Context, key K, flag Flag, value V) error

Insert a new value into the Map at the given key. The given Flag determines how the value is intended to be inserted. If the Flag is Upsert, the value will overwrite any existing value at the given key. If the Flag is Create, the value will only be inserted if there is no existing value at the given key, otherwise an error will be returned if the existing value differs from the given value.

func (*Map[K, V]) Lookup

func (m *Map[K, V]) Lookup(ctx context.Context, key K) (V, bool, error)

Lookup the specified key in the map and return the value associated with it, if the value is not present in the map, the resulting boolean will be false.

func (*Map[K, V]) Mutate

func (m *Map[K, V]) Mutate(ctx context.Context, key K, check CheckFunc[V], patch PatchFunc[V]) (bool, error)

Mutate the value at the specified key in the map. The CheckFunc is called with the current value at the specified key, if the CheckFunc returns true, then the PatchFunc is called with the current value at the specified key. The PatchFunc should return the modifications to be made to the value at the specified key.

func (*Map[K, V]) Output

func (m *Map[K, V]) Output(ctx context.Context, query QueryFunc[K, V], stats StatsFunc[K, V]) error

func (*Map[K, V]) Search

func (m *Map[K, V]) Search(ctx context.Context, query QueryFunc[K, V], issue *error) iter.Seq2[K, V]

func (*Map[K, V]) Set

func (m *Map[K, V]) Set(ctx context.Context, key K, value V) error

Set the value at the given key in the Map. If the key does not exist, a new value is inserted. If the key does exist, the value is updated.

func (*Map[K, V]) UnsafeDelete

func (m *Map[K, V]) UnsafeDelete(ctx context.Context, query QueryFunc[K, V]) (int, error)

UnsafeDelete each value in the map that matches the given query. The number of values that were deleted is returned, along with any error that occurred. The query must include a slice operation that limits the number of values that can be deleted, otherwise the operation will fail. Unsafe because a large amount of data can be permanently deleted this way.

func (*Map[K, V]) Update

func (m *Map[K, V]) Update(ctx context.Context, query QueryFunc[K, V], patch PatchFunc[V]) (int, error)

Update each value in the map that matches the given query with the given patch. The number of values that were updated is returned, along with any error that occurred.

type Patch

type Patch []sodium.Modification

type PatchFunc

type PatchFunc[V any] func(*V) Patch

PatchFunc that returns a Patch for the given value.

type Query

type Query []sodium.Expression

type QueryFunc

type QueryFunc[K comparable, V any] func(*K, *V) Query

QueryFunc that returns a Query for the given key and value.

type Stats

type Stats []Counter

type StatsFunc

type StatsFunc[K comparable, V any] func(*K, *V) Stats

StatsFunc that returns a Stats for the given key and value.

type Table

type Table string

Table name, may contain a slash to indicate the default primary key column. If no slash is present, the primary key column is 'id'.

type UnsupportedTypeError

type UnsupportedTypeError struct {
	Type xyz.TypeOf[sodium.Value]
}

func (UnsupportedTypeError) Error

func (e UnsupportedTypeError) Error() string

Directories

Path Synopsis
std
sodium
Package sodium provides a specification for the SODIUM standard database interface.
Package sodium provides a specification for the SODIUM standard database interface.

Jump to

Keyboard shortcuts

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