Documentation
¶
Index ¶
- Constants
- Variables
- func ComputeSchemaChecksum(content string) string
- func Migrate(ctx context.Context, db Execer, schemaPath string) error
- func MigrateFromString(ctx context.Context, db Execer, content string) error
- func MigrateWithOptions(ctx context.Context, db Execer, schemaPath string, opts MigrateOptions) (skipped bool, err error)
- type Execer
- type GeneratedSQL
- type InternalMigrateOptions
- type ListGeneratedSQL
- type MigrateOptions
- type MigrationRecord
- type Migrator
- func (m *Migrator) ApplyDDL(ctx context.Context) error
- func (m *Migrator) GetLastMigration(ctx context.Context) (*MigrationRecord, error)
- func (m *Migrator) GetStatus(ctx context.Context) (*Status, error)
- func (m *Migrator) HasSchema() bool
- func (m *Migrator) MigrateWithTypes(ctx context.Context, types []TypeDefinition) error
- func (m *Migrator) MigrateWithTypesAndOptions(ctx context.Context, types []TypeDefinition, opts InternalMigrateOptions) error
- func (m *Migrator) SchemaPath() string
- type Status
- type TypeDefinition
Constants ¶
const CodegenVersion = "1"
CodegenVersion is incremented when SQL generation templates or logic change. This ensures migrations re-run even if schema checksum matches. Bump this when:
- SQL templates in tooling/schema/templates/ change
- Codegen logic in schema/codegen.go or schema/codegen_list.go changes
- New function patterns are added
Variables ¶
var ( DetectCycles = schema.DetectCycles ComputeRelationClosure = schema.ComputeRelationClosure AnalyzeRelations = sqlgen.AnalyzeRelations ComputeCanGenerate = sqlgen.ComputeCanGenerate GenerateSQL = sqlgen.GenerateSQL GenerateListSQL = sqlgen.GenerateListSQL CollectFunctionNames = sqlgen.CollectFunctionNames )
Function aliases from schema and sqlgen packages.
Functions ¶
func ComputeSchemaChecksum ¶
ComputeSchemaChecksum returns a SHA256 hash of the schema content. Used to detect schema changes for skip-if-unchanged optimization.
func Migrate ¶
Migrate parses an OpenFGA schema file and applies it to the database in one operation. This is the recommended high-level API for most applications.
The function is idempotent - safe to call on every application startup. It validates the schema, generates specialized SQL functions per relation, and applies everything atomically within a transaction (when db supports BeginTx).
Migration workflow:
- Reads the schema file at schemaPath
- Parses OpenFGA DSL using the official parser
- Validates schema (cycle detection, referential integrity)
- Generates specialized check_permission and list_accessible functions
- Applies generated SQL atomically via transaction
Example usage on application startup:
if err := migrator.Migrate(ctx, db, "schemas/schema.fga"); err != nil {
log.Fatalf("migration failed: %v", err)
}
For embedded schemas (no file I/O), use MigrateFromString. For fine-grained control (dry-run, skip optimization), use MigrateWithOptions. For programmatic use with pre-parsed types, use Migrator directly:
types, _ := parser.ParseSchema("schemas/schema.fga")
m := migrator.NewMigrator(db, "schemas/schema.fga")
err := m.MigrateWithTypes(ctx, types)
func MigrateFromString ¶
MigrateFromString parses schema content and applies it to the database. Useful for testing or when schema is embedded in the application binary.
This allows bundling the authorization schema with the application rather than reading from disk, which simplifies deployment and versioning.
Example:
//go:embed schema.fga var embeddedSchema string err := migrator.MigrateFromString(ctx, db, embeddedSchema)
The migration is idempotent and transactional (when using *sql.DB).
func MigrateWithOptions ¶
func MigrateWithOptions(ctx context.Context, db Execer, schemaPath string, opts MigrateOptions) (skipped bool, err error)
MigrateWithOptions performs migration with control over dry-run and skip behavior. Use this when you need to preview migrations, force re-application, or detect skips.
The skip-if-unchanged optimization compares the schema content hash and codegen version against the last successful migration. If both match and Force is false, the migration is skipped (skipped=true). This avoids redundant function regeneration on every application restart when schemas are stable.
Returns (skipped, error):
- skipped=true if migration was skipped due to unchanged schema (only when Force=false and DryRun=nil)
- error is non-nil if migration failed (parse error, validation error, DB error)
Example: Generate migration script without applying
var buf bytes.Buffer
_, err := migrator.MigrateWithOptions(ctx, db, "schemas/schema.fga", migrator.MigrateOptions{
DryRun: &buf,
})
os.WriteFile("migrations/001_authz.sql", buf.Bytes(), 0644)
Example: Force re-migration (e.g., after manual schema corruption)
skipped, err := migrator.MigrateWithOptions(ctx, db, "schemas/schema.fga", migrator.MigrateOptions{
Force: true,
})
Types ¶
type Execer ¶
type Execer interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
Execer is the minimal interface needed for schema migration operations. Implemented by *sql.DB, *sql.Tx, and *sql.Conn.
type InternalMigrateOptions ¶
type InternalMigrateOptions struct {
DryRun io.Writer
Force bool
// Version is the melange CLI/library version (e.g., "v0.4.3").
// Recorded in melange_migrations for traceability.
Version string
// SchemaContent is the raw schema text used for checksum calculation to detect schema changes.
// If empty, skip-if-unchanged optimization is disabled.
SchemaContent string
}
InternalMigrateOptions extends MigrateOptions with internal fields.
type ListGeneratedSQL ¶
type ListGeneratedSQL = sqlgen.ListGeneratedSQL
Type aliases for cleaner code.
type MigrateOptions ¶
type MigrateOptions struct {
// DryRun outputs SQL to the provided writer without applying changes to the database.
// If nil, migration proceeds normally. Use for previewing migrations or generating migration scripts.
DryRun io.Writer
// Force re-runs migration even if schema/codegen unchanged. Use when manually fixing corrupted state or testing.
Force bool
// Version is the melange CLI/library version (e.g., "v0.4.3").
// Recorded in melange_migrations for traceability.
Version string
}
MigrateOptions controls migration behavior (public API).
type MigrationRecord ¶
type MigrationRecord struct {
MelangeVersion string
SchemaChecksum string
CodegenVersion string
FunctionNames []string
}
MigrationRecord represents a row in the melange_migrations table.
type Migrator ¶
type Migrator struct {
// contains filtered or unexported fields
}
Migrator handles loading authorization schemas into PostgreSQL. The migrator is idempotent - safe to run on every application startup.
The migration process:
- Creates/replaces check_permission and list_accessible_* functions
- Loads generated SQL entrypoints into the database
Usage ¶
Use the convenience functions in pkg/migrator for most use cases:
import "github.com/pthm/melange/pkg/migrator" err := migrator.Migrate(ctx, db, "schemas/schema.fga")
For embedded schemas (no file I/O):
err := migrator.MigrateFromString(ctx, db, schemaContent)
Use the Migrator directly when you have pre-parsed TypeDefinitions or need fine-grained control (DDL-only, status checks, etc.):
types, _ := parser.ParseSchema("schemas/schema.fga")
m := migrator.NewMigrator(db, "schemas/schema.fga")
err := m.MigrateWithTypes(ctx, types)
func NewMigrator ¶
NewMigrator creates a new schema migrator. The schemaPath should point to an OpenFGA DSL schema file (e.g., "schemas/schema.fga"). The Execer is typically *sql.DB but can be *sql.Tx for testing.
func (*Migrator) ApplyDDL ¶
ApplyDDL applies any base schema required by Melange. With fully generated SQL entrypoints, no base DDL is required.
func (*Migrator) GetLastMigration ¶
func (m *Migrator) GetLastMigration(ctx context.Context) (*MigrationRecord, error)
GetLastMigration returns the most recent migration record, or nil if none exists. This can be used to check if migration is needed before calling MigrateWithTypesAndOptions.
func (*Migrator) GetStatus ¶
GetStatus returns the current migration status. Useful for health checks or migration diagnostics.
func (*Migrator) HasSchema ¶
HasSchema returns true if the schema file exists. Use this to conditionally run migration or skip if not configured.
func (*Migrator) MigrateWithTypes ¶
func (m *Migrator) MigrateWithTypes(ctx context.Context, types []TypeDefinition) error
MigrateWithTypes performs database migration using pre-parsed type definitions. This is the core migration method used by the tooling package's Migrate function.
The method:
- Validates the schema (checks for cycles)
- Computes derived data (closure)
- Analyzes relations and generates specialized SQL functions
- Applies everything atomically in a transaction: - Generated specialized functions and dispatcher
This is idempotent - safe to run multiple times with the same types.
Uses a transaction if the db supports it (*sql.DB). This ensures the schema is updated atomically or not at all.
func (*Migrator) MigrateWithTypesAndOptions ¶
func (m *Migrator) MigrateWithTypesAndOptions(ctx context.Context, types []TypeDefinition, opts InternalMigrateOptions) error
MigrateWithTypesAndOptions performs database migration with options. This is the full-featured migration method that supports dry-run, skip-if-unchanged, and orphan cleanup.
See MigrateWithTypes for basic usage without options.
func (*Migrator) SchemaPath ¶
SchemaPath returns the path to the schema file.
type Status ¶
type Status struct {
// SchemaExists indicates if the schema.fga file exists on disk.
SchemaExists bool
// TuplesExists indicates if the melange_tuples relation exists (view, table, or materialized view).
// This must be created by the user to map their domain tables.
TuplesExists bool
}
Status represents the current migration state. Use GetStatus to check if the authorization system is properly configured.