Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go-bindata support #6

Closed
hsyed opened this issue Feb 11, 2017 · 8 comments
Closed

go-bindata support #6

hsyed opened this issue Feb 11, 2017 · 8 comments

Comments

@hsyed
Copy link
Contributor

hsyed commented Feb 11, 2017

I am embedding migrations into the product I am working on. I could do with a go-bindata support in the Migrator. go-bindata generated files have no dependencies -- which fits with pgx / tern.

hsyed added a commit to axsy-dev/tern that referenced this issue Feb 12, 2017
@jackc
Copy link
Owner

jackc commented Feb 12, 2017

Have you looked at Migrator.AppendMigration? With that you wouldn't need go-bindata or to modify tern/migrate to have your migration data embedded in your program. I'm not totally opposed to making the file system loader more abstract, but I think what you want can be done without it.

hsyed added a commit to axsy-dev/tern that referenced this issue Feb 12, 2017
@hsyed
Copy link
Contributor Author

hsyed commented Feb 12, 2017

We are working on a multi-tenant meta-schema based access layer / incremental event processing engine for very specific use-cases -- the business logic is runtime installable into postgres (plv8).

There is a thin layer of DDL / stored procedures and JS framework that needs to be installed into the database. I say thin, but I suspect there will be quite a bit of it !

The JS framework will be written by a colleague who will be comping online shortly, I don't yet know what approach he has in mind for packaging the JS.

I have given it some thought and it might make more sense for us to generate go files with AppendMigration calls -- but that does mean being careful with versioning !

@hsyed
Copy link
Contributor Author

hsyed commented Feb 12, 2017

I am almost done with getting go-bindata working -- what do you think of this (this depends on the refactoring I did in my PR) ? I can commit this into the PR tomorrow. Gonna hit the sack now !


// MigrationDir is a function that follows the semantics defined by the go-bindata AssetDir function:
// AssetDir returns the file names below a certain
// returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
//     data/
//       foo.txt
//       img/
//         a.png
//         b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
type MigrationDir func (string) ([]string, error)

// MigrationAsset is a function that follows the semantics defined by the go-bindata Asset function:
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
type MigrationAsset func (string) ([]byte, error)

// recursive function to harvest the shared sql templates.
func traverseShared(l migrationLoader, dir MigrationDir, asset MigrationAsset, cwd string, sps []string) error {
	for _, name := range sps {
		filePath := filepath.Join(cwd, name)
		if sharedDirs, err := dir(name); err == nil {
			traverseShared(l, dir, asset, filePath, sharedDirs)
		} else if body, err := asset(filePath); err != nil {
			return err
		} else if err := l.loadShared(name, body); err != nil {
			return err
		}
	}
	return nil
}

type kv struct {
	key string
	value []byte
}

func (m * Migrator) LoadMigrationsFromGoBindata(base string, dir MigrationDir, asset MigrationAsset) error {
	if as, err := dir(base); err != nil {
		return err
	} else {
		toResolve := []kv{}
		loader := m.newMigrationLoader(base+"/")
		for _, name := range as {
			filePath := filepath.Join(base, name)
			// first check if the entry is a subdirectory, if it is harvest shared sql
			if shared, err := dir(filePath); err == nil {
				if err := traverseShared(loader, dir, asset, filePath, shared); err != nil {
					return err
				}
			// otherwise it must be a migration at the root, in which case defer processing it till all
			// shared sql has been loaded
			} else if body, err := asset(filePath); err != nil {
				return err
			} else {
				toResolve = append(toResolve, kv{ key: name, value: body})
			}
		}
		// now resolve the actual migrations
		for k, v := range toResolve {
			if err := loader.load(k, v); err != nil { return err }
		}
		return nil
	}
}

hsyed added a commit to axsy-dev/tern that referenced this issue Feb 12, 2017
hsyed added a commit to axsy-dev/tern that referenced this issue Feb 12, 2017
hsyed added a commit to axsy-dev/tern that referenced this issue Feb 12, 2017
@jackc
Copy link
Owner

jackc commented Feb 12, 2017

I'm still mostly of the opinion that Migrator.AppendMigration is a sufficient integration point for non-file system migration sources, but that said, if you really feel the need for more integrated support there is an alternative abstraction that may be much cleaner than directly adding go-bindata support to the Migrator type.

A quick look at migrate.go indicates that FindMigrations and LoadMigrations are the only methods that directly work with the file system. Further inspection of those methods shows that the only operations they perform are directory listings and reading files. What if we had a pseudo-file system interface that had those two methods? The default implementation could read the real file system. An alternative go-bindata implementation could read from there. In this way the only changes to the Migrator type would be to use that interface instead of directly using the Go file system functions. This also shows the advantage of using a options struct for configuring Migrator. A file system interface value can be added to that struct without changing the API at all.

@hsyed
Copy link
Contributor Author

hsyed commented Feb 14, 2017

I think I could use append migration as things stand now. However, I am storing the migrations in the bindata according to the naming conventions of tern, and I have put in the above / below string. Plus It would be nice to be able to rely on the template support if needed.

I did pursue the implementation you suggested at first since I didn't understand exactly what the two methods were doing. I'll give creating the filesystem abstraction a whirl.

@hsyed
Copy link
Contributor Author

hsyed commented Feb 14, 2017

Hmm How about ?

type MigratorFS interface {
	ReadDir(dirname string) ([]os.FileInfo, error)
	ReadFile(filename string) ([]byte, error)
	Glob(pattern string) (matches []string, err error) // <-- easy enough to implement
}

Turns out it's literally a copy paste -- I love the interfaces in go.

hsyed added a commit to axsy-dev/tern that referenced this issue Feb 14, 2017
hsyed added a commit to axsy-dev/tern that referenced this issue Feb 14, 2017
@hsyed
Copy link
Contributor Author

hsyed commented Feb 14, 2017

My colleague has come online and we have decided on bundling the JS framework as a single plv8_init function. So my needs are currently met by LoadMigration. I'd like to get the FS abstraction in though !

jackc added a commit that referenced this issue Feb 15, 2017
@hsyed hsyed closed this as completed Feb 15, 2017
@bojanz
Copy link

bojanz commented Aug 10, 2020

If anyone is wondering how to integrate tern and vfsgen:
https://github.com/runbilliam/billiam/commit/635b810074ce72c87027ed954f3454708590076c

The main part being:

import (
	"net/http"
	"os"

	"github.com/shurcooL/httpfs/path/vfspath"
	"github.com/shurcooL/httpfs/vfsutil"
)

// migratorFS wraps a http.Filesystem for usage with jackc/tern.
type migratorFS struct {
	fs http.FileSystem
}

// ReadDir implements the migrate.MigratorFS interface.
func (m migratorFS) ReadDir(dirname string) ([]os.FileInfo, error) {
	return vfsutil.ReadDir(m.fs, dirname)
}

// ReadFile implements the migrate.MigratorFS interface.
func (m migratorFS) ReadFile(filename string) ([]byte, error) {
	return vfsutil.ReadFile(m.fs, filename)
}

// Glob implements the migrate.MigratorFS interface.
func (m migratorFS) Glob(pattern string) (matches []string, err error) {
	return vfspath.Glob(m.fs, pattern)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants