mgrt

package module
v3.3.0 Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2023 License: MIT Imports: 13 Imported by: 0

README

mgrt

mgrt is a simple tool for managing revisions across SQL databases. It takes SQL scripts, runs them against the database, and keeps a log of them.

Quick start

To install mgrt, clone the repository and run the ./make.sh script,

$ git clone https://github.com/andrewpillar/mgrt
$ cd mgrt
$ ./make.sh

to build mgrt with SQLite3 support add sqlite3 to the TAGS environment variable,

$ TAGS="sqlite3" ./make.sh

this will produce a binary at bin/mgrt, add this to your PATH.

Once installed you can start using mgrt right away, there is nothing to initialize. To begin writing revisions simply invoke mgrt add,

$ mgrt add "My first revision"

this will create a new revision file in the revisions directory, and open it up for editting with the revision to write,

/*
Revision: 20060102150405
Author:   Andrew Pillar <[email protected]>

My first revision
*/

CREATE TABLE users (
    id INT NOT NULL UNIQUE
);

once you've saved the revision and quit the editor, you will see the revision ID printed out,

$ mgrt add "My first revision"
revision created 20060102150405

local revisions can be viewed with mgrt ls. This will display the ID, the author of the revision, and its comment, if any,

$ mgrt ls
20060102150405: Andrew Pillar <[email protected]> - My first revision

revisions can be applied to the database via mgrt run. This command takes two flags, -type and -dsn to specify the type of database to run the revision against, and the data source for that database. Let's run our revision against an SQLite3 database,

$ mgrt run -type sqlite3 -dsn acme.db

revisions can only be performed on a database once, and cannot be undone. We can view the revisions that have been run against the database with mgrt log. Just like mgrt run, we use the -type and -dsn flags to specify the database to connect to,

$ mgrt log -type sqlite3 -dsn acme.db
revision 20060102150405
Author:    Andrew Pillar <[email protected]>
Performed: Mon Jan  6 15:04:05 2006
My first revision

    CREATE TABLE users (
        id INT NOT NULL UNIQUE
    );

this will list out the revisions that have been performed, along with the SQL code that was executed as part of that revision.

mgrt also offers the ability to sync the revisions that have been performed on a database against what you have locally. This is achieved with mgrt sync, and just like before, this also takes the -type and -dsn flags. Lets delete the revisions directory that was created for us and do a mgrt sync.

$ rm -rf revisions
$ mgrt ls
$ mgrt sync -type sqlite3 -dsn acme.db
$ mgrt ls
20060102150405: Andrew Pillar <[email protected]> - My first revision

with mgrt sync you can easily view the revisions that have been run against different databases.

Database connection

Database connections for mgrt can be managed via the mgrt db command. This allows you to set aliases for the different databases you can connect to, for example,

$ mgrt db set local-db postgresql "host=localhost port=5432 dbname=dev user=admin password=secret"

this can then be used via the -db flag for the commands that require a database connection.

The mgrt db set command expects the type of the database, and the DSN for connecting to the database. The type will be one of,

  • mysql
  • postgresql
  • sqlite3

the DSN will vary depending on the type of database being used. The mysql and postgresql you can use the URI connection string, such as,

type://[user[:password]@][host]:[port][,...][/dbname][?param1=value1&...]

where type would either be mysql or postgresql. The postgresql type also allows for the DSN string such as,

host=localhost port=5432 dbname=mydb connect_timeout=10

sqlite3 however will accept a filepath.

You can also specify the -type and -dsn flags too. These take the same arguments as above. The -db flag however is more convenient to use.

Revisions

Revisions are SQL scripts that are performed against the given database. Each revision can only be performed once, and cannot be undone. If you wish to undo a revision, then it is recommended to write another revision that does the inverse of the prior.

Revisions are stored in the revisions directory from where the mgrt add command was run. Each revision file is prefixed with a comment block header that contains metadata about the revision itself, such as the ID, the author and a short comment about the revision.

Categories

Revisions can be organized into categories via the command line. This is done by passing the -c flag to the mgrt add command and specifying the category for that revision. This will create a sub-directory in the revisions directory containing that revision. Revisions in a category will only be performed when the -c flag for that category is given to the mgrt run command.

Organizing revisions into categories can be useful if you want to keep certain revision logic separate from other revision logic. For example, if you want to separate table creation from permission granting, you could do something like,

$ mgrt add -c schema "Create users table"
$ mgrt add -c perms "Grant permissions on users table"

then, to perform the above revisions you would,

$ mgrt run -c schema -db prod
$ mgrt run -c perms -db prod

Revision log

Each time a revision is performed, a log will be made of that revision. This log is stored in the database, in the mgrt_revisions table. This will contain the ID, the author, the comment (if any), and the SQL code itself, along with the time of execution.

The revisions performed against a database can be viewed with mgrt log,

$ mgrt log -db local-dev
revision 20060102150405
Author:    Andrew Pillar <[email protected]>
Performed: Mon Jan  6 15:04:05 2006

    My first revision

Viewing revisions

Local revisions can be viewed with mgrt cat. This simply takes a list of revision IDs to view.

$ mgrt cat 20060102150405
/*
Revision: 20060102150405
Author:   Andrew Pillar <[email protected]>

My first revision
*/

CREATE TABLE users (
        id INT NOT NULL UNIQUE
);

The -sql flag can be passed to the command too to only display the SQL portion of the revision,

$ mgrt cat -sql 20060102150405
CREATE TABLE users (
        id INT NOT NULL UNIQUE
);

performed revisions can also be seen with mgrt show. You can pass a revision ID to mgrt show to view an individual revision. If no revision ID is given, then the latest revision is shown.

$ mgrt show -db local-dev 20060102150405
revision 20060102150405
Author:    Andrew Pillar <[email protected]>
Performed: Mon Jan  6 15:04:05 2006

    My first revision

    CREATE TABLE users (
            id INT NOT NULL UNIQUE
    );

Library usage

As well as a CLI application, mgrt can be used as a library should you want to be able to have revisions performed directly in your application. To start using it just import the repository into your code,

import "github.com/andrewpillar/mgrt"

from here you will be able to start creating revisions and performing them against any pre-existing database connection you may have,

// mgrt.Open will wrap sql.Open from the stdlib, and initialize the database
// for performing revisions.
db, err := mgrt.Open("sqlite3", "acme.db")

if err != nil {
    panic(err) // maybe acceptable here
}

rev := mgrt.NewRevision("Andrew", "This is being done from Go.")

if err := rev.Perform(db); err != nil {
    if !errors.Is(err, mgrt.ErrPerformed) {
        panic(err) // not best practice
    }
}

all pre-existing revisions can be retrieved via GetRevisions,

revs, err := mgrt.GetRevisions(db)

if err != nil {
    panic(err) // don't actually do this
}

more information about using mgrt as a library can be found in the Go doc itself for mgrt.

Documentation

Overview

package mgrt provides a collection of functions for performing revisions against any given database connection.

Index

Constants

This section is empty.

Variables

View Source
var (

	// ErrInvalid is returned whenever an invalid Revision ID is encountered. A
	// Revision ID is considered invalid when the time layout 20060102150405
	// cannot be used for parse the ID.
	ErrInvalid = errors.New("revision id invalid")

	// ErrPerformed is returned whenever a Revision has already been performed.
	// This can be treated as a benign error.
	ErrPerformed = errors.New("revision already performed")

	ErrNotFound = errors.New("revision not found")
)

Functions

func PerformRevisions

func PerformRevisions(db *DB, revs0 ...*Revision) error

PerformRevisions will perform the given revisions against the given database. The given revisions will be sorted into ascending order first before they are performed. If any of the given revisions have already been performed then the Errors type will be returned containing *RevisionError for each revision that was already performed.

func Register

func Register(typ string, db *DB)

Register will register the given *DB for the given database type. If the given type is a duplicate, then this panics. If the given *DB is nil, then this panics.

func RevisionPerformed

func RevisionPerformed(db *DB, rev *Revision) error

RevisionPerformed checks to see if the given Revision has been performed against the given database.

Types

type Collection

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

Collection stores revisions in a binary tree. This ensures that when they are retrieved, they will be retrieved in ascending order from when they were initially added.

func (*Collection) Len

func (c *Collection) Len() int

Len returns the number of items in the collection.

func (*Collection) Put

func (c *Collection) Put(r *Revision) error

Put puts the given Revision in the current Collection.

func (*Collection) Slice

func (c *Collection) Slice() []*Revision

Slice returns a sorted slice of all the revisions in the collection.

type DB

type DB struct {
	*sql.DB

	// Type is the type of database being connected to. This will be passed to
	// sql.Open when the connection is being opened.
	Type string

	// Init is the function to call to initialize the database for performing
	// revisions.
	Init func(*sql.DB) error

	// Parameterize is the function that is called to parameterize the query
	// that will be executed against the database. This will make sure the
	// correct SQL dialect is being used for the type of database.
	Parameterize func(string) string
}

DB is a thin abstraction over the *sql.DB struct from the stdlib.

func Open

func Open(typ, dsn string) (*DB, error)

Open is a utility function that will call sql.Open with the given typ and dsn. The database connection returned from this will then be passed to Init for initializing the database.

type Errors

type Errors []error

Errors is a collection of errors that occurred.

func (Errors) Error

func (e Errors) Error() string

Error returns the string representation of all the errors in the underlying slice. Each error will be on a separate line in the returned string.

type Revision

type Revision struct {
	ID          string    // ID is the unique ID of the Revision.
	Category    string    // Category of the revision.
	Author      string    // Author is who authored the original Revision.
	Comment     string    // Comment provides a short description for the Revision.
	SQL         string    // SQL is the code that will be executed when the Revision is performed.
	PerformedAt time.Time // PerformedAt is when the Revision was executed.
}

Revision is the type that represents what SQL code has been executed against a database as a revision. Typically, this would be changes made to the database schema itself.

func GetRevision

func GetRevision(db *DB, id string) (*Revision, error)

GetRevision get's the Revision with the given ID.

func GetRevisions

func GetRevisions(db *DB, n int) ([]*Revision, error)

GetRevisions returns a list of all the revisions that have been performed against the given database. If n is <= 0 then all of the revisions will be retrieved, otherwise, only the given amount will be retrieved. The returned revisions will be ordered by their performance date descending.

func LoadRevisions added in v3.2.4

func LoadRevisions(dir string) ([]*Revision, error)

LoadRevisions loads all of the revisions from the given directory. This will only load from a file with the .sql suffix in the name.

func NewRevision

func NewRevision(author, comment string) *Revision

NewRevision creates a new Revision with the given author, and comment.

func NewRevisionCategory added in v3.2.0

func NewRevisionCategory(category, author, comment string) *Revision

NewRevisionCategory creates a new Revision in the given category with the given author and comment.

func OpenRevision

func OpenRevision(path string) (*Revision, error)

OpenRevision opens the revision at the given path.

func UnmarshalRevision

func UnmarshalRevision(r io.Reader) (*Revision, error)

UnmarshalRevision will unmarshal a Revision from the given io.Reader. This will expect to see a comment block header that contains the metadata about the Revision itself. This will check to see if the given Revision ID is valid. A Revision id is considered valid when it can be parsed into a valid time via time.Parse using the layout of 20060102150405.

func (*Revision) Perform

func (r *Revision) Perform(db *DB) error

Perform will perform the current Revision against the given database. If the Revision is emtpy, then nothing happens. If the Revision has already been performed, then ErrPerformed is returned.

func (*Revision) Slug added in v3.2.1

func (r *Revision) Slug() string

Slug returns the slug of the revision ID, this will be in the format of category/id if the revision belongs to a category.

func (*Revision) String

func (r *Revision) String() string

String returns the string representation of the Revision. This will be the comment block header followed by the Revision SQL itself.

func (*Revision) Title

func (r *Revision) Title() string

Title will extract the title from the comment of the current Revision. First, this will truncate the title to being 72 characters. If the comment was longer than 72 characters, then the title will be suffixed with "...". If a LF character can be found in the title, then the title will be truncated again up to where that LF character occurs.

type RevisionError

type RevisionError struct {
	ID  string // ID is the ID of the revisions that errored.
	Err error  // Err is the underlying error itself.
}

RevisionError represents an error that occurred with a revision.

func (*RevisionError) Error

func (e *RevisionError) Error() string

func (*RevisionError) Unwrap

func (e *RevisionError) Unwrap() error

Unwrap returns the underlying error that caused the original RevisionError.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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