func FromFS(ctx context.Context, session gocqlx.Session, f fs.FS) error {
	dbm, err := List(ctx, session)
	if err != nil {
		return fmt.Errorf("list migrations: %s", err)
	}
	fm, err := fs.Glob(f, "*.cql")
	if err != nil {
		return fmt.Errorf("list migrations: %w", err)
	}
	if len(fm) == 0 {
		return fmt.Errorf("no migration files found")
	}
	sort.Strings(fm)
	if len(dbm) > len(fm) {
		return fmt.Errorf("database is ahead")
	}
	for i := 0; i < len(dbm); i++ {
		if dbm[i].Name != fm[i] {
			fmt.Println(dbm[i].Name, fm[i], i)
			return errors.New("inconsistent migrations")
		}
		c, err := fileChecksum(f, fm[i])
		if err != nil {
			return fmt.Errorf("calculate checksum for %q: %s", fm[i], err)
		}
		if dbm[i].Checksum != c {
			return fmt.Errorf("file %q was tempered with, expected md5 %s", fm[i], dbm[i].Checksum)
		}
	}
	if len(dbm) > 0 {
		last := len(dbm) - 1
		if err := applyMigration(ctx, session, f, fm[last], dbm[last].Done); err != nil {
			return fmt.Errorf("apply migration %q: %s", fm[last], err)
		}
	}
	for i := len(dbm); i < len(fm); i++ {
		if err := applyMigration(ctx, session, f, fm[i], 0); err != nil {
			return fmt.Errorf("apply migration %q: %s", fm[i], err)
		}
	}
	if err = session.AwaitSchemaAgreement(ctx); err != nil {
		return fmt.Errorf("awaiting schema agreement: %s", err)
	}
	return nil
}
FromFS executes new CQL files from a file system abstraction (io/fs.FS).
The provided FS has to be a flat directory containing *.cql files.

It supports code based migrations, see Callback and CallbackFunc.
Any comment in form `-- CALL <name>;` will trigger an CallComment callback.
package migrate
import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"
	"github.com/gocql/gocql"
	"github.com/scylladb/gocqlx/v2"
	"github.com/scylladb/gocqlx/v2/qb"
)
var DefaultAwaitSchemaAgreement = AwaitSchemaAgreementDisabled
type awaitSchemaAgreement int
const (
	AwaitSchemaAgreementDisabled awaitSchemaAgreement = iota
	AwaitSchemaAgreementBeforeEachFile
	AwaitSchemaAgreementBeforeEachStatement
)
func (as awaitSchemaAgreement) ShouldAwait(stage awaitSchemaAgreement) bool {
	return as == stage
}
const (
	infoSchema = `CREATE TABLE IF NOT EXISTS gocqlx_migrate (
	name text,
	checksum text,
	done int,
	start_time timestamp,
	end_time timestamp,
	PRIMARY KEY(name)
)`
	selectInfo = "SELECT * FROM gocqlx_migrate"
)
type Info struct {
	Name      string
	Checksum  string
	Done      int
	StartTime time.Time
	EndTime   time.Time
}
func List(ctx context.Context, session gocqlx.Session) ([]*Info, error) {
	if err := ensureInfoTable(ctx, session); err != nil {
		return nil, err
	}
	q := session.ContextQuery(ctx, selectInfo, nil)
	var v []*Info
	if err := q.SelectRelease(&v); err == gocql.ErrNotFound {
		return nil, nil
	} else if err != nil {
		return v, err
	}
	sort.Slice(v, func(i, j int) bool {
		return v[i].Name < v[j].Name
	})
	return v, nil
}
func ensureInfoTable(ctx context.Context, session gocqlx.Session) error {
	return session.ContextQuery(ctx, infoSchema, nil).ExecRelease()
}
func Migrate(ctx context.Context, session gocqlx.Session, dir string) error {
	return FromFS(ctx, session, os.DirFS(dir))
}