Documentation
¶
Index ¶
- Constants
- func LogFromLog(logger interface{ ... }) *internal.Log
- func LogFromLogur(logur Logur) *internal.Log
- func LogFromPrintln(printer interface{ ... }) *internal.Log
- func OpenAnyDB(dsn string) (*sql.DB, error)
- type Database
- type Driver
- type Logur
- type Migration
- type MigrationBase
- func (m *MigrationBase) ApplyForceOverride()
- func (m MigrationBase) Copy() MigrationBase
- func (m *MigrationBase) ForcedNonTransactional() bool
- func (m *MigrationBase) ForcedTransactional() bool
- func (m *MigrationBase) HasSkipIf() bool
- func (m *MigrationBase) NonTransactional() bool
- func (m *MigrationBase) SetNonTransactional(v bool)
- func (m *MigrationBase) SetStatus(status MigrationStatus)
- func (m *MigrationBase) Status() MigrationStatus
- type MigrationName
- type MigrationOption
- func After(lib, migration string) MigrationOption
- func Asynchronous() MigrationOption
- func ForceNonTransactional() MigrationOption
- func ForceTransactional() MigrationOption
- func RepeatUntilNoOp() MigrationOption
- func SkipIf(pred func() (bool, error)) MigrationOption
- func SkipRemainingIf(pred func() (bool, error)) MigrationOption
- type MigrationStatus
- type Options
- type OverrideOptions
- type Schema
Constants ¶
const ( // ErrDataAndDDL indicates a single migration mixes schema changes (DDL) and data // manipulation (DML) where that combination is disallowed. ErrDataAndDDL errors.String = "migration combines DDL (schema changes) and data manipulation" // ErrNonIdempotentDDL indicates a non-transactional migration contains easily // guardable DDL lacking an IF (NOT) EXISTS clause. ErrNonIdempotentDDL errors.String = "unconditional migration has non-idempotent DDL" // ErrNonTxMultipleStatements indicates a non-transactional (idempotent) script migration // attempted to execute multiple SQL statements when only one is allowed. ErrNonTxMultipleStatements errors.String = "non-transactional migration has multiple statements" // ErrNonIdempotentNonTx indicates a required-to-be-idempotent non-transactional migration // failed idempotency validation heuristics. ErrNonIdempotentNonTx errors.String = "non-idempotent non-transactional migration" )
const DefaultTrackingTable = "libschema.migration_status"
Variables ¶
This section is empty.
Functions ¶
func LogFromLog ¶ added in v0.0.2
LogFromPrintln creates a logger for libschema from a logger that implements Log() like testing.T does.
func LogFromLogur ¶ added in v0.0.2
LogFromLogur creates a logger for libschema from a logger that implments the recommended interface in Logur. See https://github.com/logur/logur#readme
func LogFromPrintln ¶ added in v0.0.2
LogFromPrintln creates a logger for libchema from a logger that implements Println() like the standard "log" pacakge.
import "log" LogFromPrintln(log.Default())
Types ¶
type Database ¶
Database tracks all of the migrations for a specific database.
func (*Database) Migrations ¶
Migrations specifies the migrations needed for a library. By default, each migration is dependent upon the prior migration and they'll run in the order given. By default, all the migrations for a library will run in the order in which the library migrations are defined.
type Driver ¶
type Driver interface {
CreateSchemaTableIfNotExists(context.Context, *internal.Log, *Database) error
LockMigrationsTable(context.Context, *internal.Log, *Database) error
UnlockMigrationsTable(*internal.Log) error
// DoOneMigration must update the both the migration status in
// the Database object and it must persist the migration status
// in the tracking table. It also does the migration.
// The returned sql.Result is optional: Computed() migrations do not
// need to provide results. The result is used for RepeatUntilNoOp.
DoOneMigration(context.Context, *internal.Log, *Database, Migration) (sql.Result, error)
// IsMigrationSupported exists to guard against additional migration
// options and features. It should return nil except if there are new
// migration features added that haven't been included in all support
// libraries.
IsMigrationSupported(*Database, *internal.Log, Migration) error
LoadStatus(context.Context, *internal.Log, *Database) ([]MigrationName, error)
}
Driver interface is what's required to use libschema with a new database.
type Migration ¶
type Migration interface {
Base() *MigrationBase
Copy() Migration
}
MigrationBase is a workaround for lacking object inheritance.
type MigrationBase ¶
type MigrationBase struct {
Name MigrationName
// contains filtered or unexported fields
}
Migration defines a single database defintion update.
func (*MigrationBase) ApplyForceOverride ¶ added in v0.7.0
func (m *MigrationBase) ApplyForceOverride()
ApplyForceOverride overrides transactionality for any prior force call (ForceTransactional or ForceNonTransactional)
func (MigrationBase) Copy ¶
func (m MigrationBase) Copy() MigrationBase
func (*MigrationBase) ForcedNonTransactional ¶ added in v0.7.0
func (m *MigrationBase) ForcedNonTransactional() bool
ForcedNonTransactional reports if ForceNonTransactional() was explicitly called.
func (*MigrationBase) ForcedTransactional ¶ added in v0.7.0
func (m *MigrationBase) ForcedTransactional() bool
ForcedTransactional reports if ForceTransactional() was explicitly called.
func (*MigrationBase) HasSkipIf ¶
func (m *MigrationBase) HasSkipIf() bool
func (*MigrationBase) NonTransactional ¶ added in v0.7.0
func (m *MigrationBase) NonTransactional() bool
NonTransactional reports if the migration must not be wrapped in a transaction. This is automatically set for drivers (e.g. Postgres) that provide generic migration helpers which infer non-transactional status from the connection type used (e.g. *sql.DB vs *sql.Tx). A migration marked nonTransactional will be executed without an encompassing BEGIN/COMMIT; status recording still occurs within its own small transaction when supported.
func (*MigrationBase) SetNonTransactional ¶ added in v0.7.0
func (m *MigrationBase) SetNonTransactional(v bool)
func (*MigrationBase) SetStatus ¶
func (m *MigrationBase) SetStatus(status MigrationStatus)
func (*MigrationBase) Status ¶
func (m *MigrationBase) Status() MigrationStatus
type MigrationName ¶
MigrationName holds both the name of the specific migration and the library to which it belongs.
func (MigrationName) String ¶
func (n MigrationName) String() string
type MigrationOption ¶
type MigrationOption func(Migration)
MigrationOption modifies a migration to set additional parameters
func After ¶
func After(lib, migration string) MigrationOption
After sets up a dependency between one migration and another. This can be across library boundaries. By default, migrations are dependent on the prior migration defined. After specifies that the current migration must run after the named migration.
func Asynchronous ¶ added in v0.0.2
func Asynchronous() MigrationOption
Asynchronous marks a migration is okay to run asynchronously. If all of the remaining migrations can be asynchronous, then schema.Migrate() will return while the remaining migrations run.
func ForceNonTransactional ¶ added in v0.7.0
func ForceNonTransactional() MigrationOption
ForceNonTransactional forces a migration (Script, Generate, or Computed) to run without a wrapping transaction. By using this you assert the migration is idempotent (safe to retry). Overrides any automatic inference the driver would normally perform.
Generate note: For drivers where generation always occurs inside a transaction context (e.g. Postgres Generate with *sql.Tx generator) forcing non-transactional only affects execution of the produced SQL, not the generator callback itself. Drivers will reject impossible combinations (e.g. attempting to force non-transactional on a generator that fundamentally requires *sql.Tx if they cannot safely downgrade).
func ForceTransactional ¶ added in v0.7.0
func ForceTransactional() MigrationOption
ForceTransactional forces a migration to run inside a transaction even if automatic inference would choose non-transactional execution.
Generate note: For generator-based migrations whose callback already receives *sql.Tx (Generate) this is effectively a no-op (they are already transactional). For generators that inherently execute outside a transaction (none exist in current public API) forcing transactional would be rejected.
WARNING (foot-gun): On MySQL / SingleStore this DOES NOT make DDL atomic. Those engines autocommit each DDL statement regardless of any BEGIN you issue. By forcing transactional mode:
- Earlier DML in the same migration may roll back while preceding DDL remains applied.
- The bookkeeping UPDATE that records migration completion can fail while schema changes have already been partially or fully applied (leaving the migration marked incomplete and retried later against an already-changed schema).
- Mixed DDL + DML ordering inside a forced transactional migration can produce inconsistent, misleading results during retries or partial failures.
Use this only if you fully understand these semantics and prefer to retain a transactional wrapper for non-DDL statements. If you simply need safe DDL, prefer writing idempotent statements and let the driver downgrade automatically.
func RepeatUntilNoOp ¶ added in v0.0.2
func RepeatUntilNoOp() MigrationOption
RepeatUntilNoOp marks a migration (Script or Generate) to be re-executed until the underlying driver reports zero rows affected. Use it for single, pure DML statements that progressively transform data (e.g. UPDATE batches that move a limited subset each run). It is NOT a generic looping primitive.
Safety / correctness guidelines:
- Single statement only - multi-statement scripts can give a meaningless final RowsAffected().
- DML only - avoid DDL (CREATE/ALTER/DROP/REFRESH) or utility commands; most return 0 and will terminate immediately or loop uselessly.
- Idempotent per batch - repeating the same statement after partial success must not corrupt data.
- Do NOT mix with non-transactional Postgres DDL (concurrent index builds, REFRESH MATERIALIZED VIEW CONCURRENTLY).
- If logic needs conditionals or multiple statements, write a Computed migration and loop explicitly.
Prefer a Computed migration when:
- You need to run multiple statements per batch
- You must inspect progress with custom queries
- RowsAffected() is unreliable or driver-dependent
Future note: libschema may emit debug warnings for obviously unreliable usages (e.g. DDL + RepeatUntilNoOp). Treat the above bullets as normative behavior now.
func SkipIf ¶
func SkipIf(pred func() (bool, error)) MigrationOption
SkipIf is checked before the migration is run. If the function returns true then this migration is skipped. For MySQL, this allows migrations that are not idempotent to be checked before they're run and skipped if they have already been applied.
func SkipRemainingIf ¶
func SkipRemainingIf(pred func() (bool, error)) MigrationOption
SkipRemainingIf is checked before the migration is run. If the function returns true then this migration and all following it are not run at this time. One use for this to hold back migrations that have not been released yet. For example, in a blue-green deploy organization, you could first do a migration that creates another column, then later do a migration that removes the old column. The migration to remove the old column can be defined and tested in advance but held back by SkipRemainingIf until it's time to deploy it.
type MigrationStatus ¶
type MigrationStatus struct {
Done bool
Error string // If an attempt was made but failed, this will be set
}
MigrationStatus tracks if a migration is complete or not.
type Options ¶
type Options struct {
// Overrides change the behavior of libschema in big ways: causing it to
// call os.Exit() when finished or not migrating. If overrides is not
// specified then DefaultOverrides is used.
Overrides *OverrideOptions
// TrackingTable is the name of the table used to track which migrations
// have been applied
TrackingTable string
// SchemaOverride is used to override the default schema. This is most useful
// for testing schema migrations
SchemaOverride string
// These TxOptions will be used for all migration transactions.
MigrationTxOptions *sql.TxOptions
ErrorOnUnknownMigrations bool
// OnMigrationFailure is only called when there is a failure
// of a specific migration. OnMigrationsComplete will also
// be called. OnMigrationFailure is called for each Database
// (if there is a failure).
OnMigrationFailure func(dbase *Database, n MigrationName, err error)
// OnMigrationsStarted is only called if migrations are needed
// OnMigrationsStarted is called for each Database (if needed).
OnMigrationsStarted func(dbase *Database)
// OnMigrationsComplete called even if no migrations are needed. It
// will be called when async migrations finish even if they finish
// with an error. OnMigrationsComplete is called for each Database.
OnMigrationsComplete func(dbase *Database, err error)
// DebugLogging turns on extra debug logging
DebugLogging bool
}
Options operate at the Database level but are specified at the Schema level at least initially. If you want separate options on a per-Database basis, you must override the values after attaching the database to the Schema.
type OverrideOptions ¶ added in v0.0.2
type OverrideOptions struct {
// MigrateOnly causes program to exit when migrations are complete.
// Asynchronous migrations will be skipped. If no migrations are need,
// the program will exit very quickly. This is accomplished with a call
// to os.Exit().
MigrateOnly bool `flag:"migrate-only" help:"Call os.Exit() after completing migrations"`
// MigrateDatabase specifies that only a specific database should
// be migrated. The name must match to a name provided with the schema.NewDatabase() call.
// For both libschema/lsmysql and libschema/lspostgres, that is the name parameter to
// New() NOT the database name in the DSN. This is a logical name.
MigrateDatabase string `flag:"migrate-database" help:"Migrate only the this database"`
// MigrateDSN overrides the data source name for a single database. It must be used in
// conjunction with MigrateDatabase unless there are only migrations for a single database.
MigrateDSN string `flag:"migrate-dsn" help:"Override *sql.DB, must combine with --migrate-database"`
// NoMigrate command line flag / config variable skips all migrations
NoMigrate bool `flag:"no-migrate" help:"Skip all migrations (except async)"`
// ErrorIfMigrateNeeded command line flag / config variable causes Migrate() to return error if there are
// migrations required. Asynchronous migrations do not count as required and will
// run in the background.
// In combination with EverythingSynchronous = true, if there are asychronous migrations pending then
// Migrate() will return error immediately.
ErrorIfMigrateNeeded bool `flag:"error-if-migrate-needed" help:"Return error if migrations are not current"`
// EverythingSynchronous command line flag / config variable causes asynchronous migrations to be
// treated like regular migrations from the point of view of --migrate-only, --no-migrate,
// and --error-if-migrate-needed.
EverythingSynchronous bool `flag:"migrate-all-synchronously" help:"Run async migrations synchronously"`
}
OverrideOptions define the command line flags and/or configuration block that can be provided to control libschema's behavior. OverrideOptions is designed to be filled with https://github.com/muir/nfigure but it doesn't have to be done that way.
var DefaultOverrides OverrideOptions
DefaultOverrides provides default values for Options.Overrides. DefaultOverrides is only used when Options.Overrides is nil.
DefaultOverrides can be filled by nfigure:
import "github.com/muir/nfigure" registry := nfigure.NewRegistry() request, err := registry.Request(&libschema.DefaultOverrides) err := registry.Configure()
DefaultOverrides can be filled using the "flag" package:
import "flag" import "github.com/muir/nfigure" nfigure.MustExportToFlagSet(flag.CommandLine, "flag", &libschema.DefautOverrides)
type Schema ¶
type Schema struct {
// contains filtered or unexported fields
}
Schema tracks all the migrations
func (*Schema) Migrate ¶
Migrate runs pending migrations that have been registered as long as the command line flags support doing migrations. We all remaining migrations are asynchronous then the remaining migrations will be run in the background.
A lock is held while migrations are in progress so that there is no chance of double migrations.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package lsmysql has a libschema.Driver support MySQL
|
Package lsmysql has a libschema.Driver support MySQL |
|
Package lspostgres has a libschema.Driver support PostgreSQL
|
Package lspostgres has a libschema.Driver support PostgreSQL |
|
Package lssinglestore is a libschema.Driver for connecting to SingleStore databases.
|
Package lssinglestore is a libschema.Driver for connecting to SingleStore databases. |
