README
¶
Overview
Package lifecycle
is designed to simplify the setup of simple SQL-focused
sqlite3 database projects.
A common thing you want to do when handed a sqlite file is 1) Determine if it's already initialized, or needs the tables created 2) Determine if it's the correct schema version 3) Migrate to the newest schema version (if required). The aim of this library is to do the grunt work for that.
Quick-start
Given version 2 of a schema in schemav2.sql
, and migration code to migrate
from version 1 to version 2 in migratev1.sql
, the following code will:
- Check to see if the database is initialized; if not, it will initialize a "params" table, write the current database version and schema name to it, and then execute schemav2.sql before returning
- If it's initialized:
- Check the schema name.
- If it matches (where "both unspecified" is a match), fine
- If it's different (either different name, or present in one place but not the other), return an error
- Check the version.
- If it's the current version (2), it will return success.
- If it's newer (>2), it will return an error
- If it's older (1), it will migrate the database.
- Check the schema name.
//go:embed schemav2.sql
var schemav2 string
//go:embed migratev1.sql
var migratev1 string
func Open(filename string) (*sqlx.DB, error) {
db, err := sqlx.Open("sqlite3", "file:"+filename+"?_fk=true&mode=rwc")
if err != nil {
return nil, fmt.Errorf("Opening database: %w", err)
}
err = lifecycle.Schema(2, lifecycle.Recipe{Sql: schemav2}).
SetSchemaName("gitlab.com/user/module/package#db").
SetMigrationRecipe(1, lifecycle.Recipe{Sql: migratev1}).
SetAutoMigrate(true).
Open(db)
if err != nil {
db.Close()
return nil, fmt.Errorf("Init / migrate failed: %w", err)
}
return db, nil
}
To-do
- Add an example directory, perhaps with v1 / v2 code to see how it works
- Have
Open
take a driver / address (i.e., filename) argument - Have a method which returns an sqlx database
- Do proper testing of "parallel upgrades", which may require a transaction loop
- See if we can add code to sanity-check the results of migrations; i.e., make an empty database w/ the current version, and compare the tables
- Add caller-supplied sanity checks for the contents of the databases
- Add more tests for transaction-based updates
Documentation
¶
Overview ¶
Package lifecycle is designed to simplify the setup of simple SQL-focused sqlite3 database projects.
A common thing you want to do when handed a sqlite file is 1) Determine if it's already initialized, or needs the tables created 2) Determine if it's the correct schema version 3) Migrate to the newest schema version (if required). The aim of this library is to do the grunt work for that.
Index ¶
- Constants
- Variables
- type Config
- func (cfg *Config) Check() error
- func (cfg *Config) GetParam(eq sqlx.Ext, key string) (string, error)
- func (cfg *Config) Open(dbx *sqlx.DB) error
- func (cfg *Config) OpenTx(eq sqlx.Ext) error
- func (cfg *Config) SetAutoMigrate(autoMigrate bool) *Config
- func (cfg *Config) SetMigrationRecipe(vfrom int, recipe Recipe) *Config
- func (cfg *Config) SetParam(eq sqlx.Ext, param string, value string) error
- func (cfg *Config) SetParamTableName(name string) *Config
- func (cfg *Config) SetSchemaName(schemaName string) *Config
- type LifecycleError
- type Recipe
Constants ¶
const ( PARAMS_KEY_DBVERSION = "dbversion" PARAMS_KEY_DBSCHEMANAME = "dbschemaname" )
Variables ¶
var ( ErrorNeedsUpgrade = LifecycleError{/* contains filtered or unexported fields */} ErrorInvalidMigrationVersion = LifecycleError{/* contains filtered or unexported fields */} ErrorInternal = LifecycleError{/* contains filtered or unexported fields */} ErrorNoMigrationRecipe = LifecycleError{/* contains filtered or unexported fields */} ErrorMigrationFailed = LifecycleError{/* contains filtered or unexported fields */} ErrorSchemaNameMismatch = LifecycleError{/* contains filtered or unexported fields */} ErrDbTooNew = LifecycleError{/* contains filtered or unexported fields */} )
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// contains filtered or unexported fields
}
Config is the configuration for database initialization and migration. It should be created with the Schema() function.
Note that some error checking is done as the configuration is assembled; but those errors cannot be returned immediately; they will only be returned when Config.Open() or Config.Check() are called.
If an error happens in any of the directives, the rest of the directives are skipped.
func Schema ¶
Schema creates a basic configuration: The current database version, and the current schema to create the database.
func (*Config) GetParam ¶
GetParam gets the value of the specified key. If the key doesn't exist, sql.ErrNoRows will be returned (perhaps wrapped).
func (*Config) Open ¶
Open will check whether the database has been initialized, and if so, what version it is. If it cannot read the version, it will attempt to initialize the database with the newest schema version.
func (*Config) OpenTx ¶
OpenTx will do the same thing as open, but with an sqlx.Tx instead. (Alternately, if you want to do the open operation without a transaction, you can call this with the raw database, but that's not recommended.)
func (*Config) SetAutoMigrate ¶
SetAutoMigrate configures whether database schema migration will happen automatically or not. If false, an error will be returned if an old version of the database is opened.
Default: false
func (*Config) SetMigrationRecipe ¶
SetMigrationRecipe configures how to upgrade from version vfrom to vfrom+1. vfrom must be strictly less than the swDbVersion passed to Schema().
func (*Config) SetParam ¶
SetParam sets the key to the specified value (updating they key to the new value if it already exists). It can be passed either a transaction or a plain database.
func (*Config) SetParamTableName ¶
SetParamTableName configures the name of the parameter table which contains the database version. This can be used for other purposes, as long as the key "dbversion" is not used. NB that this table name CANNOT BE MIGRATED, so choose it wisely.
Default: "params"
func (*Config) SetSchemaName ¶
SetSchemaName sets the database schema name. It is recommended this be of the form <packagepath>#<schemaname>; for example, "gitlab.com/martyros/languagedb#words". If set, opened databases will check that it is equivalent when opening; if not set, opened databases will verify that no such key exists.
type LifecycleError ¶
type LifecycleError struct {
// contains filtered or unexported fields
}
func (LifecycleError) Is ¶
func (e LifecycleError) Is(target error) bool
func (LifecycleError) Unwrap ¶
func (e LifecycleError) Unwrap() error
type Recipe ¶
Recipe contains a recipe for doing something with the database; for instance, initializing an empty database, or migrating from version N of a database to version N+1. Sql is sql which is executed (as part of the transaction). Function is a function called with the transaction handle.
If both are present, Sql is executed first, then Function called.
If either one results in an error, the migration will stop and the entire transaction will be rolled back.