ddl

package
v9.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: AGPL-3.0 Imports: 5 Imported by: 0

Documentation

Overview

Package ddl renders a package's embedded schema against a dialect and a table prefix, and vets the prefix against every identifier the schema would create.

It exists because six packages in this module ship the same three .sql files and the same forty lines of Go to render them: pick the body for the dialect, substitute the prefix, split on semicolons, and check the result is a legal identifier. Six copies is six chances for one of them to drift — and one of them already had, in the one place drift is invisible: whether the prefix carries its own trailing separator or the template supplies it.

Like database/dialect, this is a leaf. A migrations subpackage cannot import its parent without closing a cycle through the parent's tests, so the shared pieces have to live below both.

How a table name is built

A name has three parts, and only the first is configurable:

ddb          _  audit_log   _  entries
consumer        component      table
namespace       segment

The component segment is owned by the schema and baked into its DDL, so a table always says which package created it. A consumer reading their schema cold can tell audit_log_entries from metering_events without consulting this module.

The namespace is the caller's, and is optional. Empty renders the component's own names — audit_log_entries — which is what a consumer with one application per database wants. Setting it to "ddb" renders ddb_audit_log_entries, which is what a consumer sharing a database between applications wants:

CREATE TABLE {{PREFIX}}audit_log_entries
         ""  -> audit_log_entries
      "ddb"  -> ddb_audit_log_entries

The separator belongs to the renderer, not the caller: a non-empty namespace has '_' appended once, here. A caller who passes "ddb_" would otherwise render ddb__audit_log_entries — legal SQL, and a table nobody meant to name — so a namespace ending in '_' is rejected rather than silently accepted.

Index

Constants

View Source
const MaxIdentifierLength = 63

MaxIdentifierLength is the longest identifier the supported engines all accept.

Postgres allows 63 bytes and silently truncates anything longer; MySQL allows 64 and errors. Truncation is the dangerous half: two index names that differ only past byte 63 become one name, and the second CREATE INDEX fails against a schema that looks correct. Validating against the smaller limit turns both outcomes into the same error, raised before any DDL runs.

View Source
const Placeholder = "{{PREFIX}}"

Placeholder is the token every schema in this module uses for its table prefix.

Variables

View Source
var ErrPrefixTooLong = platformerrors.New("table prefix renders an over-long SQL identifier")

ErrPrefixTooLong indicates a prefix that renders an identifier longer than the supported engines accept. It is distinct from dialect.ErrInvalidIdentifier because the fix is different: the prefix is well-formed, just too long.

View Source
var ErrPrefixTrailingSeparator = platformerrors.New("table prefix must not end in '_'")

ErrPrefixTrailingSeparator indicates a namespace ending in '_'. The renderer supplies the separator, so a namespace carrying one too renders a doubled separator — legal SQL, and a table nobody meant to name.

Functions

func Qualify

func Qualify(namespace string) string

Qualify renders the namespace portion of an identifier: empty for an empty namespace, and the namespace with a single trailing '_' otherwise.

It is exported because packages build some names in Go rather than in SQL — a query builder naming the table it selects from — and those names have to agree with the DDL exactly.

Types

type Schema

type Schema struct {
	// Component names the owning package, and appears in every error this type
	// raises so a failure says which schema rejected the prefix.
	Component string

	Postgres string
	MySQL    string
	SQLite   string
}

Schema is one package's DDL, in each dialect it supports.

func (Schema) Identifiers

func (s Schema) Identifiers(namespace string) []string

Identifiers returns every identifier the schema would create under prefix, across all three dialects, sorted and deduplicated.

It reads them out of the DDL rather than from a hand-maintained list, so an index added to the .sql files is covered by validation the moment it is added — which is what the previous per-package TableSuffixes lists could not do. They named the tables only, leaving the longest identifiers in every schema, the index names, unchecked.

func (Schema) SQL

func (s Schema) SQL(d dialect.Dialect, prefix string) (string, error)

SQL renders the same DDL as Statements, joined back into one migration body. It is what a caller hands to database/migrate's WithGeneratedMigration.

The comments are already stripped, which matters: goose splits a migration into statements on semicolons, and a '--' comment containing one would be torn in half.

func (Schema) Statements

func (s Schema) Statements(d dialect.Dialect, prefix string) ([]string, error)

Statements renders the DDL for the dialect against prefix and splits it into individually executable statements, each table before its indexes.

func (Schema) ValidatePrefix

func (s Schema) ValidatePrefix(namespace string) error

ValidatePrefix reports whether prefix renders a legal identifier for every table and index the schema creates.

The prefix is interpolated into query text rather than bound, so it is restricted rather than escaped. Vetting the prefix alone would not be enough: every rendered name has to be legal too, and a prefix that is fine on its own can still push an index name past the length limit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL