-
Notifications
You must be signed in to change notification settings - Fork 0
Embedded DB Push
Embedded DB Push lets a production binary apply a trusted .gcorm schema
without installing the gco CLI on the target machine. Embed schema files with
Go's embed package and call dbpush.Push from an explicit migrator command,
job, or administrative startup path.
Good fit:
- First-time initialization of an empty production database.
- Self-hosted deployments where shipping one binary is simpler than installing a separate CLI.
- Internal tools or controlled environments where direct schema synchronization is acceptable.
- Tenant/database bootstrap workflows.
Use reviewed SQL migrations instead when:
- Every production change must be reviewed as a stable SQL file.
- You need custom data backfills.
- Multiple application versions run at the same time during deploys.
- Destructive schema changes need a manual rollout plan.
Keep production credentials out of .gcorm files. Prefer env() in the
datasource block:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
Embed the schema directory in the binary:
package main
import (
"context"
"database/sql"
"embed"
"fmt"
"os"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/arsfy/gcorm/pkg/tooling/dbpush"
)
//go:embed schema/*.gcorm
var schemaFS embed.FS
func main() {
ctx := context.Background()
dsn := os.Getenv("DATABASE_URL")
db, err := sql.Open("pgx", dsn)
if err != nil {
panic(err)
}
defer db.Close()
result, err := dbpush.Push(ctx, db, dbpush.Options{
SchemaFS: schemaFS,
SchemaRoot: "schema",
DatabaseURL: dsn,
Lock: true,
AllowDestructive: false,
})
if err != nil {
panic(err)
}
fmt.Printf("db push: models=%d changes=%d hash=%s noop=%v\n",
result.ModelCount, result.ChangeCount, result.SchemaHash, result.Noop)
}For MySQL or SQLite, open the database with the matching database/sql driver
and import that driver in the migrator binary.
When the database has no user tables, dbpush.Push introspects an empty schema,
diffs it against the embedded .gcorm schema, and applies the full set of
CREATE TABLE, index, unique constraint, and foreign key statements that the
DDL generator supports.
This is equivalent to synchronizing from an empty database to the target schema. It does not require a migration directory.
On every run, dbpush.Push:
- Compiles the embedded
.gcormfiles. - Resolves the connection URL from
Options.DatabaseURLor the schema datasource. - Introspects the live database.
- Computes a diff from the live database to the target schema.
- Refuses destructive or review-required changes unless
AllowDestructiveis true. - Executes supported SQL in a transaction.
- Records a row in
__gco_schema_pushesafter a successful non-noop push.
If the live database already matches the embedded schema, the result has
Noop=true and no SQL is executed.
Successful non-noop pushes create and write to __gco_schema_pushes. The table
stores:
- Schema hash.
- Provider.
- Model count.
- Change count.
- Applied time.
- GCORM tool version.
GCORM ignores __gco_schema_pushes and __gco_migrations during introspection,
so metadata tables do not appear as drift from the .gcorm schema.
The metadata table is for audit and visibility. Live database introspection is still the source of truth for deciding what SQL to apply.
Use DryRun to inspect the generated SQL statements without applying them:
result, err := dbpush.Push(ctx, db, dbpush.Options{
SchemaFS: schemaFS,
SchemaRoot: "schema",
DatabaseURL: os.Getenv("DATABASE_URL"),
DryRun: true,
})DryRun does not create user tables and does not write __gco_schema_pushes.
- Run embedded DB Push from one explicit migrator job or admin command, not from every web worker automatically.
- Keep
Lock=truefor production use. - Keep
AllowDestructive=falseby default. - Use a separate database credential with DDL permissions for the migrator.
- Keep application runtime credentials more restrictive when possible.
- Back up important databases before applying schema changes.
- Use
gco migrate init-sqlor reviewed migrations when you need stable SQL artifacts for approval.