Half way to migrations
	
		
			
	
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
					Details
				
			
		
	
				
					
				
			
				
	
				continuous-integration/drone/push Build is failing
				
					Details
				
			
		
	
							parent
							
								
									44982e3e0e
								
							
						
					
					
						commit
						fc397d83c2
					
				@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					FLUX_DATABASE_DRIVER=libsql
 | 
				
			||||||
 | 
					FLUX_DATABASE_PATH=file:./flux-local.db
 | 
				
			||||||
 | 
					FLUX_MIGRATIONS_PATH=./migrations
 | 
				
			||||||
@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"git.runcible.io/androiddrew/flux-feed/config"
 | 
				
			||||||
 | 
						"git.runcible.io/androiddrew/flux-feed/database"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						cfg := config.New()
 | 
				
			||||||
 | 
						dbHandle := database.NewSqlx(cfg.Database)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/joho/godotenv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						Database
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func New() *Config {
 | 
				
			||||||
 | 
						err := godotenv.Load()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &Config{
 | 
				
			||||||
 | 
							Database: DataStore(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "github.com/kelseyhightower/envconfig"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Database struct {
 | 
				
			||||||
 | 
						DatabaseDriver      string `split_words:"true"`
 | 
				
			||||||
 | 
						DatabasePath        string `split_words:"true"`
 | 
				
			||||||
 | 
						MigrationsPath      string `default:"head" split_words:"true"`
 | 
				
			||||||
 | 
						LibsqlConnectorType string `default:"local" split_words:"true"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DataStore() Database {
 | 
				
			||||||
 | 
						var db Database
 | 
				
			||||||
 | 
						envconfig.MustProcess("flux", &db)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return db
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,269 @@
 | 
				
			|||||||
 | 
					package libsql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"database/sql"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						nurl "net/url"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"go.uber.org/atomic"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/golang-migrate/migrate/v4"
 | 
				
			||||||
 | 
						"github.com/golang-migrate/migrate/v4/database"
 | 
				
			||||||
 | 
						"github.com/hashicorp/go-multierror"
 | 
				
			||||||
 | 
						_ "github.com/tursodatabase/go-libsql"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						database.Register("libsql", &Sqlite{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var DefaultMigrationsTable = "schema_migrations"
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						ErrDatabaseDirty  = fmt.Errorf("database is dirty")
 | 
				
			||||||
 | 
						ErrNilConfig      = fmt.Errorf("no config")
 | 
				
			||||||
 | 
						ErrNoDatabaseName = fmt.Errorf("no database name")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						MigrationsTable string
 | 
				
			||||||
 | 
						DatabaseName    string
 | 
				
			||||||
 | 
						NoTxWrap        bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Sqlite struct {
 | 
				
			||||||
 | 
						db       *sql.DB
 | 
				
			||||||
 | 
						isLocked atomic.Bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config *Config
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) {
 | 
				
			||||||
 | 
						if config == nil {
 | 
				
			||||||
 | 
							return nil, ErrNilConfig
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := instance.Ping(); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(config.MigrationsTable) == 0 {
 | 
				
			||||||
 | 
							config.MigrationsTable = DefaultMigrationsTable
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mx := &Sqlite{
 | 
				
			||||||
 | 
							db:     instance,
 | 
				
			||||||
 | 
							config: config,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := mx.ensureVersionTable(); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mx, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ensureVersionTable checks if versions table exists and, if not, creates it.
 | 
				
			||||||
 | 
					// Note that this function locks the database, which deviates from the usual
 | 
				
			||||||
 | 
					// convention of "caller locks" in the Sqlite type.
 | 
				
			||||||
 | 
					func (m *Sqlite) ensureVersionTable() (err error) {
 | 
				
			||||||
 | 
						if err = m.Lock(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if e := m.Unlock(); e != nil {
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									err = e
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									err = multierror.Append(err, e)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query := fmt.Sprintf(`
 | 
				
			||||||
 | 
						CREATE TABLE IF NOT EXISTS %s (version uint64,dirty bool);
 | 
				
			||||||
 | 
					  CREATE UNIQUE INDEX IF NOT EXISTS version_unique ON %s (version);
 | 
				
			||||||
 | 
					  `, m.config.MigrationsTable, m.config.MigrationsTable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := m.db.Exec(query); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) Open(url string) (database.Driver, error) {
 | 
				
			||||||
 | 
						purl, err := nurl.Parse(url)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						dbfile := strings.Replace(migrate.FilterCustomQuery(purl).String(), "sqlite://", "", 1)
 | 
				
			||||||
 | 
						db, err := sql.Open("libsql", dbfile)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qv := purl.Query()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						migrationsTable := qv.Get("x-migrations-table")
 | 
				
			||||||
 | 
						if len(migrationsTable) == 0 {
 | 
				
			||||||
 | 
							migrationsTable = DefaultMigrationsTable
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						noTxWrap := false
 | 
				
			||||||
 | 
						if v := qv.Get("x-no-tx-wrap"); v != "" {
 | 
				
			||||||
 | 
							noTxWrap, err = strconv.ParseBool(v)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("x-no-tx-wrap: %s", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mx, err := WithInstance(db, &Config{
 | 
				
			||||||
 | 
							DatabaseName:    purl.Path,
 | 
				
			||||||
 | 
							MigrationsTable: migrationsTable,
 | 
				
			||||||
 | 
							NoTxWrap:        noTxWrap,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mx, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) Close() error {
 | 
				
			||||||
 | 
						return m.db.Close()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) Drop() (err error) {
 | 
				
			||||||
 | 
						query := `SELECT name FROM sqlite_master WHERE type = 'table';`
 | 
				
			||||||
 | 
						tables, err := m.db.Query(query)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Query: []byte(query)}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if errClose := tables.Close(); errClose != nil {
 | 
				
			||||||
 | 
								err = multierror.Append(err, errClose)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tableNames := make([]string, 0)
 | 
				
			||||||
 | 
						for tables.Next() {
 | 
				
			||||||
 | 
							var tableName string
 | 
				
			||||||
 | 
							if err := tables.Scan(&tableName); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(tableName) > 0 {
 | 
				
			||||||
 | 
								tableNames = append(tableNames, tableName)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := tables.Err(); err != nil {
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Query: []byte(query)}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(tableNames) > 0 {
 | 
				
			||||||
 | 
							for _, t := range tableNames {
 | 
				
			||||||
 | 
								query := "DROP TABLE " + t
 | 
				
			||||||
 | 
								err = m.executeQuery(query)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return &database.Error{OrigErr: err, Query: []byte(query)}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							query := "VACUUM"
 | 
				
			||||||
 | 
							_, err = m.db.Query(query)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &database.Error{OrigErr: err, Query: []byte(query)}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) Lock() error {
 | 
				
			||||||
 | 
						if !m.isLocked.CAS(false, true) {
 | 
				
			||||||
 | 
							return database.ErrLocked
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) Unlock() error {
 | 
				
			||||||
 | 
						if !m.isLocked.CAS(true, false) {
 | 
				
			||||||
 | 
							return database.ErrNotLocked
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) Run(migration io.Reader) error {
 | 
				
			||||||
 | 
						migr, err := io.ReadAll(migration)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						query := string(migr[:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if m.config.NoTxWrap {
 | 
				
			||||||
 | 
							return m.executeQueryNoTx(query)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return m.executeQuery(query)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) executeQuery(query string) error {
 | 
				
			||||||
 | 
						tx, err := m.db.Begin()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Err: "transaction start failed"}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err := tx.Exec(query); err != nil {
 | 
				
			||||||
 | 
							if errRollback := tx.Rollback(); errRollback != nil {
 | 
				
			||||||
 | 
								err = multierror.Append(err, errRollback)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Query: []byte(query)}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := tx.Commit(); err != nil {
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Err: "transaction commit failed"}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) executeQueryNoTx(query string) error {
 | 
				
			||||||
 | 
						if _, err := m.db.Exec(query); err != nil {
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Query: []byte(query)}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) SetVersion(version int, dirty bool) error {
 | 
				
			||||||
 | 
						tx, err := m.db.Begin()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Err: "transaction start failed"}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query := "DELETE FROM " + m.config.MigrationsTable
 | 
				
			||||||
 | 
						if _, err := tx.Exec(query); err != nil {
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Query: []byte(query)}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Also re-write the schema version for nil dirty versions to prevent
 | 
				
			||||||
 | 
						// empty schema version for failed down migration on the first migration
 | 
				
			||||||
 | 
						// See: https://github.com/golang-migrate/migrate/issues/330
 | 
				
			||||||
 | 
						if version >= 0 || (version == database.NilVersion && dirty) {
 | 
				
			||||||
 | 
							query := fmt.Sprintf(`INSERT INTO %s (version, dirty) VALUES (?, ?)`, m.config.MigrationsTable)
 | 
				
			||||||
 | 
							if _, err := tx.Exec(query, version, dirty); err != nil {
 | 
				
			||||||
 | 
								if errRollback := tx.Rollback(); errRollback != nil {
 | 
				
			||||||
 | 
									err = multierror.Append(err, errRollback)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return &database.Error{OrigErr: err, Query: []byte(query)}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := tx.Commit(); err != nil {
 | 
				
			||||||
 | 
							return &database.Error{OrigErr: err, Err: "transaction commit failed"}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Sqlite) Version() (version int, dirty bool, err error) {
 | 
				
			||||||
 | 
						query := "SELECT version, dirty FROM " + m.config.MigrationsTable + " LIMIT 1"
 | 
				
			||||||
 | 
						err = m.db.QueryRow(query).Scan(&version, &dirty)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return database.NilVersion, false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return version, dirty, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "database/sql"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Migrator struct {
 | 
				
			||||||
 | 
						DB *sql.DB
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					package database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.runcible.io/androiddrew/flux-feed/config"
 | 
				
			||||||
 | 
						"github.com/jmoiron/sqlx"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewSqlx(cfg config.Database) *sqlx.DB {
 | 
				
			||||||
 | 
						var dsn string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO add additional database driver support
 | 
				
			||||||
 | 
						switch cfg.DatabaseDriver {
 | 
				
			||||||
 | 
						case "libsql":
 | 
				
			||||||
 | 
							if cfg.DatabaseDriver == "local" {
 | 
				
			||||||
 | 
								dsn = cfg.DatabasePath
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							log.Fatal("Must choose a database driver")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db := sqlx.MustConnect(cfg.DatabaseDriver, dsn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,3 +1,17 @@
 | 
				
			|||||||
module git.runcible.io/androiddrew/fluxfeed
 | 
					module git.runcible.io/androiddrew/flux-feed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.22.5
 | 
					go 1.22.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
 | 
				
			||||||
 | 
						github.com/golang-migrate/migrate/v4 v4.17.1 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/errwrap v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-multierror v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/jmoiron/sqlx v1.4.0 // indirect
 | 
				
			||||||
 | 
						github.com/joho/godotenv v1.5.1 // indirect
 | 
				
			||||||
 | 
						github.com/kelseyhightower/envconfig v1.4.0 // indirect
 | 
				
			||||||
 | 
						github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect
 | 
				
			||||||
 | 
						github.com/tursodatabase/go-libsql v0.0.0-20240725130945-f44f2b84c8c8 // indirect
 | 
				
			||||||
 | 
						go.uber.org/atomic v1.7.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 | 
				
			||||||
 | 
					github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
 | 
				
			||||||
 | 
					github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 | 
				
			||||||
 | 
					github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
 | 
				
			||||||
 | 
					github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
 | 
				
			||||||
 | 
					github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
				
			||||||
 | 
					github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
 | 
				
			||||||
 | 
					github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 | 
				
			||||||
 | 
					github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
 | 
				
			||||||
 | 
					github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
 | 
				
			||||||
 | 
					github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
				
			||||||
 | 
					github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
				
			||||||
 | 
					github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
 | 
				
			||||||
 | 
					github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 | 
				
			||||||
 | 
					github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
 | 
					github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM=
 | 
				
			||||||
 | 
					github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM=
 | 
				
			||||||
 | 
					github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
 | 
					github.com/tursodatabase/go-libsql v0.0.0-20240725130945-f44f2b84c8c8 h1:nxpR20uTcKWd+IcojEUCCieKTmBhrEnIhl0SiwUMBPk=
 | 
				
			||||||
 | 
					github.com/tursodatabase/go-libsql v0.0.0-20240725130945-f44f2b84c8c8/go.mod h1:TjsB2miB8RW2Sse8sdxzVTdeGlx74GloD5zJYUC38d8=
 | 
				
			||||||
 | 
					go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 | 
				
			||||||
 | 
					go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 | 
				
			||||||
					Loading…
					
					
				
		Reference in New Issue