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)
Search ¶
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
- func Avoid(expr sodium.Expression) sodium.Expression
- func Cases(exprs ...sodium.Expression) sodium.Expression
- func Empty[V comparable](ptr *V) sodium.Expression
- func Index[V comparable](ptr *V) struct{ ... }
- func Match[V ~string](ptr *V) struct{ ... }
- func Merge(exprs ...sodium.Expression) sodium.Expression
- func NewOutput(calcs []sodium.Calculation, scanner func(...any) error) ([]sodium.Value, error)
- func NewResult(table sodium.Table, scanner func(...any) error) ([]sodium.Value, error)
- func Open[T any](db Database) *T
- func Order[V orderable](ptr *V) struct{ ... }
- func Set[V any](ptr *V, val V) sodium.Modification
- func Slice(from, upto int) sodium.Expression
- func Test(ctx context.Context, db Database) error
- func ValuesOf(val any) []sodium.Value
- func Where[V whereable](ptr *V) struct{ ... }
- type BatchFunc
- type Chan
- type Check
- type CheckFunc
- type Counter
- type Database
- type Flag
- type IncompatibleTypeError
- type Map
- func (m *Map[K, V]) Add(ctx context.Context, value V) (K, error)
- func (m *Map[K, V]) Delete(ctx context.Context, key K, check CheckFunc[V]) (bool, error)
- func (m *Map[K, V]) Get(ctx context.Context, key K) (V, error)
- func (m *Map[K, V]) Insert(ctx context.Context, key K, flag Flag, value V) error
- func (m *Map[K, V]) Lookup(ctx context.Context, key K) (V, bool, error)
- func (m *Map[K, V]) Mutate(ctx context.Context, key K, check CheckFunc[V], patch PatchFunc[V]) (bool, error)
- func (m *Map[K, V]) Output(ctx context.Context, query QueryFunc[K, V], stats StatsFunc[K, V]) error
- func (m *Map[K, V]) Search(ctx context.Context, query QueryFunc[K, V], issue *error) iter.Seq2[K, V]
- func (m *Map[K, V]) Set(ctx context.Context, key K, value V) error
- func (m *Map[K, V]) UnsafeDelete(ctx context.Context, query QueryFunc[K, V]) (int, error)
- func (m *Map[K, V]) Update(ctx context.Context, query QueryFunc[K, V], patch PatchFunc[V]) (int, error)
- type Patch
- type PatchFunc
- type Query
- type QueryFunc
- type Stats
- type StatsFunc
- type Table
- type UnsupportedTypeError
Constants ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 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.
type IncompatibleTypeError ¶
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 (*Map[K, V]) Add ¶
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 ¶
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 ¶
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 ¶
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 ¶
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]) Set ¶
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 ¶
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.
type Patch ¶
type Patch []sodium.Modification
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 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 ¶
func (UnsupportedTypeError) Error ¶
func (e UnsupportedTypeError) Error() string