lifecycle

package
v0.0.0-...-cfcd19d Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2024 License: MIT Imports: 5 Imported by: 0

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.
//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

View Source
const (
	PARAMS_KEY_DBVERSION    = "dbversion"
	PARAMS_KEY_DBSCHEMANAME = "dbschemaname"
)

Variables

View Source
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

func Schema(swDbVersion int, initRecipe Recipe) *Config

Schema creates a basic configuration: The current database version, and the current schema to create the database.

func (*Config) Check

func (cfg *Config) Check() error

Check to see if the assembled config resulted in any errors.

func (*Config) GetParam

func (cfg *Config) GetParam(eq sqlx.Ext, key string) (string, error)

GetParam gets the value of the specified key. If the key doesn't exist, sql.ErrNoRows will be returned (perhaps wrapped).

func (*Config) Open

func (cfg *Config) Open(dbx *sqlx.DB) error

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

func (cfg *Config) OpenTx(eq sqlx.Ext) error

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

func (cfg *Config) SetAutoMigrate(autoMigrate bool) *Config

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

func (cfg *Config) SetMigrationRecipe(vfrom int, recipe Recipe) *Config

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

func (cfg *Config) SetParam(eq sqlx.Ext, param string, value string) error

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

func (cfg *Config) SetParamTableName(name string) *Config

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

func (cfg *Config) SetSchemaName(schemaName string) *Config

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

type Recipe struct {
	Sql      string
	Function func(*Config, sqlx.Ext) error
}

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.

Jump to

Keyboard shortcuts

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