Feat v0.5.4 package updates (#34)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #34.
This commit is contained in:
@@ -186,6 +186,130 @@ func CreateExtTables(
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListExtColumns returns the set of column names for a physical table.
|
||||
// Uses PRAGMA table_info on SQLite and information_schema on Postgres.
|
||||
func ListExtColumns(ctx context.Context, db *sql.DB, isPostgres bool, physicalTable string) (map[string]bool, error) {
|
||||
cols := make(map[string]bool)
|
||||
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
if isPostgres {
|
||||
rows, err = db.QueryContext(ctx,
|
||||
`SELECT column_name FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public'`,
|
||||
physicalTable)
|
||||
} else {
|
||||
rows, err = db.QueryContext(ctx, fmt.Sprintf("PRAGMA table_info(%s)", physicalTable))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list columns for %s: %w", physicalTable, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
if isPostgres {
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
continue
|
||||
}
|
||||
cols[name] = true
|
||||
} else {
|
||||
// PRAGMA table_info returns: cid, name, type, notnull, dflt_value, pk
|
||||
var cid int
|
||||
var name, colType string
|
||||
var notnull int
|
||||
var dfltValue sql.NullString
|
||||
var pk int
|
||||
if err := rows.Scan(&cid, &name, &colType, ¬null, &dfltValue, &pk); err != nil {
|
||||
continue
|
||||
}
|
||||
cols[name] = true
|
||||
}
|
||||
}
|
||||
return cols, rows.Err()
|
||||
}
|
||||
|
||||
// MigrateExtTables applies additive-only schema changes for a package update.
|
||||
// For each table in the new manifest:
|
||||
// - New table: CREATE TABLE (full creation, same as CreateExtTables)
|
||||
// - Existing table: ALTER TABLE ADD COLUMN for each new column
|
||||
//
|
||||
// Indexes are applied idempotently with CREATE INDEX IF NOT EXISTS.
|
||||
// New tables are registered in the ext_data_tables catalog.
|
||||
// Returns a log of changes applied (for API response).
|
||||
//
|
||||
// No destructive changes: columns present in DB but absent from manifest are NOT dropped.
|
||||
func MigrateExtTables(
|
||||
ctx context.Context,
|
||||
db *sql.DB,
|
||||
isPostgres bool,
|
||||
stores store.Stores,
|
||||
packageID string,
|
||||
newTables map[string]TableDef,
|
||||
) ([]string, error) {
|
||||
if db == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get existing registered tables for this package
|
||||
existingTableNames := make(map[string]bool)
|
||||
if stores.ExtData != nil {
|
||||
names, err := stores.ExtData.ListTables(ctx, packageID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list existing tables for %s: %w", packageID, err)
|
||||
}
|
||||
for _, n := range names {
|
||||
existingTableNames[n] = true
|
||||
}
|
||||
}
|
||||
|
||||
var changes []string
|
||||
|
||||
for logicalName, td := range newTables {
|
||||
physical := extPhysicalTable(packageID, logicalName)
|
||||
|
||||
if !existingTableNames[logicalName] {
|
||||
// New table — full creation
|
||||
if err := CreateExtTables(ctx, db, isPostgres, stores, packageID,
|
||||
map[string]TableDef{logicalName: td}); err != nil {
|
||||
return changes, fmt.Errorf("create new table %s: %w", physical, err)
|
||||
}
|
||||
changes = append(changes, fmt.Sprintf("created table %s", physical))
|
||||
continue
|
||||
}
|
||||
|
||||
// Existing table — diff columns and add missing ones
|
||||
existingCols, err := ListExtColumns(ctx, db, isPostgres, physical)
|
||||
if err != nil {
|
||||
return changes, err
|
||||
}
|
||||
|
||||
for col, typ := range td.Columns {
|
||||
if existingCols[col] {
|
||||
continue
|
||||
}
|
||||
sqlType := mapColType(typ, isPostgres)
|
||||
alterDDL := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s", physical, col, sqlType)
|
||||
if _, err := db.ExecContext(ctx, alterDDL); err != nil {
|
||||
return changes, fmt.Errorf("add column %s.%s: %w", physical, col, err)
|
||||
}
|
||||
changes = append(changes, fmt.Sprintf("added column '%s' to %s", col, physical))
|
||||
log.Printf("[ext-db] added column %s (%s) to %s", col, sqlType, physical)
|
||||
}
|
||||
|
||||
// Re-apply indexes idempotently
|
||||
for i, idxCols := range td.Indexes {
|
||||
idxName := fmt.Sprintf("idx_%s_%d", physical, i)
|
||||
idxDDL := fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s ON %s (%s)",
|
||||
idxName, physical, strings.Join(idxCols, ", "))
|
||||
if _, err := db.ExecContext(ctx, idxDDL); err != nil {
|
||||
log.Printf("[ext-db] index create failed (%s): %v", idxName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
// DropExtTables drops all physical tables registered for a package and removes
|
||||
// their entries from the ext_data_tables catalog.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user