From 8245f89305c25c7b2598321ff2e913558b4c2b08 Mon Sep 17 00:00:00 2001 From: wispem-wantex Date: Sat, 23 Aug 2025 16:55:02 -0700 Subject: [PATCH] Add parsing of schema indexes --- pkg/schema/parse.go | 20 ++++++++++++++++---- pkg/schema/parse_test.go | 31 ++++++++++++++++++++++++++++--- pkg/schema/table.go | 14 ++++++++++++-- pkg/schema/views.sql | 14 ++++++++++++++ sample_data/test_schemas/food.sql | 1 + 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/pkg/schema/parse.go b/pkg/schema/parse.go index 02c46fe..c797f98 100644 --- a/pkg/schema/parse.go +++ b/pkg/schema/parse.go @@ -24,24 +24,36 @@ func InitDB(sql_schema string) *sqlx.DB { // SchemaFromDB takes a DB connection, checks its schema metadata tables, and returns a Schema. func SchemaFromDB(db *sqlx.DB) Schema { - ret := Schema{} + ret := Schema{Tables: map[string]Table{}, Indexes: map[string]Index{}} var tables []Table err := db.Select(&tables, `select name, is_strict, is_without_rowid 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) + err = db.Select(&tbl.Columns, `select * from columns where table_name = ?`, tbl.TableName) if err != nil { panic(err) } - ret[tbl.TableName] = tbl + ret.Tables[tbl.TableName] = tbl + } + + var indexes []Index + err = db.Select(&indexes, `select index_name, table_name, is_unique from indexes`) + if err != nil { + panic(err) + } + for _, idx := range indexes { + err = db.Select(&idx.Columns, `select column_name from index_columns where index_name = ? order by rank`, idx.Name) + if err != nil { + panic(err) + } + ret.Indexes[idx.Name] = idx } return ret } diff --git a/pkg/schema/parse_test.go b/pkg/schema/parse_test.go index f6ec854..8dd102f 100644 --- a/pkg/schema/parse_test.go +++ b/pkg/schema/parse_test.go @@ -19,11 +19,11 @@ func TestParseSchema(t *testing.T) { 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] + _, is_ok := schema.Tables[tbl_name] assert.True(is_ok) } - foods := schema["foods"] + foods := schema.Tables["foods"] assert.Equal(foods.TableName, "foods") assert.Equal(foods.TypeName, "Food") assert.Equal(foods.TypeIDName, "FoodID") @@ -44,11 +44,36 @@ func TestParseSchema(t *testing.T) { assert.Equal(foods.Columns[16].HasDefaultValue, true) assert.Equal(foods.Columns[16].DefaultValue, "100") - ingredients := schema["ingredients"] + ingredients := schema.Tables["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") + + // ------- + // Indexes + // ------- + + // Unique index + units_name_idx, isOk := schema.Indexes["sqlite_autoindex_units_1"] + require.True(t, isOk) + assert.True(units_name_idx.IsUnique) + assert.Equal("units", units_name_idx.TableName) + assert.Equal([]string{"name"}, units_name_idx.Columns) + + // Non-unique, declared index + foods_protein_idx, isOk := schema.Indexes["foods_protein"] + require.True(t, isOk) + assert.False(foods_protein_idx.IsUnique) + assert.Equal("foods", foods_protein_idx.TableName) + assert.Equal([]string{"protein"}, foods_protein_idx.Columns) + + // Multi-column index + ingr_seq_idx, isOk := schema.Indexes["sqlite_autoindex_ingredients_1"] + require.True(t, isOk) + assert.True(ingr_seq_idx.IsUnique) + assert.Equal("ingredients", ingr_seq_idx.TableName) + assert.Equal([]string{"in_recipe_id", "list_order"}, ingr_seq_idx.Columns) } diff --git a/pkg/schema/table.go b/pkg/schema/table.go index 0d70468..cc69032 100644 --- a/pkg/schema/table.go +++ b/pkg/schema/table.go @@ -32,5 +32,15 @@ type Table struct { TypeName string } -// Schema is a container for a bunch of Tables, indexed by table name. -type Schema map[string]Table +type Index struct { + Name string `db:"index_name"` + TableName string `db:"table_name"` + Columns []string + IsUnique bool `db:"is_unique"` + // TODO: `where ...` for partial indexes +} + +type Schema struct { + Tables map[string]Table + Indexes map[string]Index +} diff --git a/pkg/schema/views.sql b/pkg/schema/views.sql index 6b91a39..ca6eca0 100644 --- a/pkg/schema/views.sql +++ b/pkg/schema/views.sql @@ -22,3 +22,17 @@ create temporary view columns as 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; + +create temporary view indexes as + select idx.name as index_name, + tables.name as table_name, + idx."unique" as is_unique + from tables + join pragma_index_list(tables.name) idx; + +create temporary view index_columns as + select indexes.index_name, + idx_cols.name as column_name, + idx_cols.seqno as rank + from indexes + join pragma_index_info(indexes.index_name) as idx_cols; diff --git a/sample_data/test_schemas/food.sql b/sample_data/test_schemas/food.sql index aa01e51..b96bd8a 100644 --- a/sample_data/test_schemas/food.sql +++ b/sample_data/test_schemas/food.sql @@ -42,6 +42,7 @@ create table foods (rowid integer primary key, density real not null default 1, cook_ratio real not null default 1 ) strict; +create index foods_protein on foods(protein); create table units (rowid integer primary key,