Documentation
¶
Overview ¶
Package db stores tanGO models in SQLite or PostgreSQL.
A Store wraps a *sql.DB with the Dialect it speaks: NewStore creates one, and its methods create, read, update, delete and filter models, generating each dialect's SQL. Raw SQL goes through the Store too, when a query outgrows the built-in filtering. ParseDSN turns a scheme-qualified connection string (sqlite://app.db, postgres://…) into the dialect, driver name and connection string to open, so the three can't disagree.
See https://tangoframework.com/docs/guides/persistence-crud-and-raw-sql/ and https://tangoframework.com/docs/guides/sqlite-and-postgresql-setup/.
Index ¶
- Variables
- func ColumnName(name string) string
- func IsUniqueConstraintViolation(err error) bool
- func SQLiteForeignKeysDSN(dsn string) string
- type Condition
- type DSN
- type Dialect
- type Op
- type Query
- type Store
- func (s *Store) Count(ctx context.Context, meta model.ModelMeta, query Query) (int, error)
- func (s *Store) Create(ctx context.Context, meta model.ModelMeta, dest any) error
- func (s *Store) Delete(ctx context.Context, meta model.ModelMeta, pk any) error
- func (s *Store) Get(ctx context.Context, meta model.ModelMeta, pk any, dest any) error
- func (s *Store) List(ctx context.Context, meta model.ModelMeta, query Query, dest any) error
- func (s *Store) Query(ctx context.Context, dest any, sqlQuery string, args ...any) error
- func (s *Store) QueryRow(ctx context.Context, dest any, sqlQuery string, args ...any) error
- func (s *Store) Update(ctx context.Context, meta model.ModelMeta, dest any) error
- func (s *Store) UseModels(models *model.Registry)
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidForeignKey = errors.New("tango db: invalid foreign key")
ErrInvalidForeignKey is returned by Create and Update when a set (non-zero) foreign key field does not reference an existing row of its related model. This is referential-integrity validation: a preflight SELECT, not run inside a shared transaction with the write it guards, and only performed when UseModels has been called. It is distinct from model.ErrUnknownForeignKeyTarget, which validates that the related *model* exists at all, once, at registration time.
var ErrNotFound = errors.New("tango db: not found")
ErrNotFound is returned by Get, Update, and Delete when no row matches the given primary key.
Functions ¶
func ColumnName ¶
ColumnName derives the database table or column name for a Go identifier.
func IsUniqueConstraintViolation ¶
IsUniqueConstraintViolation reports whether err is a unique-constraint violation returned by Create or Update, on either supported dialect.
This exists so a caller can attempt a write directly and treat the database's own unique constraint as the actual correctness boundary for a duplicate value, rather than a SELECT-then-INSERT pre-check — which only narrows, and never closes, the race between two concurrent writers for the same unique value. A pre-check may still exist purely for a faster/friendlier error on the common non-racing path, but correctness must not depend on it.
func SQLiteForeignKeysDSN ¶
SQLiteForeignKeysDSN appends the modernc.org/sqlite driver's "_foreign_keys=on" query parameter to a SQLite DSN, so every connection the driver opens for it enforces FOREIGN KEY constraints (SQLite treats this as a per-connection setting, off by default). This is a plain string helper — it never imports the driver itself, keeping db driver-agnostic — so it composes with any DSN shape (a file path, ":memory:", or one that already carries other query parameters).
ParseDSN applies it to every sqlite:// DSN, so an app that opens its database from ParseDSN's result (as tango.LoadDBConfigFromEnv, and so the generated scaffold, does) already enforces foreign keys. Anyone opening their own *sql.DB with driver "sqlite" from a bare path should call it too if they rely on foreign key constraints. It isn't automatic inside db.NewStore itself: NewStore wraps an already-open *sql.DB and must never touch it, so the flags that don't need a database ("-check", "-tango-dump-models") stay database-free.
Types ¶
type Condition ¶
Condition compares one model field with a non-nil value. OpLike accepts a SQL LIKE pattern on string fields, including % and _ wildcards.
type DSN ¶ added in v0.1.0
type DSN struct {
// Dialect is the SQL dialect the DSN's scheme names.
Dialect Dialect
// Driver is the database/sql driver name: "sqlite" or "pgx".
Driver string
// Source is the driver's own data source name.
Source string
}
DSN is a parsed scheme-qualified DSN: the Dialect to build a Store with, and the two arguments to pass to sql.Open.
func ParseDSN ¶ added in v0.1.0
ParseDSN turns a scheme-qualified DSN into the Dialect, the database/sql driver name, and the driver's own data source name to pass to sql.Open:
sqlite://app.db SQLite file relative to the working directory sqlite:///var/data/app.db SQLite file at an absolute path sqlite://:memory: SQLite, in-memory postgres://… postgresql://… PostgreSQL, passed to pgx unchanged
Anything else, including a bare path such as "app.db", is an error. For SQLite the returned Source already enables foreign key enforcement (see SQLiteForeignKeysDSN) and makes a writer wait up to 5 seconds for another connection's write lock instead of failing with SQLITE_BUSY, unless the DSN sets its own busy_timeout. ParseDSN never imports or registers a driver: the app still imports modernc.org/sqlite ("sqlite") or github.com/jackc/pgx/v5/stdlib ("pgx") itself.
type Query ¶
Query carries filtering, pagination, and ordering options for List. Where conditions are ANDed; Any conditions are ORed as one group, which is ANDed with Where when both are present.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is tanGO's persistence boundary over a database/sql connection.
func (*Store) Count ¶
Count returns the number of rows matching query's Where and Any conditions. Limit, Offset, and OrderBy do not affect the total.
func (*Store) Create ¶
Create inserts dest using metadata-derived table and column names. A zero primary key is generated by the database and backfilled into dest; a set one is inserted as given, and on PostgreSQL the column's sequence is advanced past it so later Creates without an ID don't collide with it.
func (*Store) Delete ¶
Delete removes the row matching pk. If UseModels has been called, it first cascades: every row of every other registered model that references this one via a foreign key field is deleted first (recursively), all inside one transaction, before the target row itself is removed. See UseModels.
func (*Store) List ¶
List selects rows into dest (a pointer to a slice of the model struct), applying query's Where, Any, Limit, Offset, and OrderBy.
func (*Store) Query ¶
Query executes sql with args and appends one scanned element per resulting row into dest (a pointer to a slice of structs), matching returned columns to struct fields by name (case-insensitively). A query matching no rows leaves dest an empty, non-nil slice with a nil error.
sqlQuery must use the placeholder syntax matching this Store's configured Dialect ("?" for SQLite, "$1", "$2", ... for Postgres) — Query does not translate or validate placeholder syntax between dialects.
func (*Store) QueryRow ¶
QueryRow executes sql with args and scans the single resulting row into dest (a pointer to a struct), matching returned columns to struct fields by name (case-insensitively). It returns an error wrapping ErrNotFound when the query yields no rows.
sqlQuery must use the placeholder syntax matching this Store's configured Dialect ("?" for SQLite, "$1", "$2", ... for Postgres) — QueryRow does not translate or validate placeholder syntax between dialects.
func (*Store) Update ¶
Update writes dest's current field values to the row matching its primary key.
func (*Store) UseModels ¶
UseModels attaches the model registry Delete needs to cascade: when set, deleting a row also deletes, recursively, every row of every other registered model that references it through a foreign key field (a tango:"fk=X" tag), matching Django's ORM-level cascade. This is application-level cascade, not a database ON DELETE CASCADE constraint. Without calling UseModels, Delete only removes the target row, exactly as before this method existed. tango.Registry.SetStore calls this automatically with its own Models(), so apps using the standard Registry/Serve flow get cascading deletes with no code change.