gas-stack/pkg/schema/table.go
~wispem-wantex 1bc7f9111f
Some checks failed
CI / build-docker (push) Successful in 6s
CI / build-docker-bootstrap (push) Has been skipped
CI / release-test (push) Failing after 16s
codegen: implement "without rowid" tables
2026-03-18 10:39:38 -07:00

136 lines
3.6 KiB
Go

package schema
import (
"sort"
"strings"
"git.offline-twitter.com/offline-labs/gas-stack/pkg/textutils"
)
// 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"`
PrimaryKeyRank uint `db:"primary_key_rank"`
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
}
func (c Column) IsNonCodeTableForeignKey() bool {
return c.IsForeignKey && strings.HasSuffix(c.Name, "_id")
}
func (c Column) GoFieldName() string {
if c.Name == "rowid" {
return "ID"
}
if c.IsNonCodeTableForeignKey() {
return textutils.SnakeToCamel(strings.TrimSuffix(c.Name, "_id")) + "ID"
}
return textutils.SnakeToCamel(c.Name)
}
// GoVarName returns the name of a local variable for this column, e.g., when used as a function parameter.
func (c Column) GoVarName() string {
if c.Name == "rowid" {
return strings.ToLower(c.TableName)[0:1] + "ID"
// TODO: Or should it just be "id"??
}
// For foreign keys, use first letter of the target type and "ID". "UserID" => "uID"
if c.IsNonCodeTableForeignKey() {
return strings.ToLower(c.ForeignKeyTargetTable)[0:1] + "ID"
}
// Otherwise, just use the whole name
return c.LongGoVarName()
}
// LongGoVarName returns a lowercased version of the field name (Pascal => Camel).
func (c Column) LongGoVarName() string {
fieldname := c.GoFieldName()
return strings.ToLower(fieldname)[0:1] + fieldname[1:]
}
// Table is a single SQLite table.
type Table struct {
TableName string `db:"name"`
// One of "table", "view", "shadow", or "virtual"
TableType string `db:"table_type"`
IsStrict bool `db:"is_strict"`
IsWithoutRowid bool `db:"is_without_rowid"`
Columns []Column
TypeIDName string
// Default variable name for variables of this type to use when generating Go code
VarName string
// Name of corresponding model type to be generated
GoTypeName string
}
// PrimaryKeyColumns returns the ordered list of columns in this table's primary key.
// This can be useful for "without rowid" tables with composite primary keys.
//
// TODO: needs test
func (t Table) PrimaryKeyColumns() []Column {
pks := make([]Column, 0)
for _, c := range t.Columns {
if c.IsPrimaryKey {
pks = append(pks, c)
}
}
sort.Slice(pks, func(i, j int) bool {
return pks[i].PrimaryKeyRank < pks[j].PrimaryKeyRank
})
return pks
}
func (t Table) GetColumnByName(name string) Column {
for _, c := range t.Columns {
if c.Name == name {
return c
}
}
panic("no such column: " + name)
}
func (t Table) HasAutoTimestamps() (hasCreatedAt bool, hasUpdatedAt bool) {
for _, c := range t.Columns {
if c.Name == "created_at" && c.Type == "integer" {
hasCreatedAt = true
}
if c.Name == "updated_at" && c.Type == "integer" {
hasUpdatedAt = true
}
}
return
}
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
// TODO: identify columns that are expressions
}
type Schema struct {
Tables map[string]Table
Indexes map[string]Index
}