lsmysql

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Apr 29, 2022 License: MIT Imports: 9 Imported by: 0

README

libschema/lsmysql - mysql support for libschema

GoDoc

Install:

go get github.com/muir/libschema

DDL Transactions

MySQL and MariaDB do not support transactions around DDL (Data Definition Language) changes like CREATE TABLE. Such commands cause the current transaction to switch to autocommit mode.

The consequence of this is that it is not possible for a schema migration tool, like libschema, to track if a migration has been applied or not by tracking the status of a transaction (did it error or succeed?)

When working with MySQL and MariaDB, schema-changing migrations should be done separately from data-changing migrations. Schema-changing transactions that are idempotent are safe and require no special handling.

Schema-changing transactions that are not idempotent need to be guarded with conditionals so that they're skipped if they've already been applied.

Fortunately, IF EXISTS and IF NOT EXISTS clauses can be added to most of the DDL statements.

Conditionals

The DDL statements missing IF EXISTS and IF NOT EXISTS include:

ALTER TABLE ...
	ADD CONSTRAINT
	ALTER COLUMN SET SET DEFAULT
	ALTER COLUMN SET DROP DEFAULT
	ADD FULLTEXT 
	ADD SPATIAL
	ADD PERIOD FOR SYSTEM TIME
	ADD {INDEX|KEY} index_name [NOT] INVISIBLE
	DROP PRIMARY KEY
	RENAME COLUMN
	RENAME INDEX
	RENAME KEY
	DISCARD TABLESPACE
	IMPORT TABLESPACE
	COALESCE PARTITION
	REORGANIZE PARTITION
	EXCHANGE PARTITION
	REMOVE PARTITIONING
	DISABLE KEYS
	ENABLE KEYS

To help make these conditional, the lsmysql provides some helper functions to easily check the current database state.

For example:

schema := libschema.NewSchema(ctx, libschema.Options{})

sqlDB, err := sql.Open("mysql", "....")

database, mysql, err := lsmysql.New(logger, "main-db", schema, sqlDB)

database.Migrations("MyLibrary",
	lsmysql.Script("createUserTable", `
		CREATE TABLE users (
			name	text,
			id	bigint,
			PRIMARY KEY (id)
		) ENGINE=InnoDB`
	}),
	lsmysql.Script("dropUserPK", `
		ALTER TABLE users
			DROP PRIMARY KEY`,
		libschema.SkipIf(func() (bool, error) {
			hasPK, err := mysql.HasPrimaryKey("users")
			return !hasPK, err
		})),
	)
Some notes on MySQL

While most identifiers (table names, etc) can be "quoted", you cannot use quotes around a schema (database really) name with CREATE SCHEMA.

MySQL does not support schemas. A schema is just a synonym for DATABASE in the MySQL world. This means that it is easier to put migrations tracking table in the same schema (database) as the rest of the tables. It also means that to run migration unit tests, the DSN for testing has to give access to a user that can create and drop databases.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Computed

func Computed(
	name string,
	action func(context.Context, *sql.Tx) error,
	opts ...libschema.MigrationOption) libschema.Migration

Computed creates a libschema.Migration from a Go function to run the migration directly.

func Generate

func Generate(
	name string,
	generator func(context.Context, *sql.Tx) string,
	opts ...libschema.MigrationOption) libschema.Migration

Generate creates a libschema.Migration from a function that returns a SQL string

func Script

func Script(name string, sqlText string, opts ...libschema.MigrationOption) libschema.Migration

Script creates a libschema.Migration from a SQL string

Types

type CheckResult

type CheckResult string
const (
	Safe             CheckResult = "safe"
	DataAndDDL       CheckResult = "dataAndDDL"
	NonIdempotentDDL CheckResult = "nonIdempotentDDL"
)

func CheckScript

func CheckScript(s string) CheckResult

CheckScript attempts to validate that an SQL command does not do both schema changes (DDL) and data changes.

type MySQL

type MySQL struct {
	// contains filtered or unexported fields
}

MySQL is a libschema.Driver for connecting to MySQL-like databases that have the following characteristics: * CANNOT do DDL commands inside transactions * Support UPSERT using INSERT ... ON DUPLICATE KEY UPDATE * uses /* -- and # for comments

Because mysql DDL commands cause transactions to autocommit, tracking the schema changes in a secondary table (like libschema does) is inherently unsafe. The MySQL driver will record that it is about to attempt a migration and it will record if that attempts succeeds or fails, but if the program terminates mid-transaction, it is beyond the scope of libschema to determine if the transaction succeeded or failed. Such transactions will be retried. For this reason, it is reccomend that DDL commands be written such that they are idempotent.

There are methods the MySQL type that can be used to query the state of the database and thus transform DDL commands that are not idempotent (like CREATE INDEX) into idempotent commands by only running them if they need to be run.

Because Go's database/sql uses connection pooling and the mysql "USE database" command leaks out of transactions, it is strongly recommended that the libschema.Option value of SchemaOverride be set when creating the libschema.Schema object. That SchemaOverride will be propagated into the MySQL object and be used as a default table for all of the functions to interrogate data defintion status.

func New

func New(log *internal.Log, name string, schema *libschema.Schema, db *sql.DB) (*libschema.Database, *MySQL, error)

New creates a libschema.Database with a mysql driver built in.

func (*MySQL) ColumnDefault

func (p *MySQL) ColumnDefault(table, column string) (*string, error)

ColumnDefault returns the default value for a column. If there is no default value, then nil is returned. The table is assumed to be in the current database unless m.UseDatabase() has been called.

func (*MySQL) CreateSchemaTableIfNotExists

func (p *MySQL) CreateSchemaTableIfNotExists(ctx context.Context, _ *internal.Log, d *libschema.Database) error

CreateSchemaTableIfNotExists creates the migration tracking table for libschema. It is expected to be called by libschema.

func (*MySQL) DatabaseName

func (m *MySQL) DatabaseName() (string, error)

DatabaseName returns the name of the current database (aka schema for MySQL). A call to UseDatabase() overrides all future calls to DatabaseName(). If the MySQL object was created from a libschema.Schema that had SchemaOverride set then this will return whatever that value was. It is reccomened that UseDatabase() be called to make sure that the right database is returned.

func (*MySQL) DoOneMigration

func (p *MySQL) DoOneMigration(ctx context.Context, log *internal.Log, d *libschema.Database, m libschema.Migration) (result sql.Result, err error)

DoOneMigration applies a single migration. It is expected to be called by libschema.

func (*MySQL) DoesColumnExist

func (p *MySQL) DoesColumnExist(table, column string) (bool, error)

DoesColumnExist returns true if the column exists The table is assumed to be in the current database unless m.UseDatabase() has been called.

func (*MySQL) GetTableConstraint

func (p *MySQL) GetTableConstraint(table, constraintName string) (string, bool, error)

GetTableConstraints returns the type of constraint and if it is enforced. The table is assumed to be in the current database unless m.UseDatabase() has been called.

func (*MySQL) HasPrimaryKey

func (p *MySQL) HasPrimaryKey(table string) (bool, error)

HasPrimaryKey returns true if the table has a primary key The table is assumed to be in the current database unless m.UseDatabase() has been called.

func (*MySQL) IsMigrationSupported

func (p *MySQL) IsMigrationSupported(d *libschema.Database, _ *internal.Log, migration libschema.Migration) error

IsMigrationSupported checks to see if a migration is well-formed. Absent a code change, this should always return nil. It is expected to be called by libschema.

func (*MySQL) LoadStatus

func (p *MySQL) LoadStatus(ctx context.Context, _ *internal.Log, d *libschema.Database) ([]libschema.MigrationName, error)

LoadStatus loads the current status of all migrations from the migration tracking table. It is expected to be called by libschema.

func (*MySQL) LockMigrationsTable

func (p *MySQL) LockMigrationsTable(ctx context.Context, _ *internal.Log, d *libschema.Database) error

LockMigrationsTable locks the migration tracking table for exclusive use by the migrations running now. It is expected to be called by libschema. In MySQL, locks are _not_ tied to transactions so closing the transaction does not release the lock. We'll use a transaction just to make sure that we're using the same connection. If LockMigrationsTable succeeds, be sure to call UnlockMigrationsTable.

func (*MySQL) TableHasIndex

func (p *MySQL) TableHasIndex(table, indexName string) (bool, error)

TableHasIndex returns true if there is an index matching the name given. The table is assumed to be in the current database unless m.UseDatabase() has been called.

func (*MySQL) UnlockMigrationsTable

func (p *MySQL) UnlockMigrationsTable(_ *internal.Log) error

UnlockMigrationsTable unlocks the migration tracking table. It is expected to be called by libschema.

func (*MySQL) UseDatabase added in v0.0.2

func (m *MySQL) UseDatabase(name string)

UseDatabase() overrides the default database for DatabaseName(), ColumnDefault(), HasPrimaryKey(), HasTableIndex(), DoesColumnExist(), and GetTableConstraint(). If name is empty then the override is removed and the database will be queried from the mysql server. Due to connection pooling in Go, that's a bad idea.

Jump to

Keyboard shortcuts

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