Move schema parsing to its own package
All checks were successful
CI / release-test (push) Successful in 4s

This commit is contained in:
2025-07-12 11:46:50 -07:00
parent e7ee10deb1
commit 378b86b7f1
4 changed files with 30 additions and 31 deletions

64
pkg/schema/parse.go Normal file
View File

@@ -0,0 +1,64 @@
package schema
import (
"gas_stack/pkg/textutils"
"strings"
"github.com/jinzhu/inflection"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
// InitDB creates an in-memory DB from a given schema string.
func InitDB(sql_schema string) *sqlx.DB {
db := sqlx.MustOpen("sqlite3", ":memory:")
db.MustExec(sql_schema)
db.MustExec(`
create temporary view tables as
select l.schema, l.name, l.type, l.ncol, l.wr, l.strict
from sqlite_schema s
left join pragma_table_list l on s.name = l.name
where s.type = 'table';
create temporary view columns as
select tables.name as table_name,
table_info.name as column_name,
lower(table_info.type) as column_type,
"notnull",
dflt_value is not null as has_default_value,
ifnull(dflt_value, 0) dflt_value,
pk as is_primary_key,
fk."table" is not null as is_foreign_key,
ifnull(fk."table", '') as fk_target_table,
ifnull(fk."to", '') as fk_target_column
from tables
join pragma_table_info(tables.name) as table_info
left join pragma_foreign_key_list(tables.name) as fk on fk."from" = column_name;
`)
return db
}
// SchemaFromDB takes a DB connection, checks its schema metadata tables, and returns a Schema.
func SchemaFromDB(db *sqlx.DB) Schema {
ret := Schema{}
var tables []Table
err := db.Select(&tables, `select name, strict from tables`)
if err != nil {
panic(err)
}
for _, tbl := range tables {
tbl.TypeName = textutils.SnakeToCamel(inflection.Singular(tbl.TableName))
tbl.TypeIDName = tbl.TypeName + "ID"
tbl.VarName = strings.ToLower(string(tbl.TableName[0]))
err := db.Select(&tbl.Columns, `select * from columns where table_name = ?`, tbl.TableName)
if err != nil {
panic(err)
}
ret[tbl.TableName] = tbl
}
return ret
}

54
pkg/schema/parse_test.go Normal file
View File

@@ -0,0 +1,54 @@
package schema_test
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gas_stack/pkg/schema"
)
func TestParseSchema(t *testing.T) {
assert := assert.New(t)
schema_sql, err := os.ReadFile("../../sample_data/test_schemas/food.sql")
require.NoError(t, err)
db := schema.InitDB(string(schema_sql))
schema := schema.SchemaFromDB(db)
expected_tbls := []string{"food_types", "foods", "units", "ingredients", "recipes", "iterations", "db_version"}
for _, tbl_name := range expected_tbls {
_, is_ok := schema[tbl_name]
assert.True(is_ok)
}
foods := schema["foods"]
assert.Equal(foods.TableName, "foods")
assert.Equal(foods.TypeName, "Food")
assert.Equal(foods.TypeIDName, "FoodID")
assert.Equal(foods.IsStrict, true)
assert.Len(foods.Columns, 20)
assert.Equal(foods.Columns[0].Name, "rowid")
assert.Equal(foods.Columns[0].Type, "integer")
assert.Equal(foods.Columns[0].IsNotNull, false) // Explicit not-null
assert.Equal(foods.Columns[0].IsPrimaryKey, true)
assert.Equal(foods.Columns[0].IsForeignKey, false)
assert.Equal(foods.Columns[1].Name, "name")
assert.Equal(foods.Columns[1].Type, "text")
assert.Equal(foods.Columns[1].IsNotNull, true)
assert.Equal(foods.Columns[1].HasDefaultValue, false)
assert.Equal(foods.Columns[1].IsPrimaryKey, false)
assert.Equal(foods.Columns[16].Name, "mass")
assert.Equal(foods.Columns[16].Type, "real")
assert.Equal(foods.Columns[16].HasDefaultValue, true)
assert.Equal(foods.Columns[16].DefaultValue, "100")
ingredients := schema["ingredients"]
assert.Equal(ingredients.Columns[0].Name, "rowid")
assert.Equal(ingredients.Columns[0].IsPrimaryKey, true)
assert.Equal(ingredients.Columns[1].Name, "food_id")
assert.Equal(ingredients.Columns[1].IsForeignKey, true)
assert.Equal(ingredients.Columns[1].ForeignKeyTargetTable, "foods")
assert.Equal(ingredients.Columns[1].ForeignKeyTargetColumn, "rowid")
}

34
pkg/schema/table.go Normal file
View File

@@ -0,0 +1,34 @@
package schema
// Column represents a single column in a table.
type Column struct {
TableName string `db:"table_name"`
Name string `db:"column_name"`
Type string `db:"column_type"`
IsNotNull bool `db:"notnull"`
HasDefaultValue bool `db:"has_default_value"`
DefaultValue string `db:"dflt_value"`
IsPrimaryKey bool `db:"is_primary_key"`
IsForeignKey bool `db:"is_foreign_key"`
ForeignKeyTargetTable string `db:"fk_target_table"`
ForeignKeyTargetColumn string `db:"fk_target_column"`
}
// IsNullableForeignKey is a helper function.
func (c Column) IsNullableForeignKey() bool {
return !c.IsNotNull && !c.IsPrimaryKey && c.IsForeignKey
}
// Table is a single SQLite table.
type Table struct {
TableName string `db:"name"`
IsStrict bool `db:"strict"`
Columns []Column
TypeIDName string
VarName string
TypeName string
}
// Schema is a container for a bunch of Tables, indexed by table name.
type Schema map[string]Table