statement

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

README

Statement

The statement package provides SQL statement parsing and analysis capabilities for Spirit. It wraps pkg/parser (Spirit's MySQL-only fork of the TiDB parser) to extract structured information from DDL statements and determine their safety characteristics for online schema changes.

Design Philosophy

Spirit needs to understand DDL statements to:

  1. Validate that statements are supported for online migration (i.e., not an INSERT statement)
  2. Extract table names, schema names, and ALTER clauses
  3. Analyze whether operations are safe for INPLACE algorithm
  4. Transform statements (e.g., rewrite CREATE INDEX to ALTER TABLE)
  5. Parse CREATE TABLE statements into structured data for comparison
  6. Normalize parsed CREATE TABLE definitions to MySQL's canonical form so equivalent schemas compare equal (see Normalization)

Rather than implementing a parser from scratch, Spirit maintains a fork of the TiDB parser (see pkg/parser), which provides:

  • Battle-tested SQL parsing compatible with MySQL syntax
  • AST (Abstract Syntax Tree) representation of statements
  • Ability to restore modified ASTs back to SQL

The statement package adds Spirit-specific logic on top of the parser, such as safety analysis and structured CREATE TABLE parsing.

Core Types

AbstractStatement

AbstractStatement represents a parsed DDL statement with extracted metadata:

type AbstractStatement struct {
    Schema    string          // Schema name (if fully qualified)
    Table     string          // Table name
    Alter     string          // ALTER clause (empty for non-ALTER statements)
    Statement string          // Original SQL statement
    StmtNode  *ast.StmtNode   // Parsed AST node
}

Key Points:

  • For multi-table statements (e.g., DROP TABLE t1, t2), only the first table is stored in Table
  • Alter contains the normalized ALTER clause without ALTER TABLE table_name prefix
  • StmtNode provides access to the full AST for advanced operations
CreateTable

CreateTable represents a parsed CREATE TABLE statement with structured access to all components:

type CreateTable struct {
    Raw          *ast.CreateTableStmt
    TableName    string
    Temporary    bool
    IfNotExists  bool
    Columns      Columns
    Indexes      Indexes
    Constraints  Constraints
    TableOptions *TableOptions
    Partition    *PartitionOptions
}

This structured representation makes it easy to:

  • Compare table definitions
  • Extract specific columns or indexes
  • Generate modified CREATE TABLE statements
  • Validate table structure

Supported Statements

ALTER TABLE

The primary statement type for Spirit migrations:

stmts, err := statement.New("ALTER TABLE t1 ADD COLUMN c INT")
// stmts[0].Table = "t1"
// stmts[0].Alter = "ADD COLUMN `c` INT"

Features:

  • Normalizes ALTER clauses (adds backticks, standardizes formatting)
  • Supports fully qualified table names (schema.table)
  • Can parse multiple ALTER statements in one call
  • Parses ALGORITHM and LOCK clauses but does not reject them; callers should invoke AlterContainsUnsupportedClause on the resulting AbstractStatement if they need to enforce that these clauses are not present (Spirit manages these)
  • Detects column renames via ColumnRenameMap(), which returns a map of old→new column names for both RENAME COLUMN and CHANGE COLUMN syntax
CREATE TABLE

Supports CREATE TABLE for table creation operations:

stmts, err := statement.New("CREATE TABLE t1 (id INT PRIMARY KEY)")
// stmts[0].Table = "t1"
// stmts[0].Alter = "" (empty for non-ALTER)

For structured parsing:

ct, err := statement.ParseCreateTable("CREATE TABLE t1 (id INT PRIMARY KEY)")
// ct.TableName = "t1"
// ct.Columns[0].Name = "id"
// ct.Columns[0].Type = "int"
// ct.Columns[0].PrimaryKey = true
CREATE INDEX

Automatically rewritten to ALTER TABLE:

stmts, err := statement.New("CREATE INDEX idx ON t1 (a)")
// stmts[0].Table = "t1"
// stmts[0].Alter = "ADD INDEX idx (a)"
// stmts[0].Statement = "/* rewritten from CREATE INDEX */ ALTER TABLE `t1` ADD INDEX idx (a)"

Limitations:

  • Functional indexes cannot be converted (use ALTER TABLE ADD INDEX directly). See issue 444.
DROP TABLE

Supports DROP TABLE operations:

stmts, err := statement.New("DROP TABLE t1")
// stmts[0].Table = "t1"
// stmts[0].Alter = "" (empty for non-ALTER)

Validation:

  • Multi-table drops must use the same schema (e.g., DROP TABLE test.t1, test.t2 is valid, but DROP TABLE test.t1, prod.t2 is not)
RENAME TABLE

Supports RENAME TABLE operations:

stmts, err := statement.New("RENAME TABLE t1 TO t2")
// stmts[0].Table = "t1"
// stmts[0].Alter = "" (empty for non-ALTER)

Validation:

  • Cannot rename across schemas (e.g., RENAME TABLE test.t1 TO prod.t2 is rejected)

Safety Analysis

The statement package provides methods to determine if ALTER operations are safe for online execution.

AlgorithmInplaceConsideredSafe

Determines if an ALTER statement can use MySQL's INPLACE algorithm safely:

stmt := statement.MustNew("ALTER TABLE t1 RENAME INDEX a TO b")[0]
err := stmt.AlgorithmInplaceConsideredSafe()
// err == nil (safe - metadata-only operation)

stmt = statement.MustNew("ALTER TABLE t1 ADD COLUMN c INT")[0]
err = stmt.AlgorithmInplaceConsideredSafe()
// err == ErrUnsafeForInplace (unsafe - requires table rebuild)

This feature exists because some DDL changes in MySQL only respond to the INPLACE DDL assertion, even though they are actually INSTANT operations (metadata-only). Since not all INPLACE operations are safe for online execution, we explicitly parse the statement to identify only known safe operations. See https://bugs.mysql.com/bug.php?id=113355.

AlterContainsUnsupportedClause

Checks for clauses that conflict with Spirit's operation:

stmt := statement.MustNew("ALTER TABLE t1 ADD INDEX (a), ALGORITHM=INPLACE")[0]
err := stmt.AlterContainsUnsupportedClause()
// err != nil (ALGORITHM clause not allowed)

Unsupported Clauses:

  • ALGORITHM=... (Spirit manages algorithm selection)
  • LOCK=... (Spirit manages locking strategy)
AlterContainsAddUnique

Detects if an ALTER adds a UNIQUE index:

stmt := statement.MustNew("ALTER TABLE t1 ADD UNIQUE INDEX (email)")[0]
err := stmt.AlterContainsAddUnique()
// err == ErrAlterContainsUnique

This is used to customize the error message if a checksum operation fails. This is because adding a UNIQUE index on non-unique data will result in a checksum failure, and it's helpful to hint this out to the user.

CREATE TABLE Parsing

The package provides detailed parsing of CREATE TABLE statements into structured data. This is extensively used by the lint package.

Basic Usage
ct, err := statement.ParseCreateTable(`
    CREATE TABLE users (
        id INT PRIMARY KEY AUTO_INCREMENT,
        email VARCHAR(255) NOT NULL UNIQUE,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        status ENUM('active', 'inactive') DEFAULT 'active'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`)

// Access columns
for _, col := range ct.Columns {
    fmt.Printf("%s: %s\n", col.Name, col.Type)
}

// Access indexes
for _, idx := range ct.Indexes {
    fmt.Printf("%s (%s): %v\n", idx.Name, idx.Type, idx.Columns)
}

// Access table options
if ct.TableOptions.Engine != nil {
    fmt.Printf("Engine: %s\n", *ct.TableOptions.Engine)
}
Column Information

Each Column provides detailed information:

type Column struct {
    Raw        *ast.ColumnDef    // Raw AST node from parser
    Name       string
    Type       string            // "int", "varchar", "decimal", etc.
    Length     *int              // For VARCHAR(100), Length = 100
    Precision  *int              // For DECIMAL(10,2), Precision = 10
    Scale      *int              // For DECIMAL(10,2), Scale = 2
    Unsigned   *bool
    EnumValues []string          // For ENUM('a','b'), EnumValues = ["a", "b"]
    SetValues  []string          // For SET('x','y'), SetValues = ["x", "y"]
    Nullable   bool
    Default    *string
    OnUpdate   *string           // ON UPDATE CURRENT_TIMESTAMP[(n)] for TIMESTAMP/DATETIME
    GeneratedExpr   *string      // Expression for GENERATED ALWAYS AS (...) columns
    GeneratedStored bool         // true = STORED, false = VIRTUAL
    Check      *string           // Column-level CHECK (...) expression
    SRID       *uint32           // SRID attribute for spatial columns
    AutoInc    bool
    PrimaryKey bool              // Column-level PRIMARY KEY
    Unique     bool              // Column-level UNIQUE
    Comment    *string
    Charset    *string
    Collation  *string
    Options    map[string]string // Additional column options
}

Example:

col := ct.Columns.ByName("email")
// col.Name = "email"
// col.Type = "varchar"
// col.Length = 255
// col.Nullable = false
// col.Unique = true
Index Information

Each Index provides:

type Index struct {
    Raw          *ast.Constraint   // Raw AST node from parser
    Name         string
    Type         string            // "PRIMARY KEY", "UNIQUE", "INDEX", "FULLTEXT", "SPATIAL"
    Columns      []string
    Invisible    *bool
    Using        *string           // "BTREE", "HASH", "RTREE"
    Comment      *string
    KeyBlockSize *uint64
    ParserName   *string           // For FULLTEXT indexes
    Options      map[string]string // Additional index options
}

Example:

idx := ct.Indexes.ByName("PRIMARY")
// idx.Type = "PRIMARY KEY"
// idx.Columns = ["id"]

idx = ct.Indexes.ByName("email")
// idx.Type = "UNIQUE"
// idx.Columns = ["email"]
Constraint Information

Each Constraint represents CHECK or FOREIGN KEY constraints:

type Constraint struct {
    Raw         *ast.Constraint      // Raw AST node from parser
    Name        string
    Type        string                // "CHECK", "FOREIGN KEY"
    Columns     []string
    Expression  *string               // For CHECK constraints
    References  *ForeignKeyReference  // For FOREIGN KEY constraints
    Definition  *string               // Full constraint definition
    NotEnforced bool                  // For CHECK constraints: true when NOT ENFORCED
    Options     map[string]any        // Additional constraint options
}
Partition Information

For partitioned tables, PartitionOptions provides:

type PartitionOptions struct {
    Type         string                // "RANGE", "LIST", "HASH", "KEY", "SYSTEM_TIME"
    Expression   *string               // For HASH and RANGE
    Columns      []string              // For KEY, RANGE COLUMNS, LIST COLUMNS
    Linear       bool
    Partitions   uint64
    Definitions  []PartitionDefinition
    SubPartition *SubPartitionOptions
}

Partitioning is compared as a whole: MySQL cannot alter a partition method in place, so any difference other than a HASH/KEY partition-count change is emitted as REMOVE PARTITIONING followed by a complete PARTITION BY — including its SUBPARTITION BY clause, partition comments, and any explicitly named subpartitions. The per-partition ENGINE clause is the one thing deliberately not compared: MySQL requires every partition to use the table's engine, so it carries no information, yet SHOW CREATE TABLE always prints it while authored SQL does not.

Normalization

MySQL rewrites many constructs when it stores a table definition, so the form a human writes rarely matches what SHOW CREATE TABLE reports. Left unhandled, this produces spurious diffs — a schema file that says active BOOLEAN would appear to differ from the live active tinyint(1), and a diff would emit a pointless MODIFY COLUMN. To prevent this, ParseCreateTable runs a pipeline of normalization rules over the parsed CreateTable before returning it, canonicalizing both sides so Diff compares like with like.

Two layers of canonicalization apply:

  1. The parser already folds most type aliases before Spirit sees them: BOOL/BOOLEANtinyint(1), SERIALbigint unsigned NOT NULL AUTO_INCREMENT UNIQUE, INTEGERint, NVARCHARvarchar, DECdecimal. Nothing in Spirit is needed for these.

  2. Spirit's normalization rules handle the canonicalizations the parser does not — each mirrors something MySQL does when storing the table:

    Rule (normalize_*.go) Canonicalization
    primaryKeyNormalizer inline id INT PRIMARY KEY → table-level PRIMARY KEY index
    indexNormalizer inline c INT UNIQUE → table-level UNIQUE KEY; assigns MySQL's default names to unnamed indexes
    columnCheckNormalizer hoists a column-level CHECK into a table-level constraint
    expressionParenNormalizer rewrites CHECK and generated-column expressions into a canonical parenthesization, keeping only the parentheses the expression's own precedence does not already imply: MySQL stores them fully parenthesized and the parser preserves input parens verbatim, so CHECK ((a=1) OR ((b=2) AND (c=3))) and CHECK (a=1 OR b=2 AND c=3) both canonicalize to the latter
    functionAliasNormalizer rewrites a function name to the one MySQL stores, in expression DEFAULTs, generated columns, CHECKs, functional indexes and partition expressions: STRING_TO_VECTORto_vector, LCASElower, SUBSTRING/MIDsubstr, DAYdayofmonth, and the timestamp family inside an expression default → now()
    binaryAttributeNormalizer resolves the legacy BINARY column attribute to the column charset's _bin collation
    integerDisplayWidthNormalizer strips deprecated integer display widths (int(11)int), keeping tinyint(1) and ZEROFILL
    vectorDimensionNormalizer fills in the default dimension of a VECTOR column declared without one (vectorvector(2048), MySQL 9.7+)
    charsetlessTypeNormalizer drops charset/collation from the types that cannot carry one (VECTOR, spatial) — both the parser's synthetic binary charset and one an author wrote by hand, which MySQL accepts and silently discards
    partitionCommentNormalizer pushes a partition-level COMMENT down onto explicitly named subpartitions that have none, and clears it from the partition — what MySQL stores for PARTITION p0 ... COMMENT 'c' (SUBPARTITION s0, SUBPARTITION s1). A partition comment on implicit subpartitions (SUBPARTITIONS n) stays on the partition
Pipeline

Rules implement the Normalizer interface (normalize.go):

type Normalizer interface {
    Name() string
    Normalize(ct *CreateTable) *CreateTable
}
  • Each rule lives in its own normalize_<name>.go file and self-registers via init() calling registerNormalizer(...) — the same registration pattern pkg/lint uses for linters, so a new rule is added by dropping in a file with no change to Diff or the parser.
  • runNormalizers applies every registered rule at the tail of parseToStruct, after all fields are populated. Rules therefore see the whole struct and are order-independent.
  • Rules rewrite the structured fields of CreateTable (Columns, Indexes, …), never Raw. Code that reads Column.Raw / CreateTable.Raw (e.g. AST Restore, some linters) bypasses normalization.

Because canonicalization happens at parse time, Diff assumes normalized input: two CreateTables obtained from ParseCreateTable are always canonical, so the diff logic compares them structurally without re-deriving equivalences (inline vs. table-level keys, unnamed indexes, etc.). A CreateTable built by hand — without going through ParseCreateTable — is not normalized.

Relationship to spirit fmt

Normalization is an offline, best-effort approximation of what MySQL does: it needs no database and covers the common cases. spirit fmt is the ground-truth canonicalizer — it round-trips a CREATE TABLE through a live MySQL server and reads back SHOW CREATE TABLE, so it captures every transformation, including ones normalization does not implement (e.g. DEFAULT FALSEDEFAULT '0', and the expression rewrites that restructure rather than rename — MOD(a,b)(a % b), INSTR(a,b)locate(b,a), WEEKOFYEAR(d)week(d,3)). Use spirit fmt to canonicalize schema files on disk; normalization keeps in-memory parsing and diffing accurate without a server.

Helper Functions

RemoveSecondaryIndexes

Removes regular secondary indexes from a CREATE TABLE statement while preserving PRIMARY KEY, UNIQUE, and FULLTEXT indexes:

original := `CREATE TABLE t1 (
    id INT PRIMARY KEY,
    email VARCHAR(255) UNIQUE,
    name VARCHAR(100),
    description TEXT,
    INDEX idx_name (name),
    FULLTEXT idx_description (description)
)`

modified, err := statement.RemoveSecondaryIndexes(original)
// Result: CREATE TABLE with PRIMARY KEY, UNIQUE, and FULLTEXT preserved, but without idx_name

What's Preserved:

  • PRIMARY KEY (fundamental to table structure)
  • UNIQUE indexes (enforce data integrity constraints)
  • FULLTEXT indexes (different index type with special requirements)

What's Removed:

  • Regular INDEX (non-unique secondary indexes)

This functionality is used by move tables operations to defer regular index creation until after data is copied, improving copy performance.

GetMissingSecondaryIndexes

Compares two CREATE TABLE statements and generates ALTER TABLE to add missing indexes:

source := `CREATE TABLE t1 (
    id INT PRIMARY KEY,
    email VARCHAR(255),
    INDEX idx_email (email),
    INDEX idx_created (created_at)
)`

target := `CREATE TABLE t1 (
    id INT PRIMARY KEY,
    email VARCHAR(255),
    INDEX idx_email (email)
)`

alterStmt, err := statement.GetMissingSecondaryIndexes(source, target, "t1")
// alterStmt = "ALTER TABLE `t1` ADD INDEX `idx_created` (`created_at`)"

This is used in combination with RemoveSecondaryIndexes to re-add secondary indexes in move tables operations.

Usage Examples

Basic Statement Parsing
stmts, err := statement.New("ALTER TABLE users ADD COLUMN age INT")
if err != nil {
    return err
}

for _, stmt := range stmts {
    fmt.Printf("Table: %s\n", stmt.Table)
    fmt.Printf("Alter: %s\n", stmt.Alter)
    
    if stmt.IsAlterTable() {
        // Perform safety checks
        if err := stmt.AlgorithmInplaceConsideredSafe(); err != nil {
            fmt.Println("Requires Spirit migration")
        } else {
            fmt.Println("Can use native INPLACE")
        }
    }
}
Multiple Statements
sql := `
    ALTER TABLE t1 ADD COLUMN c1 INT;
    ALTER TABLE t2 ADD INDEX (c2);
    ALTER TABLE t3 RENAME INDEX old TO new;
`

stmts, err := statement.New(sql)
if err != nil {
    return err
}

// Process each statement
for _, stmt := range stmts {
    fmt.Printf("Processing %s.%s: %s\n", stmt.Schema, stmt.Table, stmt.Alter)
}
CREATE TABLE Analysis
// Get canonical CREATE TABLE from database
var tableName string
var createStmt string
err := db.QueryRow("SHOW CREATE TABLE users").Scan(&tableName, &createStmt)
if err != nil {
    return err
}

// Parse into structured format
ct, err := statement.ParseCreateTable(createStmt)
if err != nil {
    return err
}

// Check for invisible indexes
if ct.Indexes.HasInvisible() {
    fmt.Println("Table has invisible indexes")
}

// Check for foreign keys
if ct.Constraints.HasForeignKeys() {
    fmt.Println("Table has foreign key constraints")
}

// Find specific column
col := ct.Columns.ByName("email")
if col != nil && col.Unique {
    fmt.Println("Email column has UNIQUE constraint")
}
Safety Validation
stmt := statement.MustNew("ALTER TABLE t1 ADD INDEX (email)")[0]

// Check if safe for INPLACE
if err := stmt.AlgorithmInplaceConsideredSafe(); err != nil {
    switch err {
    case statement.ErrUnsafeForInplace:
        fmt.Println("Requires table rebuild - use Spirit migration")
    case statement.ErrMultipleAlterClauses:
        fmt.Println("Multiple clauses with mixed safety - split into separate ALTERs")
    }
}

// Check for unsupported clauses
if err := stmt.AlterContainsUnsupportedClause(); err != nil {
    fmt.Println("Statement contains ALGORITHM or LOCK clause - remove it")
}

// Check for UNIQUE index
if err := stmt.AlterContainsAddUnique(); err == nil {
    fmt.Println("No UNIQUE index detected")
} else {
    fmt.Println("UNIQUE index detected - may fail if duplicates exist")
}
Table Comparison
// Get source and target CREATE TABLE statements
sourceCreate := getCreateTable(db, "source_table")
targetCreate := getCreateTable(db, "target_table")

// Find missing indexes
alterStmt, err := statement.GetMissingSecondaryIndexes(sourceCreate, targetCreate, "target_table")
if err != nil {
    return err
}

if alterStmt != "" {
    fmt.Println("Need to add indexes:", alterStmt)
    // Execute alterStmt to bring target in sync with source
}

Limitations

  1. Functional Indexes: CREATE INDEX with functional expressions cannot be converted to ALTER TABLE
  2. Single Schema: Multi-table operations must use the same schema
  3. SPATIAL Indexes: Not fully supported in some helper functions
  4. Statements must be parseable by pkg/parser: unparseable DDL cannot be migrated. The most commonly occurring scenarios tend to be complex DEFAULT or CHECK expressions; since the parser is part of this repo, fixes land here directly.

Best Practices

  1. Use SHOW CREATE TABLE: Always parse the output of SHOW CREATE TABLE rather than user-provided CREATE statements. We refer to this in some places as "the canonical show create table".
  2. Use ALTER TABLE: Use ALTER TABLE syntax over CREATE/DROP INDEX syntax. The rewriting of CREATE INDEX is best-effort and does not support complex expressions.

See Also

Documentation

Overview

Package statement is a wrapper around the parser with some added functionality.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotSupportedStatement   = errors.New("not a supported statement type")
	ErrNotAlterTable           = errors.New("not an ALTER TABLE statement")
	ErrMultipleSchemas         = errors.New("statement attempts to modify tables across multiple schemas")
	ErrNoStatements            = errors.New("could not find any compatible statements to execute")
	ErrMixMatchMultiStatements = errors.New("when performing atomic schema changes, all statements must be of type ALTER TABLE")
	ErrUnsafeForInplace        = errors.New("statement contains operations that are not safe for INPLACE algorithm")
	ErrAlterNoSpecs            = errors.New("ALTER TABLE does not specify any changes to make")
	ErrMultipleAlterClauses    = errors.New("ALTER contains multiple clauses. Combinations of INSTANT and INPLACE operations cannot be detected safely. Consider executing these as separate ALTER statements")
	ErrAlterContainsUnique     = errors.New("ALTER contains adding a unique index")
)

Functions

func ByName

func ByName[T HasName](slice []T, name string) *T

ByName is a generic function that finds an element by name in any slice of types with Name field NOTE: This function assumes that names are unique within the slice! That will be true for "canonical" CREATE TABLE statements as returned by SHOW CREATE TABLE, but may not be true for arbitrary input.

func DefaultCollationForCharset added in v0.17.0

func DefaultCollationForCharset(name string) (cs, collation string, ok bool)

DefaultCollationForCharset returns the charset and the collation MySQL applies to it when no COLLATE is written, and whether cs names a charset the parser knows. Both are spelled the way EffectiveCharsetCollation spells them, so values from the two can be compared directly — callers that need to supply a default for DDL which declares no charset at all should come through here rather than reading the parser's registry themselves.

func DiffCreateTables added in v0.17.0

func DiffCreateTables(table, wantCreate, gotCreate string, opts *DiffOptions) (string, error)

DiffCreateTables compares two CREATE TABLE statements and returns a runnable ALTER TABLE statement describing how they differ, or an empty string if they are equivalent under opts.

The comparison is performed by parsing both statements and diffing the structured form via CreateTable.Diff, so it is insensitive to the textual noise two servers can put in SHOW CREATE TABLE output. What is and isn't compared is controlled by opts; with NewDiffOptions the comparison:

  • ignores AUTO_INCREMENT counter values (instance-specific noise),
  • ignores ENGINE and ROW_FORMAT cosmetic defaults,
  • DOES compare column types, nullability, defaults, and per-column / per-table CHARACTER SET and COLLATE,
  • DOES compare indexes (including the primary key) and constraints.

"want" is the schema treated as the source of truth; "got" is the schema being validated against it. The returned statement describes the transformation that would turn "got" into "want", which is what makes the message actionable. "table" is the real (logical) table name used to build the runnable "ALTER TABLE <table>" prefix, escaped so identifiers containing backticks remain valid — the two CREATE TABLE statements themselves may name tables on different instances, so their names are normalized away before the diff and never compared.

CreateTable.Diff deliberately splits some reconciliations across more than one ALTER — a partition-type change, or an option-only index change that MySQL would no-op if its DROP and ADD shared a statement. Those are emitted as separate semicolon-separated ALTERs, in order, rather than merged into one (which would produce SQL that silently does the wrong thing).

If opts is nil, NewDiffOptions() defaults are used.

func GetMissingSecondaryIndexes added in v0.10.1

func GetMissingSecondaryIndexes(sourceCreateTable, targetCreateTable, tableName string) (string, error)

GetMissingSecondaryIndexes compares two CREATE TABLE statements (source and target) and returns an ALTER TABLE statement that adds any missing secondary indexes. Returns an empty string if no indexes need to be added. Considers UNIQUE, FULLTEXT, SPATIAL, and regular INDEX types. PRIMARY KEY is excluded as it's fundamental to table structure.

func ModifyColumnIsMetadataOnly added in v0.16.0

func ModifyColumnIsMetadataOnly(spec *ast.AlterTableSpec) bool

ModifyColumnIsMetadataOnly returns true if a MODIFY/CHANGE COLUMN spec only changes metadata: a VARCHAR redeclaration that neither reorders the column nor declares NOT NULL. Both of those are accepted by MySQL under ALGORITHM=INPLACE but performed with a full table rebuild.

This is a statement-level judgement: without the current column definition we can't tell a VARCHAR length change from an INT-to-VARCHAR conversion, so a true result means "not provably a rebuild" rather than "provably not".

func RemoveSecondaryIndexes added in v0.10.1

func RemoveSecondaryIndexes(createStmt string) (string, error)

RemoveSecondaryIndexes takes a CREATE TABLE statement and returns a modified version without secondary indexes (regular INDEX only). PRIMARY KEY, UNIQUE, and FULLTEXT indexes are preserved.

func SpecOnlyChangesComment added in v0.16.0

func SpecOnlyChangesComment(spec *ast.AlterTableSpec) bool

SpecOnlyChangesComment returns true if every table option in an AlterTableOption spec is a COMMENT change. A table comment change is in-place and metadata-only, but other table options (ENGINE=, ROW_FORMAT=, AUTO_INCREMENT=, ...) can force a table rebuild, so a spec that mixes any of them in is not safe for INPLACE.

Types

type AbstractStatement

type AbstractStatement struct {
	Schema    string // this will be empty unless the table name is fully qualified (ALTER TABLE test.t1 ...)
	Table     string // for statements that affect multiple tables (DROP TABLE t1, t2), only the first is set here!
	Alter     string // may be empty.
	Statement string
	StmtNode  *ast.StmtNode
}

func DeclarativeToImperative added in v0.11.3

func DeclarativeToImperative(current, desired []table.TableSchema, opts *DiffOptions) ([]*AbstractStatement, error)

DeclarativeToImperative compares current and desired schemas and returns the imperative DDL statements (ALTER, CREATE, DROP) needed to transform current into desired.

This is the core of declarative schema management: given two sets of table definitions, compute the minimal set of changes. It is used by spirit's diff subcommand, strata, and GAP.

The returned statements are ordered as CREATE → ALTER → DROP (within each group, tables are sorted alphabetically). This ordering is a correctness property: it ensures the output is safe to execute sequentially (e.g. an ALTER that adds a foreign key referencing a newly-created table will run after the CREATE, and a table referenced by a FK won't be dropped before the referencing ALTER runs).

If opts is nil, NewDiffOptions() defaults are used for table diffs.

func MustNew

func MustNew(statement string) []*AbstractStatement

MustNew is like New but panics if the statement cannot be parsed. It is used by tests.

func New

func New(statement string) ([]*AbstractStatement, error)

func NewWithOptions added in v0.12.0

func NewWithOptions(statement string, opts Options) ([]*AbstractStatement, error)

func (*AbstractStatement) AlgorithmInplaceConsideredSafe

func (a *AbstractStatement) AlgorithmInplaceConsideredSafe() error

AlgorithmInplaceConsideredSafe checks to see if all clauses of an ALTER statement are "safe". We consider an operation to be "safe" if it is "In Place" and "Only Modifies Metadata". See https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html for details. INPLACE DDL is not generally safe for online use in MySQL 8.0, because ADD INDEX can block replicas.

func (*AbstractStatement) AlterContainsAddUnique

func (a *AbstractStatement) AlterContainsAddUnique() error

AlterContainsAddUnique checks to see if any clauses of an ALTER contains add UNIQUE index. We use this to customize the error returned from checksum fails.

func (*AbstractStatement) AlterContainsUnsupportedClause

func (a *AbstractStatement) AlterContainsUnsupportedClause() error

AlterContainsUnsupportedClause checks to see if any clauses of an ALTER statement are unsupported by Spirit. These include clauses like ALGORITHM and LOCK, because they step on the toes of Spirit's own locking and algorithm selection.

func (*AbstractStatement) AlterWithRenamedCheckConstraints added in v0.17.0

func (a *AbstractStatement) AlterWithRenamedCheckConstraints(renames map[string]string) (string, []string, error)

AlterWithRenamedCheckConstraints returns this ALTER's clauses with its check constraint symbols rewritten for a table other than the one the user named: the copy algorithm's _new table, which holds the same check constraints under different names because check constraint names are unique per schema rather than per table.

renames maps a lower-cased check constraint name on the user's table to the name the same constraint has on the table the ALTER will be applied to. Names in DROP CHECK / DROP CONSTRAINT and ALTER CHECK clauses are translated through it; a name that is not in the map is left alone, so MySQL still reports it as missing rather than spirit guessing at what was meant.

A named check constraint being added by this same ALTER under a name it also drops (the "widen this constraint" idiom: DROP CHECK c, ADD CONSTRAINT c CHECK (...)) has its symbol removed, because the user's table still owns that name for as long as it exists, and adding it to a second table in the schema is an error. MySQL generates a name instead - the same outcome the copy algorithm already produces for every check constraint it copies, and the resolution recommended in issue #418. Those names are returned so the caller can report them.

func (*AbstractStatement) AsAlterTable

func (a *AbstractStatement) AsAlterTable() (*ast.AlterTableStmt, bool)

AsAlterTable is a helper function that simply wraps the type case so the caller doesn't have to import the ast package and use the cast syntax

func (*AbstractStatement) CheckConstraintsReferenced added in v0.17.0

func (a *AbstractStatement) CheckConstraintsReferenced() []string

CheckConstraintsReferenced returns the check constraint names this ALTER refers to by name, i.e. the names in its DROP CHECK / DROP CONSTRAINT and ALTER CHECK clauses. It returns nil for a statement that is not an ALTER TABLE, or one that names no check constraints.

Note that MySQL's DROP CONSTRAINT is not specific to check constraints - it also drops a foreign key or a unique constraint of that name - so a name it contributes here is only a candidate. Callers match it against the check constraints that the table actually has.

func (*AbstractStatement) ColumnRenameMap added in v0.13.0

func (a *AbstractStatement) ColumnRenameMap() map[string]string

ColumnRenameMap returns a mapping of old column name → new column name for any RENAME COLUMN or CHANGE COLUMN (with a different name) specs in this ALTER TABLE statement. Returns nil if there are no renames or if this is not an ALTER TABLE statement. MySQL column identifiers are case-insensitive, so a case-only change (e.g. foo → FOO) is not considered a rename: the data mapping is unaffected and case-insensitive identity matching handles it. The map keys and values keep the case as typed in the ALTER; consumers must match them against declared column names case-insensitively.

func (*AbstractStatement) GenericConstraintDrops added in v0.17.0

func (a *AbstractStatement) GenericConstraintDrops() []string

GenericConstraintDrops returns the names in this ALTER's DROP CONSTRAINT clauses - the subset of CheckConstraintsReferenced that does not say which kind of constraint it means. DROP CHECK and ALTER CHECK do say, so they are not returned.

MySQL resolves such a name against the table's CHECK, FOREIGN KEY, UNIQUE and PRIMARY KEY constraints, which are separate namespaces, and refuses the ALTER when more than one of them holds it: "Table has multiple constraints with the name 'x'. Please use constraint specific 'DROP' clause" (error 3939). A caller that resolves the name itself has to reproduce that rather than pick one.

func (*AbstractStatement) IsAlterTable

func (a *AbstractStatement) IsAlterTable() bool

func (*AbstractStatement) IsCreateTable

func (a *AbstractStatement) IsCreateTable() bool

func (*AbstractStatement) IsDropTable added in v0.11.1

func (a *AbstractStatement) IsDropTable() bool

func (*AbstractStatement) IsRenameTable added in v0.11.1

func (a *AbstractStatement) IsRenameTable() bool

func (*AbstractStatement) ParseCreateTable

func (a *AbstractStatement) ParseCreateTable() (*CreateTable, error)

func (*AbstractStatement) TrimAlter

func (a *AbstractStatement) TrimAlter() string

type Classification added in v0.11.1

type Classification struct {
	Type   StatementType
	Table  string // First table referenced (empty for unparseable statements)
	Schema string // Schema if fully qualified (e.g. "test" from "test.t1")
}

Classification holds the result of classifying a single SQL statement.

func Classify added in v0.11.1

func Classify(sql string) ([]Classification, error)

Classify parses one or more SQL statements and returns their classifications. Unlike New(), this accepts any statement type including DML and TRUNCATE.

type Column

type Column struct {
	Raw             *ast.ColumnDef    `json:"-"`
	Name            string            `json:"name"`
	Type            string            `json:"type"`
	Length          *int              `json:"length,omitempty"`
	Precision       *int              `json:"precision,omitempty"`
	Scale           *int              `json:"scale,omitempty"`
	Unsigned        *bool             `json:"unsigned,omitempty"`
	Zerofill        *bool             `json:"zerofill,omitempty"`    // ZEROFILL display attribute (implies unsigned)
	EnumValues      []string          `json:"enum_values,omitempty"` // Permitted values for ENUM type
	SetValues       []string          `json:"set_values,omitempty"`  // Permitted values for SET type
	Nullable        bool              `json:"nullable"`
	Default         *string           `json:"default,omitempty"`
	DefaultIsExpr   bool              `json:"default_is_expr,omitempty"`   // true when default is an expression (needs parens), e.g. DEFAULT (json_object())
	DefaultIsString bool              `json:"default_is_string,omitempty"` // true when the default is a quoted string literal (so it must be re-quoted on emission, even if it looks like a keyword/number)
	OnUpdate        *string           `json:"on_update,omitempty"`         // ON UPDATE expression for TIMESTAMP/DATETIME, e.g. "current_timestamp"
	GeneratedExpr   *string           `json:"generated_expr,omitempty"`    // Expression for GENERATED ALWAYS AS (...) columns
	GeneratedStored bool              `json:"generated_stored,omitempty"`  // true = STORED, false = VIRTUAL (only meaningful when GeneratedExpr is set)
	Check           *string           `json:"check,omitempty"`             // Column-level CHECK (...) constraint expression
	SRID            *uint32           `json:"srid,omitempty"`              // SRID attribute for spatial columns
	AutoInc         bool              `json:"auto_increment"`
	PrimaryKey      bool              `json:"primary_key"`
	Unique          bool              `json:"unique"`
	Comment         *string           `json:"comment,omitempty"`
	Charset         *string           `json:"charset,omitempty"`
	Collation       *string           `json:"collation,omitempty"`
	Options         map[string]string `json:"options,omitempty"`
}

Column represents a table column definition

func (*Column) CarriesCharset added in v0.17.0

func (c *Column) CarriesCharset() bool

CarriesCharset reports whether the column's type stores text, and therefore has a charset and collation that participate in comparisons. Numeric, date, binary, JSON and spatial types are excluded: they carry at most a synthetic "binary" charset that is identical for any two columns of the same type.

func (*Column) EffectiveCharsetCollation added in v0.17.0

func (c *Column) EffectiveCharsetCollation(table *CreateTable) (cs, collation string)

EffectiveCharsetCollation returns the charset and collation the column actually compares under, given the table that owns it. It resolves the column's own clauses against the table defaults exactly as MySQL does (see resolvedCharsetCollation), and then fills in the charset's *default* collation when no COLLATE was written anywhere. That last step matters because SHOW CREATE TABLE omits COLLATE whenever it is the charset default, so on MySQL 8.0 a table spelled `DEFAULT CHARSET=utf8mb4` really means utf8mb4_0900_ai_ci and must compare unequal to one that spells `COLLATE=utf8mb4_general_ci`. This is decidable without a server: a charset used without a collation takes that charset's default collation — collation_server does not enter into it.

Either return value is "" when the statement does not determine it: a table with no DEFAULT CHARSET at all (only reachable from hand-written DDL, since SHOW CREATE TABLE always emits one) inherits the schema/server default, and a charset this parser does not know has no default collation to look up. Callers must treat "" as "unknown" rather than as a value that can differ.

Names are returned in MySQL 8.0's spelling: the legacy utf8/utf8_* forms are folded onto utf8mb3/utf8mb3_*, so the two spellings of the same charset compare equal.

The diff does not use this: it deliberately treats an unwritten collation as a match (see charsetCollationEqual) so it never emits a MODIFY it cannot prove converged. A linter has the opposite bias — it reports a difference it can prove, and stays silent otherwise.

func (Column) GetName

func (c Column) GetName() string

type Columns

type Columns []Column

func (Columns) ByName

func (columns Columns) ByName(name string) *Column

type Constraint

type Constraint struct {
	Raw         *ast.Constraint      `json:"-"`
	Name        string               `json:"name"`
	Type        string               `json:"type"` // CHECK, FOREIGN KEY, etc.
	Columns     []string             `json:"columns,omitempty"`
	Expression  *string              `json:"expression,omitempty"`
	References  *ForeignKeyReference `json:"references,omitempty"`
	Definition  *string              `json:"definition,omitempty"`   // Generated definition string for compatibility
	NotEnforced bool                 `json:"not_enforced,omitempty"` // CHECK constraints only: true when NOT ENFORCED
	Options     map[string]any       `json:"options,omitempty"`
}

Constraint represents a table constraint

func (Constraint) GetName

func (c Constraint) GetName() string

type Constraints

type Constraints []Constraint

func (Constraints) ByName

func (constraints Constraints) ByName(name string) *Constraint

func (Constraints) HasForeignKeys

func (constraints Constraints) HasForeignKeys() bool

type CreateTable

type CreateTable struct {
	Raw          *ast.CreateTableStmt `json:"-"`
	TableName    string               `json:"table_name"`
	Temporary    bool                 `json:"temporary"`
	IfNotExists  bool                 `json:"if_not_exists"`
	Columns      Columns              `json:"columns"`
	Indexes      Indexes              `json:"indexes"`
	Constraints  Constraints          `json:"constraints"`
	TableOptions *TableOptions        `json:"table_options,omitempty"`
	Partition    *PartitionOptions    `json:"partition,omitempty"`
}

CreateTable represents a parsed CREATE TABLE statement with structured data

func ParseCreateTable

func ParseCreateTable(sql string) (*CreateTable, error)

ParseCreateTable parses a CREATE TABLE statement and returns an analyzer This function is particularly designed to be used with the output of SHOW CREATE TABLE, which we consider to be the "canonical" form of a CREATE TABLE statement.

Because there's so much variation in the ways a human might write a CREATE TABLE statement, from index names being auto-generated to column attributes being turned into table options, you should consider use of this function on non-canonical CREATE statements to be experimental at best.

Note also that this parser does not attempt to validate the SQL beyond what the underlying parser does. For example, it will not check that a PRIMARY KEY column is NOT NULL, or that column names are unique, or that indexed columns exist.

func (*CreateTable) Diff added in v0.11.0

func (ct *CreateTable) Diff(target *CreateTable, opts *DiffOptions) ([]*AbstractStatement, error)

Diff compares this CreateTable (source) with another CreateTable (target) and returns ALTER TABLE statements needed to transform source into target. Most changes produce a single statement, but some (e.g. changing partition type) require multiple sequential statements. Returns nil if the tables are identical. If opts is nil, NewDiffOptions() defaults are used.

func (*CreateTable) GetColumns

func (ct *CreateTable) GetColumns() Columns

func (*CreateTable) GetConstraints

func (ct *CreateTable) GetConstraints() Constraints

func (*CreateTable) GetCreateTable

func (ct *CreateTable) GetCreateTable() *CreateTable

func (*CreateTable) GetIndexes

func (ct *CreateTable) GetIndexes() Indexes

func (*CreateTable) GetPartition

func (ct *CreateTable) GetPartition() *PartitionOptions

func (*CreateTable) GetTableName

func (ct *CreateTable) GetTableName() string

func (*CreateTable) GetTableOptions

func (ct *CreateTable) GetTableOptions() map[string]any

func (*CreateTable) ToTableInfo added in v0.17.0

func (ct *CreateTable) ToTableInfo(schemaName string) (*table.TableInfo, error)

ToTableInfo builds a connection-less table.TableInfo from the parsed CREATE TABLE, carrying the column types and primary key columns that Spirit's checks read from table metadata. schemaName names the schema the table lives in; it is only used for error messages and by checks that query MySQL, which cannot run against the returned TableInfo anyway (see table.NewTableInfoFromMeta).

This lets a caller holding a table's DDL — typically its SHOW CREATE TABLE — supply check.Resources.Table without opening a connection.

func (*CreateTable) ToTableSchema added in v0.11.3

func (ct *CreateTable) ToTableSchema() (table.TableSchema, error)

ToTableSchema converts a parsed CreateTable back to a table.TableSchema by restoring the AST to SQL. This is useful when callers have already parsed schemas (e.g. for linting) but need to pass them to DeclarativeToImperative.

type DiffOptions added in v0.11.0

type DiffOptions struct {
	// IgnoreAutoIncrement skips diffing the AUTO_INCREMENT table option
	// (the table-level next-value counter, e.g. `AUTO_INCREMENT=100`).
	// Default: true (via NewDiffOptions).
	IgnoreAutoIncrement bool

	// IgnoreColumnAutoIncrement skips diffing the column-level AUTO_INCREMENT
	// attribute (whether a column carries the AUTO_INCREMENT flag). This is
	// distinct from IgnoreAutoIncrement, which only covers the table-option
	// counter. Default: false (via NewDiffOptions) — for general schema diffing
	// a column gaining or losing AUTO_INCREMENT is a real change. It is enabled
	// by consumers like the move-tables target-state check, where an unsharded
	// source legitimately differs from a sharded target that drops
	// AUTO_INCREMENT in favor of a Vitess sequence: the difference does not
	// affect copy correctness and must not block the move.
	IgnoreColumnAutoIncrement bool

	// IgnoreNotNullRelaxation lets the schema being validated be STRICTER than
	// its reference on nullability, and only stricter: a validated column
	// declared NOT NULL where the reference permits NULL is accepted, while one
	// that permits NULL where the reference is NOT NULL remains a real
	// difference. The option therefore can never quietly accept a schema that
	// lost a NOT NULL the reference had.
	//
	// Default: false (via NewDiffOptions) — for general schema diffing a column
	// gaining or losing NOT NULL is a real change.
	//
	// It is enabled by the move-tables target checks (see
	// move/check.TargetSchemaDiff), where the reference is the move's SOURCE and
	// the validated schema is its physical TARGET. What that permits is a target
	// column declared NOT NULL where the source still permits NULL — an
	// unsharded source moving into a sharded target whose shard key must be
	// NOT NULL, because a Vitess primary vindex cannot map NULL to a keyspace
	// id.
	//
	// In terms of Diff's own arguments the reference is the parameter and the
	// validated schema is the receiver, because DiffCreateTables diffs
	// got->want. Stating the direction that way inverts it, which is why the
	// wording above and TestDiff_IgnoreNotNullRelaxation both name the two
	// schemas by role instead.
	//
	// That is safe for a move because nullability is metadata, not row bytes:
	// the copy and the checksum compare values, and every column's NULL-ness is
	// compared explicitly (see ColumnMapping.ChecksumExprs, which emits an
	// ISNULL() digit per column). A tightened column whose source data holds no
	// NULLs is therefore identical on both sides, and this option hides nothing
	// about the rows themselves. One that does hold a NULL fails the move
	// instead of being accepted, and fails before the checksum ever runs — see
	// move/check.TargetSchemaDiff for where and why.
	IgnoreNotNullRelaxation bool

	// IgnoreEngine skips diffing the ENGINE table option.
	// Default: true (via NewDiffOptions).
	IgnoreEngine bool

	// IgnoreCharsetCollation skips diffing CHARSET and COLLATION table options.
	// Default: false (via NewDiffOptions).
	IgnoreCharsetCollation bool

	// IgnorePartitioning skips diffing partition options entirely.
	// Default: false (via NewDiffOptions).
	IgnorePartitioning bool

	// IgnoreRowFormat skips diffing the ROW_FORMAT table option.
	// Default: true (via NewDiffOptions).
	// ROW_FORMAT=DYNAMIC is the InnoDB default in MySQL 8.0+, so differences
	// between an unspecified ROW_FORMAT and an explicit DYNAMIC are cosmetic.
	IgnoreRowFormat bool
}

DiffOptions controls the behavior of the Diff operation.

func NewDiffOptions added in v0.11.0

func NewDiffOptions() *DiffOptions

NewDiffOptions returns DiffOptions with sensible defaults. By default, AUTO_INCREMENT, ENGINE, and ROW_FORMAT differences are ignored.

type ForeignKeyReference

type ForeignKeyReference struct {
	Table    string   `json:"table"`
	Columns  []string `json:"columns"`
	OnDelete *string  `json:"on_delete,omitempty"`
	OnUpdate *string  `json:"on_update,omitempty"`
}

ForeignKeyReference represents a foreign key reference

type HasName

type HasName interface {
	GetName() string
}

HasName is a type constraint for types that have a Name field

type Index

type Index struct {
	Raw          *ast.Constraint   `json:"-"`
	Name         string            `json:"name"`
	Type         string            `json:"type"`                  // PRIMARY, UNIQUE, INDEX, FULLTEXT, SPATIAL
	Columns      []string          `json:"columns"`               // Deprecated: use ColumnList for full details
	ColumnList   []IndexColumn     `json:"column_list,omitempty"` // Full column specifications including prefix/expression
	Invisible    *bool             `json:"invisible,omitempty"`
	Using        *string           `json:"using,omitempty"` // BTREE, HASH, RTREE
	Comment      *string           `json:"comment,omitempty"`
	KeyBlockSize *uint64           `json:"key_block_size,omitempty"`
	ParserName   *string           `json:"parser_name,omitempty"`
	Options      map[string]string `json:"options,omitempty"`

	// InlineDerived marks a UNIQUE index that indexNormalizer synthesized
	// from an inline column-level UNIQUE (`c INT UNIQUE`). Its name is only a
	// guess at the server-assigned one (the column name, suffixed on collision),
	// so diffIndexes pairs it with an equivalent live unique index by column set
	// even when the names differ, rather than emitting a spurious DROP+ADD.
	// Not serialized: it is a diff-time hint, not part of the logical schema.
	InlineDerived bool `json:"-"`
}

Index represents an index definition

func (Index) GetName

func (i Index) GetName() string

type IndexColumn added in v0.11.0

type IndexColumn struct {
	Name       string  `json:"name,omitempty"`       // Column name (empty for expression indexes)
	Expression *string `json:"expression,omitempty"` // Expression for functional indexes
	Length     *int    `json:"length,omitempty"`     // Prefix length for string columns
	Desc       bool    `json:"desc,omitempty"`       // Descending key part (MySQL 8.0+), e.g. KEY (a DESC)
}

IndexColumn represents a column or expression in an index

type Indexes

type Indexes []Index

func (Indexes) ByName

func (indexes Indexes) ByName(name string) *Index

func (Indexes) HasInvisible

func (indexes Indexes) HasInvisible() bool

type Normalizer added in v0.16.0

type Normalizer interface {
	// Name identifies the rule, for registry determinism and debugging.
	Name() string
	// Normalize returns ct rewritten to MySQL's canonical form for this rule.
	Normalize(ct *CreateTable) *CreateTable
}

Normalizer applies a single MySQL canonicalization to a parsed CreateTable. A rule takes a CreateTable and returns the normalized CreateTable — it is free to mutate and return the same instance, or to return a new one. It is deliberately a standalone type rather than a method on CreateTable so rules live in their own files and compose as a pipeline.

type Options added in v0.12.0

type Options struct {
	// AllowMixedStatementTypes permits multi-statement input containing different
	// DDL types (e.g., CREATE TABLE + ALTER TABLE). By default, multi-statement
	// input must be all ALTER TABLE statements (required for atomic schema changes).
	// Enable this when using New() to split/parse schema files that may contain
	// a mix of DDL statement types.
	AllowMixedStatementTypes bool
}

Options configures the behavior of statement parsing.

type PartitionDefinition

type PartitionDefinition struct {
	Name          string                   `json:"name"`
	Values        *PartitionValues         `json:"values,omitempty"` // VALUES LESS THAN or VALUES IN
	Comment       *string                  `json:"comment,omitempty"`
	Engine        *string                  `json:"engine,omitempty"`
	Options       map[string]any           `json:"options,omitempty"`
	SubPartitions []SubPartitionDefinition `json:"subpartitions,omitempty"`
}

PartitionDefinition represents a single partition definition

type PartitionOptions

type PartitionOptions struct {
	Type         string                `json:"type"`                   // RANGE, LIST, HASH, KEY
	Expression   *string               `json:"expression,omitempty"`   // For HASH and RANGE
	Columns      []string              `json:"columns,omitempty"`      // For KEY, RANGE COLUMNS, LIST COLUMNS
	Linear       bool                  `json:"linear,omitempty"`       // For LINEAR HASH/KEY
	Partitions   uint64                `json:"partitions,omitempty"`   // Number of partitions
	Definitions  []PartitionDefinition `json:"definitions,omitempty"`  // Individual partition definitions
	SubPartition *SubPartitionOptions  `json:"subpartition,omitempty"` // Subpartitioning options
}

PartitionOptions represents table partitioning configuration

type PartitionValues

type PartitionValues struct {
	Type   string `json:"type"`   // "LESS_THAN", "IN", "MAXVALUE"
	Values []any  `json:"values"` // The actual values
}

PartitionValues represents the VALUES clause in partition definitions

type StatementType added in v0.11.1

type StatementType int

StatementType represents the type of a SQL statement.

const (
	StatementUnknown       StatementType = iota
	StatementAlterTable                  // ALTER TABLE ...
	StatementCreateTable                 // CREATE TABLE ...
	StatementDropTable                   // DROP TABLE ...
	StatementRenameTable                 // RENAME TABLE ...
	StatementTruncateTable               // TRUNCATE TABLE ...
	StatementCreateIndex                 // CREATE INDEX ...
	StatementDropIndex                   // DROP INDEX ...
	StatementCreateView                  // CREATE VIEW ...
	StatementInsert                      // INSERT ...
	StatementUpdate                      // UPDATE ...
	StatementDelete                      // DELETE ...
)

func (StatementType) IsDDL added in v0.11.1

func (t StatementType) IsDDL() bool

IsDDL returns true if this is a DDL statement type.

func (StatementType) IsDML added in v0.11.1

func (t StatementType) IsDML() bool

IsDML returns true if this is a DML statement type.

func (StatementType) String added in v0.11.1

func (t StatementType) String() string

String returns the human-readable name for a StatementType.

type SubPartitionDefinition

type SubPartitionDefinition struct {
	Name    string         `json:"name"`
	Comment *string        `json:"comment,omitempty"`
	Engine  *string        `json:"engine,omitempty"`
	Options map[string]any `json:"options,omitempty"`
}

SubPartitionDefinition represents a single subpartition definition

type SubPartitionOptions

type SubPartitionOptions struct {
	Type       string   `json:"type"`                 // HASH, KEY
	Expression *string  `json:"expression,omitempty"` // For HASH
	Columns    []string `json:"columns,omitempty"`    // For KEY
	Linear     bool     `json:"linear,omitempty"`     // For LINEAR HASH/KEY
	Count      uint64   `json:"count,omitempty"`      // Number of subpartitions
}

SubPartitionOptions represents subpartitioning configuration

type TableOptions

type TableOptions struct {
	Engine        *string `json:"engine,omitempty"`
	Charset       *string `json:"charset,omitempty"`
	Collation     *string `json:"collation,omitempty"`
	Comment       *string `json:"comment,omitempty"`
	AutoIncrement *uint64 `json:"auto_increment,omitempty"`
	RowFormat     *string `json:"row_format,omitempty"`
}

TableOptions represents table-level options

Jump to

Keyboard shortcuts

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