Documentation
¶
Overview ¶
Package lint provides a framework for static analysis of MySQL schema definitions and DDL statements. It enables validation and best-practice enforcement beyond the runtime checks provided by the check package.
The linter framework operates on parsed CREATE TABLE statements rather than live database connections.
Basic Usage ¶
Linters are registered via init() functions and executed via RunLinters():
package naming
func init() {
lint.Register(TableNameLinter{})
}
// Later, run all linters:
violations := lint.RunLinters(tables, stmts, config)
Creating a Linter ¶
To create a custom linter, implement the Linter interface:
type MyLinter struct{}
func (l *MyLinter) Name() string { return "my_linter" }
func (l *MyLinter) Category() string { return "custom" }
func (l *MyLinter) Description() string { return "My custom linter" }
func (l *MyLinter) Lint(createTables []*statement.CreateTable, statements []*statement.AbstractStatement) []lint.Violation {
// Perform linting logic
return violations
}
Configuration ¶
Linters can be enabled/disabled via the Config.Enabled map:
config := lint.Config{
Enabled: map[string]bool{
"table_name": true,
"column_name": false,
},
}
Configurable linters can implement the ConfigurableLinter interface to accept custom settings via Config.Settings. Settings must be provided as map[string]string:
config := lint.Config{
Settings: map[string]map[string]string{
"my_linter": {
"option1": "value1",
"option2": "value2",
},
},
}
Index ¶
- func AlterTableTypeToString(tp ast.AlterTableType) string
- func ConfigBool(value string, key string) (bool, error)
- func CreateTableStatements(statements ...any) iter.Seq[*statement.CreateTable]
- func Disable(names ...string) error
- func Enable(names ...string) error
- func HasErrors(violations []Violation) bool
- func HasWarnings(violations []Violation) bool
- func List() []string
- func LoadSchemaFromDSN(ctx context.Context, dsn string) ([]*statement.CreateTable, error)
- func LoadSchemaFromDir(dir string) ([]*statement.CreateTable, error)
- func PostState(existing []*statement.CreateTable, changes []*statement.AbstractStatement) []*statement.CreateTable
- func PreStateColumns(existing []*statement.CreateTable) map[string]map[string]*statement.Column
- func Register(l Linter)
- func Reset()
- func Stringer(l Linter) string
- type AllowCharset
- func (l *AllowCharset) Configure(config map[string]string) error
- func (l *AllowCharset) DefaultConfig() map[string]string
- func (l *AllowCharset) Description() string
- func (l *AllowCharset) Lint(createTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
- func (l *AllowCharset) Name() string
- func (l *AllowCharset) String() string
- type AllowEngine
- func (l *AllowEngine) Configure(config map[string]string) error
- func (l *AllowEngine) DefaultConfig() map[string]string
- func (l *AllowEngine) Description() string
- func (l *AllowEngine) Lint(existingTables []*statement.CreateTable, ...) (violations []Violation)
- func (l *AllowEngine) Name() string
- func (l *AllowEngine) String() string
- type AutoIncCapacityLinter
- func (l *AutoIncCapacityLinter) Configure(config map[string]string) error
- func (l *AutoIncCapacityLinter) DefaultConfig() map[string]string
- func (l *AutoIncCapacityLinter) Description() string
- func (l *AutoIncCapacityLinter) Lint(existingTables []*statement.CreateTable, ...) (violations []Violation)
- func (l *AutoIncCapacityLinter) Name() string
- func (l *AutoIncCapacityLinter) String() string
- type Config
- type ConfigurableLinter
- type DatetimeIndexPositionLinter
- type DiffCmd
- type HasFKLinter
- type HasFloatLinter
- type HasTimestampLinter
- type InvisibleIndexBeforeDropLinter
- func (l *InvisibleIndexBeforeDropLinter) Configure(config map[string]string) error
- func (l *InvisibleIndexBeforeDropLinter) DefaultConfig() map[string]string
- func (l *InvisibleIndexBeforeDropLinter) Description() string
- func (l *InvisibleIndexBeforeDropLinter) Lint(existingTables []*statement.CreateTable, ...) []Violation
- func (l *InvisibleIndexBeforeDropLinter) Name() string
- func (l *InvisibleIndexBeforeDropLinter) String() string
- type LintCmd
- type Linter
- type Location
- type MultipleAlterTableLinter
- type NameCaseLinter
- type Plan
- type PlannedChange
- type PrimaryKeyLinter
- func (l *PrimaryKeyLinter) Configure(config map[string]string) error
- func (l *PrimaryKeyLinter) DefaultConfig() map[string]string
- func (l *PrimaryKeyLinter) Description() string
- func (l *PrimaryKeyLinter) Lint(existingTables []*statement.CreateTable, ...) (violations []Violation)
- func (l *PrimaryKeyLinter) Name() string
- func (l *PrimaryKeyLinter) String() string
- type RedundantIndexLinter
- type RenameColumnLinter
- type ReservedWordsLinter
- type Severity
- type TypePedanticLinter
- func (l *TypePedanticLinter) Configure(config map[string]string) error
- func (l *TypePedanticLinter) DefaultConfig() map[string]string
- func (l *TypePedanticLinter) Description() string
- func (l *TypePedanticLinter) Lint(existingTables []*statement.CreateTable, ...) (violations []Violation)
- func (l *TypePedanticLinter) Name() string
- func (l *TypePedanticLinter) String() string
- type UnsafeLinter
- func (l *UnsafeLinter) Configure(config map[string]string) error
- func (l *UnsafeLinter) DefaultConfig() map[string]string
- func (l *UnsafeLinter) Description() string
- func (l *UnsafeLinter) Lint(_ []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
- func (l *UnsafeLinter) Name() string
- func (l *UnsafeLinter) String() string
- type Violation
- type ZeroDateLinter
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AlterTableTypeToString ¶
func AlterTableTypeToString(tp ast.AlterTableType) string
AlterTableTypeToString converts an AlterTableType constant to a human-readable string
func ConfigBool ¶
ConfigBool parses a boolean configuration value from a string. It accepts "true" or "false" (case-insensitive) and returns an error for invalid values. The key parameter is used in error messages to provide context.
func CreateTableStatements ¶
func CreateTableStatements(statements ...any) iter.Seq[*statement.CreateTable]
CreateTableStatements returns an iterator over all CREATE TABLE statements, combining those from the existing schema state and those included in incoming changes.
func Disable ¶
Disable disables specific linters by name. Returns an error if the linter is not found.
func HasWarnings ¶
HasWarnings returns true if any violations have WARNING severity.
func LoadSchemaFromDSN ¶ added in v0.11.0
LoadSchemaFromDSN connects to a MySQL server and retrieves all CREATE TABLE statements from the connected database, parsed into structured CreateTable objects.
func LoadSchemaFromDir ¶ added in v0.11.0
func LoadSchemaFromDir(dir string) ([]*statement.CreateTable, error)
LoadSchemaFromDir reads all .sql files from a directory and parses them as CREATE TABLE statements. Each file should contain exactly one CREATE TABLE statement.
func PostState ¶ added in v0.14.0
func PostState(existing []*statement.CreateTable, changes []*statement.AbstractStatement) []*statement.CreateTable
PostState returns a deterministic post-state view of the schema: the existing tables augmented with CREATE TABLE statements from changes, and existing tables patched with ADD/DROP/MODIFY/CHANGE COLUMN and ADD/DROP INDEX (and DROP PRIMARY KEY) specs from ALTER TABLE statements. Other ALTER specs are ignored.
The result is sorted by table name. The input tables are not mutated.
Linters that need to evaluate the schema as it will exist *after* a set of pending changes (rather than as it exists today) should iterate this slice rather than existingTables directly — otherwise they will produce false positives for ALTER statements that fix legacy issues.
func PreStateColumns ¶ added in v0.14.0
PreStateColumns returns a lookup of (lowercased table name) → (lowercased column name) → column from the existing tables. Linters use this to distinguish columns that pre-existed (severity Warning) from columns added or modified by changes (severity Error).
func Register ¶
func Register(l Linter)
Register registers a linter with the global registry. This should be called from init() functions in linter implementations. Linters are enabled by default when registered.
Types ¶
type AllowCharset ¶
type AllowCharset struct {
// contains filtered or unexported fields
}
func (*AllowCharset) DefaultConfig ¶
func (l *AllowCharset) DefaultConfig() map[string]string
func (*AllowCharset) Description ¶
func (l *AllowCharset) Description() string
func (*AllowCharset) Lint ¶
func (l *AllowCharset) Lint(createTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint walks the post-state of the schema. The message form preserves the pre/post-state distinction that the old ALTER pass made explicit:
- "New column …" when the column did not exist in the pre-state.
- "Column … modified to use …" when MODIFY/CHANGE COLUMN retypes an existing column.
- "Column … has unsupported character set" for pre-existing untouched columns and columns inside a new CREATE TABLE.
func (*AllowCharset) Name ¶
func (l *AllowCharset) Name() string
func (*AllowCharset) String ¶
func (l *AllowCharset) String() string
type AllowEngine ¶
type AllowEngine struct {
// contains filtered or unexported fields
}
func (*AllowEngine) DefaultConfig ¶
func (l *AllowEngine) DefaultConfig() map[string]string
func (*AllowEngine) Description ¶
func (l *AllowEngine) Description() string
func (*AllowEngine) Lint ¶
func (l *AllowEngine) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint walks the post-state of the schema so an ALTER TABLE ENGINE=... that migrates *to* an allowed engine no longer false-positives against the legacy engine, and one that changes the engine to a disallowed value is reported against the table's final shape.
func (*AllowEngine) Name ¶
func (l *AllowEngine) Name() string
func (*AllowEngine) String ¶
func (l *AllowEngine) String() string
type AutoIncCapacityLinter ¶
type AutoIncCapacityLinter struct {
// contains filtered or unexported fields
}
func (*AutoIncCapacityLinter) Configure ¶
func (l *AutoIncCapacityLinter) Configure(config map[string]string) error
func (*AutoIncCapacityLinter) DefaultConfig ¶
func (l *AutoIncCapacityLinter) DefaultConfig() map[string]string
func (*AutoIncCapacityLinter) Description ¶
func (l *AutoIncCapacityLinter) Description() string
func (*AutoIncCapacityLinter) Lint ¶
func (l *AutoIncCapacityLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint walks the post-state of the schema so an ALTER that raises AUTO_INCREMENT or widens the column type is linted against the table's final shape rather than the pre-ALTER snapshot.
func (*AutoIncCapacityLinter) Name ¶
func (l *AutoIncCapacityLinter) Name() string
func (*AutoIncCapacityLinter) String ¶
func (l *AutoIncCapacityLinter) String() string
type Config ¶
type Config struct {
// Enabled maps linter names to whether they are enabled
// If a linter is not in this map, it uses its default enabled state
Enabled map[string]bool
// Settings maps linter names to their configuration as map[string]string
// Each linter's settings are provided as key-value string pairs
Settings map[string]map[string]string
// LintOnlyChanges indicates whether to lint only the changes
// or all of the existing schema plus the changes.
LintOnlyChanges bool
// IgnoreTables can be used to discard violations for specific tables
IgnoreTables map[string]bool
}
Config holds linter configuration
type ConfigurableLinter ¶
type ConfigurableLinter interface {
Linter
// Configure applies configuration to the linter
// Configuration is provided as a map of string keys to string values
Configure(config map[string]string) error
// DefaultConfig returns the default configuration for this linter
DefaultConfig() map[string]string
}
ConfigurableLinter is an optional interface for linters that support configuration
type DatetimeIndexPositionLinter ¶ added in v0.14.0
type DatetimeIndexPositionLinter struct{}
func (*DatetimeIndexPositionLinter) Description ¶ added in v0.14.0
func (l *DatetimeIndexPositionLinter) Description() string
func (*DatetimeIndexPositionLinter) Lint ¶ added in v0.14.0
func (l *DatetimeIndexPositionLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint operates on a post-state view of the schema. For each composite index (>=2 columns) on each table, it flags any DATETIME, TIMESTAMP, or DATE column that appears in a non-last position.
Rationale: date/time columns are overwhelmingly queried with range predicates (>, >=, <, <=, BETWEEN). Once the MySQL optimizer hits a range predicate on a column inside a composite index, the columns that follow can no longer be used for sorted index access — they're only available for index condition pushdown filtering. So a composite index that places a date/time column anywhere but last is usually carrying dead weight in its trailing columns.
This is a heuristic with no visibility into the actual query workload, so it always emits SeverityWarning. FULLTEXT and SPATIAL indexes are skipped — they don't behave like B-tree indexes for range scans.
func (*DatetimeIndexPositionLinter) Name ¶ added in v0.14.0
func (l *DatetimeIndexPositionLinter) Name() string
func (*DatetimeIndexPositionLinter) String ¶ added in v0.14.0
func (l *DatetimeIndexPositionLinter) String() string
type DiffCmd ¶ added in v0.11.0
type DiffCmd struct {
// Source of existing schema (exactly one required)
SourceDSN string `help:"MySQL DSN for existing schema" xor:"source" required:"" env:"MYSQL_DSN"`
SourceDir string `help:"Directory of CREATE TABLE .sql files for existing schema" xor:"source" required:"" type:"existingdir"`
// Target / proposed changes (exactly one required)
TargetDSN string `help:"MySQL DSN for target schema (will diff against source)" xor:"target" required:""`
TargetDir string `help:"Directory of CREATE TABLE .sql files for target state" xor:"target" required:"" type:"existingdir"`
TargetAlter []string `help:"ALTER TABLE statement(s) to apply" short:"a" xor:"target" required:""`
// Filtering
IgnoreTables string `help:"Regex pattern of table names to ignore" default:""`
}
DiffCmd is the Kong CLI struct for the diff command. It diffs two schemas, shows the generated DDL, and lints the changes.
type HasFKLinter ¶
type HasFKLinter struct{}
func (*HasFKLinter) Description ¶
func (l *HasFKLinter) Description() string
func (*HasFKLinter) Lint ¶
func (l *HasFKLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint walks the post-state of the schema, so an ALTER DROP FOREIGN KEY that fixes a legacy FK does not produce a false positive, and an ALTER ADD FOREIGN KEY surfaces the violation against the table's final shape.
func (*HasFKLinter) Name ¶
func (l *HasFKLinter) Name() string
func (*HasFKLinter) String ¶
func (l *HasFKLinter) String() string
type HasFloatLinter ¶
type HasFloatLinter struct{}
func (*HasFloatLinter) Description ¶
func (l *HasFloatLinter) Description() string
func (*HasFloatLinter) Lint ¶
func (l *HasFloatLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint operates on a post-state view of the schema, so a column being converted away from FLOAT/DOUBLE in an ALTER doesn't generate a false positive. The violation message uses three forms to preserve actionability:
- "New column …" when the column did not exist in the pre-state (ADD COLUMN, or a column in a CREATE TABLE).
- "Column … modified to use …" when the column existed pre-state and is being retyped by MODIFY / CHANGE COLUMN.
- "Column … uses …" for pre-existing untouched columns.
func (*HasFloatLinter) Name ¶
func (l *HasFloatLinter) Name() string
func (*HasFloatLinter) String ¶
func (l *HasFloatLinter) String() string
type HasTimestampLinter ¶ added in v0.13.0
type HasTimestampLinter struct{}
func (*HasTimestampLinter) Description ¶ added in v0.13.0
func (l *HasTimestampLinter) Description() string
func (*HasTimestampLinter) Lint ¶ added in v0.13.0
func (l *HasTimestampLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint operates on a post-state view of the schema. For each TIMESTAMP column in the post-state, the severity is:
- Warning, if the column existed in the pre-state with TIMESTAMP type and no incoming change touches it (legacy schema — don't boil the ocean).
- Error, if the column is newly added in this changeset, or modified to TIMESTAMP, or appears in a CREATE TABLE in the changeset.
Columns that were TIMESTAMP in the pre-state but are being dropped or converted to a different type in the changeset don't appear in the post-state and therefore produce no violation — which is exactly the desired behavior for migrations that fix legacy TIMESTAMP columns.
func (*HasTimestampLinter) Name ¶ added in v0.13.0
func (l *HasTimestampLinter) Name() string
func (*HasTimestampLinter) String ¶ added in v0.13.0
func (l *HasTimestampLinter) String() string
type InvisibleIndexBeforeDropLinter ¶
type InvisibleIndexBeforeDropLinter struct {
// contains filtered or unexported fields
}
InvisibleIndexBeforeDropLinter checks that indexes are made invisible before dropping. This is a safety practice to ensure the index is not needed before permanently removing it.
func (*InvisibleIndexBeforeDropLinter) Configure ¶
func (l *InvisibleIndexBeforeDropLinter) Configure(config map[string]string) error
func (*InvisibleIndexBeforeDropLinter) DefaultConfig ¶
func (l *InvisibleIndexBeforeDropLinter) DefaultConfig() map[string]string
func (*InvisibleIndexBeforeDropLinter) Description ¶
func (l *InvisibleIndexBeforeDropLinter) Description() string
func (*InvisibleIndexBeforeDropLinter) Lint ¶
func (l *InvisibleIndexBeforeDropLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) []Violation
func (*InvisibleIndexBeforeDropLinter) Name ¶
func (l *InvisibleIndexBeforeDropLinter) Name() string
func (*InvisibleIndexBeforeDropLinter) String ¶
func (l *InvisibleIndexBeforeDropLinter) String() string
type LintCmd ¶ added in v0.11.0
type LintCmd struct {
// Source of existing schema (exactly one required)
SourceDSN string `help:"MySQL DSN for existing schema" xor:"source" required:"" env:"MYSQL_DSN"`
SourceDir string `help:"Directory of CREATE TABLE .sql files for existing schema" xor:"source" required:"" type:"existingdir"`
// Filtering
IgnoreTables string `help:"Regex pattern of table names to ignore" default:""`
}
LintCmd is the Kong CLI struct for the lint command. It lints an entire schema without requiring a target.
type Linter ¶
type Linter interface {
// Name returns the unique name of this linter
Name() string
// Description returns a human-readable description of what this linter checks
Description() string
// Lint performs the actual linting and returns any violations found.
// Linters can use either or both of the parameters as needed.
Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
// String returns a string representation of the linter
String() string
}
Linter is the interface that all linters must implement
type Location ¶
type Location struct {
// Table is the name of the table where the violation occurred
Table string
// Column is the name of the column (if applicable)
Column *string
// Index is the name of the index (if applicable)
Index *string
// Constraint is the name of the constraint (if applicable)
Constraint *string
}
Location provides information about where a violation occurred
type MultipleAlterTableLinter ¶
type MultipleAlterTableLinter struct{}
MultipleAlterTableLinter checks for multiple ALTER TABLE statements affecting the same table. Multiple ALTER TABLE statements on the same table should be combined into a single statement for better performance, fewer table rebuilds, and decreased danger of bad intermediate state.
func (*MultipleAlterTableLinter) Description ¶
func (l *MultipleAlterTableLinter) Description() string
func (*MultipleAlterTableLinter) Lint ¶
func (l *MultipleAlterTableLinter) Lint(_ []*statement.CreateTable, changes []*statement.AbstractStatement) []Violation
func (*MultipleAlterTableLinter) Name ¶
func (l *MultipleAlterTableLinter) Name() string
func (*MultipleAlterTableLinter) String ¶
func (l *MultipleAlterTableLinter) String() string
type NameCaseLinter ¶
type NameCaseLinter struct{}
func (*NameCaseLinter) Description ¶
func (l *NameCaseLinter) Description() string
func (*NameCaseLinter) Lint ¶
func (l *NameCaseLinter) Lint(createTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint walks the post-state of the schema so an ALTER RENAME that fixes a non-lowercase name does not produce a false positive, and a rename that introduces a non-lowercase name surfaces against the final table name.
func (*NameCaseLinter) Name ¶
func (*NameCaseLinter) Name() string
func (*NameCaseLinter) String ¶
func (l *NameCaseLinter) String() string
type Plan ¶ added in v0.11.3
type Plan struct {
// Changes is the ordered list of DDL changes with per-statement lint results.
Changes []PlannedChange
}
Plan holds the complete result of a declarative-to-imperative diff with linting.
func PlanChanges ¶ added in v0.11.3
func PlanChanges(current, desired []table.TableSchema, diffOpts *statement.DiffOptions, lintConfig *Config) (*Plan, error)
PlanChanges computes the imperative DDL statements needed to transform the current schema into the desired schema, and lints each statement against the current schema. This combines statement.DeclarativeToImperative with RunLinters into a single call, returning per-statement lint results.
LintOnlyChanges is always set to true regardless of the provided lintConfig, since PlanChanges only produces lint results for tables that have changes.
Parameters:
- current: the current table schemas (CREATE TABLE DDL)
- desired: the desired table schemas (CREATE TABLE DDL)
- diffOpts: options for the diff (nil uses defaults)
- lintConfig: configuration for linting (nil uses defaults; LintOnlyChanges is always overridden to true)
func (*Plan) HasChanges ¶ added in v0.11.3
HasChanges returns true if the plan contains any DDL statements.
func (*Plan) HasWarnings ¶ added in v0.11.3
HasWarnings returns true if any change has lint warnings.
func (*Plan) Statements ¶ added in v0.11.3
Statements returns just the DDL strings from all changes.
type PlannedChange ¶ added in v0.11.3
type PlannedChange struct {
// Statement is the semicolon-terminated DDL (ALTER, CREATE, or DROP TABLE).
Statement string
// TableName is the table affected by this change.
TableName string
// Violations contains lint violations for this table.
// Linters operate at the table level. When a diff produces multiple
// statements for the same table, violations are attached only to the
// last statement to avoid duplication.
Violations []Violation
}
PlannedChange represents a single DDL statement produced by declarative schema diffing, along with any lint violations associated with it.
func (*PlannedChange) Errors ¶ added in v0.11.3
func (c *PlannedChange) Errors() []Violation
Errors returns lint violations with ERROR severity.
func (*PlannedChange) Infos ¶ added in v0.11.3
func (c *PlannedChange) Infos() []Violation
Infos returns lint violations with INFO severity.
func (*PlannedChange) Warnings ¶ added in v0.11.3
func (c *PlannedChange) Warnings() []Violation
Warnings returns lint violations with WARNING severity.
type PrimaryKeyLinter ¶
type PrimaryKeyLinter struct {
// contains filtered or unexported fields
}
PrimaryKeyLinter checks that primary keys are defined and use appropriate data types. Primary keys should be BIGINT (preferably UNSIGNED) or BINARY/VARBINARY, but the linter can be configured to allow other types. Severity is scoped by source: existing tables produce SeverityWarning (don't block ALTERs on legacy schemas), while CREATE TABLE statements in the incoming changes produce SeverityError (enforce standards on new tables).
func (*PrimaryKeyLinter) Configure ¶
func (l *PrimaryKeyLinter) Configure(config map[string]string) error
func (*PrimaryKeyLinter) DefaultConfig ¶
func (l *PrimaryKeyLinter) DefaultConfig() map[string]string
func (*PrimaryKeyLinter) Description ¶
func (l *PrimaryKeyLinter) Description() string
func (*PrimaryKeyLinter) Lint ¶
func (l *PrimaryKeyLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
func (*PrimaryKeyLinter) Name ¶
func (l *PrimaryKeyLinter) Name() string
func (*PrimaryKeyLinter) String ¶
func (l *PrimaryKeyLinter) String() string
type RedundantIndexLinter ¶
type RedundantIndexLinter struct{}
RedundantIndexLinter checks for redundant indexes in tables. An index is considered redundant if:
- Its columns are a prefix of the PRIMARY KEY columns
- Another index's column list starts with this index's columns (prefix match)
- Another index has exactly the same columns in the same order (duplicate)
- The index ends with PRIMARY KEY columns (suffix redundancy) — InnoDB auto-appends PK columns to secondary indexes, so spelling them out is wasteful.
- The index starts with the full PRIMARY KEY (prefix redundancy) — point and equality lookups by PK are already served by the clustered index, so the leading PK columns add no capability that a non-PK-leading variant of the index wouldn't already provide.
The linter evaluates the *post-state* of the schema (existing tables with CREATE TABLE / ALTER TABLE changes applied) so warnings fire when a redundant index is being introduced, and stay silent when it is being removed — matching the post-state convention established for other linters in #840.
func (*RedundantIndexLinter) Description ¶
func (l *RedundantIndexLinter) Description() string
func (*RedundantIndexLinter) Lint ¶
func (l *RedundantIndexLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) []Violation
func (*RedundantIndexLinter) Name ¶
func (l *RedundantIndexLinter) Name() string
func (*RedundantIndexLinter) String ¶
func (l *RedundantIndexLinter) String() string
type RenameColumnLinter ¶ added in v0.11.0
type RenameColumnLinter struct{}
RenameColumnLinter detects column renames in ALTER TABLE statements. This is specifically for imperative mode (user-supplied ALTER statements). There are many reasons why renames are problematic:
- Spirit does not support them (unless they are INSTANT)
- For applications with many pods, atomically changing a column name is basically impossible. It only works if the column is not used, or all column references are via SELECT * with ordinal reference.
- Some ORMs like jOOQ generate column names at compile time. If there is a rename, it will break the application until code is recompiled.
- They are also not supported by declarative workflows, which is what we should all be moving to.
The recommended solution is to use ADD COLUMN+later DROP COLUMN instead of RENAME. This is the only safe way.
func (*RenameColumnLinter) Description ¶ added in v0.11.0
func (l *RenameColumnLinter) Description() string
func (*RenameColumnLinter) Lint ¶ added in v0.11.0
func (l *RenameColumnLinter) Lint(_ []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
func (*RenameColumnLinter) Name ¶ added in v0.11.0
func (l *RenameColumnLinter) Name() string
func (*RenameColumnLinter) String ¶ added in v0.11.0
func (l *RenameColumnLinter) String() string
type ReservedWordsLinter ¶
type ReservedWordsLinter struct{}
ReservedWordsLinter checks for usage of MySQL reserved words in table and column names. Using reserved words as identifiers can cause syntax errors and requires quoting.
func (*ReservedWordsLinter) Description ¶
func (l *ReservedWordsLinter) Description() string
func (*ReservedWordsLinter) Lint ¶
func (l *ReservedWordsLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint walks the post-state of the schema so warnings reflect the final table and column names. ALTERs that DROP, RENAME, or MODIFY away a reserved-word identifier no longer produce false positives, and ALTERs that introduce a reserved-word identifier (ADD COLUMN, RENAME TABLE, etc.) are reported against the final shape.
func (*ReservedWordsLinter) Name ¶
func (l *ReservedWordsLinter) Name() string
func (*ReservedWordsLinter) String ¶
func (l *ReservedWordsLinter) String() string
type Severity ¶
type Severity int
Severity represents the severity level of a linting violation
const ( // SeverityInfo indicates a suggestion or style preference // This is the default value if no explicit Severity is given SeverityInfo Severity = iota // SeverityWarning indicates a best practice violation or potential issue SeverityWarning // SeverityError indicates a violation that will cause actual problems // (syntax errors, MySQL limitations, etc.) SeverityError )
type TypePedanticLinter ¶ added in v0.14.0
type TypePedanticLinter struct {
// contains filtered or unexported fields
}
TypePedanticLinter enforces type consistency across tables in the same schema.
Rule 1 (same_name): Columns sharing a name across tables should share a type. Rule 2 (inferred_fk): Columns named like {table}_id are inferred to reference {table}.id and should match its type — JOINs across mismatched types force implicit casts and prevent index use.
Both rules operate on a synthesized post-state view: existing tables with pending CREATE TABLE / ALTER TABLE changes applied. This makes the linter useful both for whole-schema audits and for ALTER-driven migration flows.
func (*TypePedanticLinter) Configure ¶ added in v0.14.0
func (l *TypePedanticLinter) Configure(config map[string]string) error
func (*TypePedanticLinter) DefaultConfig ¶ added in v0.14.0
func (l *TypePedanticLinter) DefaultConfig() map[string]string
func (*TypePedanticLinter) Description ¶ added in v0.14.0
func (l *TypePedanticLinter) Description() string
func (*TypePedanticLinter) Lint ¶ added in v0.14.0
func (l *TypePedanticLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
func (*TypePedanticLinter) Name ¶ added in v0.14.0
func (l *TypePedanticLinter) Name() string
func (*TypePedanticLinter) String ¶ added in v0.14.0
func (l *TypePedanticLinter) String() string
type UnsafeLinter ¶
type UnsafeLinter struct {
// contains filtered or unexported fields
}
func (*UnsafeLinter) DefaultConfig ¶
func (l *UnsafeLinter) DefaultConfig() map[string]string
func (*UnsafeLinter) Description ¶
func (l *UnsafeLinter) Description() string
func (*UnsafeLinter) Lint ¶
func (l *UnsafeLinter) Lint(_ []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
func (*UnsafeLinter) Name ¶
func (l *UnsafeLinter) Name() string
func (*UnsafeLinter) String ¶
func (l *UnsafeLinter) String() string
type Violation ¶
type Violation struct {
// Linter is the linter that produced this violation
Linter Linter
// Severity is the severity level of the violation
Severity Severity
// Message is a human-readable description of the violation
Message string
// Location provides information about where the violation occurred
Location *Location
// Suggestion is an optional suggestion for fixing the violation
Suggestion *string
// Context provides additional context-specific information
Context map[string]any
}
Violation represents a linting violation found during analysis
func FilterByLinter ¶
FilterByLinter returns only violations from the specified linter.
func RunLinters ¶
func RunLinters(existingSchema []*statement.CreateTable, changes []*statement.AbstractStatement, config Config) ([]Violation, error)
RunLinters runs all enabled linters and returns any violations found. Linters are executed in an undefined order.
A linter is executed if:
- It is enabled by default (set during Register), AND
- It is not explicitly disabled in config.Enabled
OR:
- It is explicitly enabled in config.Enabled
If a linter implements ConfigurableLinter and has settings in config.Settings, those settings are applied before running the linter.
type ZeroDateLinter ¶
type ZeroDateLinter struct{}
func (*ZeroDateLinter) Description ¶
func (l *ZeroDateLinter) Description() string
func (*ZeroDateLinter) Lint ¶
func (l *ZeroDateLinter) Lint(existingTables []*statement.CreateTable, changes []*statement.AbstractStatement) (violations []Violation)
Lint operates on a post-state view of the schema so that ALTERs which fix zero-date columns don't produce false positives on the pre-state.
func (*ZeroDateLinter) Name ¶
func (l *ZeroDateLinter) Name() string
func (*ZeroDateLinter) String ¶
func (l *ZeroDateLinter) String() string
Source Files
¶
- cmd_diff.go
- cmd_lint.go
- declarative.go
- lint.go
- lint_allow_charset.go
- lint_allow_engine.go
- lint_auto_inc_capacity.go
- lint_datetime_index_position.go
- lint_has_fk.go
- lint_has_float.go
- lint_has_timestamp.go
- lint_invisible_index.go
- lint_multiple_alter.go
- lint_name_case.go
- lint_primary_key_type.go
- lint_redundant_indexes.go
- lint_rename_column.go
- lint_reserved_words.go
- lint_type_pedantic.go
- lint_unsafe.go
- lint_zero_date.go
- linter.go
- load.go
- post_state.go
- registry.go
- violation.go