Skip to content

Rules

The rules of the Postgres Language Server, by group.

  • ✅ The rule is part of the recommended preset.
  • 🛠 The rule only runs on files inside the configured migrations.migrationsDir.
  • 🔌 The rule needs a database connection.

Correctness

Code that fails at runtime for reasons other than names or types.

Rule Description
avoidAlterEnumAddValue ALTER TYPE ... ADD VALUE cannot run inside a transaction block in older Postgres versions.
banConcurrentIndexCreationInTransaction ✅ Concurrent index creation is not allowed within a transaction.
invalidDropTypeSignature ✅ DROP TYPE and DROP DOMAIN don't take a parameter list.
transactionNesting Detects problematic transaction nesting that could lead to unexpected behavior.

Destructive

Code that loses data or breaks existing clients.

Rule Description
addingRequiredField 🛠 Adding a new column that is NOT NULL and has no default value to an existing table effectively makes it required.
banDeleteWithoutWhere ✅ A DELETE statement without a WHERE clause will remove all rows from the table.
banDropColumn ✅ 🛠 Dropping a column may break existing clients.
banDropDatabase 🛠 Dropping a database may break existing clients (and everything else, really).
banDropNotNull ✅ 🛠 Dropping a NOT NULL constraint may break existing clients.
banDropSchema ✅ 🛠 Dropping a schema will remove all objects within it and may break existing clients.
banDropTable ✅ 🛠 Dropping a table may break existing clients.
banTruncate ✅ 🛠 Truncating a table removes all rows and can cause data loss in production.
banTruncateCascade 🛠 Using TRUNCATE's CASCADE option will truncate any tables that are also foreign-keyed to the specified tables.
banUpdateWithoutWhere ✅ An UPDATE statement without a WHERE clause will modify all rows in the table.
changingColumnType 🛠 Changing a column type may require a table rewrite and break existing clients.
renamingColumn 🛠 Renaming columns may break existing queries and application code.
renamingTable 🛠 Renaming tables may break existing queries and application code.

Safety

Valid code that may be dangerous against a live database: locks, rewrites, or blocking.

Rule Description
addSerialColumn ✅ 🛠 Adding a column with a SERIAL type or GENERATED ALWAYS AS ... STORED causes a full table rewrite.
addingFieldWithDefault ✅ 🛠 Adding a column with a DEFAULT value may lead to a table rewrite while holding an ACCESS EXCLUSIVE lock.
addingForeignKeyConstraint ✅ 🛠 Adding a foreign key constraint requires a table scan and a SHARE ROW EXCLUSIVE lock on both tables, which blocks writes.
addingNotNullField ✅ 🛠 Setting a column NOT NULL blocks reads while the table is scanned.
addingPrimaryKeyConstraint ✅ 🛠 Adding a primary key constraint results in locks and table rewrites.
avoidAddingExclusionConstraint ✅ 🛠 Adding an exclusion constraint acquires an ACCESS EXCLUSIVE lock.
avoidAttachingPartition ✅ 🛠 Attaching a partition acquires an ACCESS EXCLUSIVE lock on the parent table.
avoidCreateTrigger 🛠 Creating a trigger acquires a SHARE ROW EXCLUSIVE lock on the table.
avoidEnableDisableTrigger 🛠 Enabling or disabling a trigger acquires a SHARE ROW EXCLUSIVE lock.
avoidWideLockWindow ✅ 🛠 Acquiring ACCESS EXCLUSIVE locks on multiple tables widens the lock window.
banDropTrigger 🛠 Dropping a trigger acquires an ACCESS EXCLUSIVE lock on the table.
banVacuumFull ✅ 🛠 VACUUM FULL rewrites the entire table and acquires an ACCESS EXCLUSIVE lock.
constraintMissingNotValid 🛠 Adding constraints without NOT VALID blocks all reads and writes.
disallowUniqueConstraint 🛠 Disallow adding a UNIQUE constraint without using an existing index.
lockTimeoutWarning ✅ 🛠 Taking a dangerous lock without setting a lock timeout can cause indefinite blocking.
multipleAlterTable ✅ 🛠 Multiple ALTER TABLE statements on the same table should be combined into a single statement.
preferRobustStmts 🛠 Prefer statements with guards for robustness in migrations.
requireConcurrentDetachPartition ✅ 🛠 Detaching a partition without CONCURRENTLY acquires an ACCESS EXCLUSIVE lock.
requireConcurrentIndexCreation 🛠 Creating indexes non-concurrently can lock the table for writes.
requireConcurrentIndexDeletion 🛠 Dropping indexes non-concurrently can lock the table for reads.
requireConcurrentRefreshMatview ✅ 🛠 REFRESH MATERIALIZED VIEW without CONCURRENTLY acquires an ACCESS EXCLUSIVE lock.
requireConcurrentReindex ✅ 🛠 REINDEX without CONCURRENTLY acquires an ACCESS EXCLUSIVE lock on the table.
requireIdleInTransactionTimeout 🛠 Dangerous lock statements should be preceded by SET idle_in_transaction_session_timeout.
requireSeparateConstraintValidation ✅ 🛠 Validating a constraint in the same transaction it was added as NOT VALID defeats the purpose.
requireStatementTimeout 🛠 Dangerous lock statements should be preceded by SET statement_timeout.
runningStatementWhileHoldingAccessExclusive ✅ 🛠 Running additional statements while holding an ACCESS EXCLUSIVE lock blocks all table access.

Style

Schema design preferences. Not enabled by the recommended preset.

Rule Description
banCharField Using CHAR(n) or CHARACTER(n) types is discouraged.
creatingEnum Creating enum types is not recommended for new applications.
preferBigInt Prefer BIGINT over smaller integer types.
preferIdentity Prefer using IDENTITY columns over serial columns.
preferJsonb Prefer JSONB over JSON types.
preferTextField Prefer using TEXT over VARCHAR(n) types.
preferTimestamptz Prefer TIMESTAMPTZ over TIMESTAMP types.

Typecheck

Code that fails at runtime because of names or types.

Rule Description
ambiguousColumn ✅ 🔌 An unqualified column name matches columns of more than one relation in scope.
assignmentTypeMismatch ✅ 🔌 An expression assigned to a column cannot be coerced to that column's type.
functionArgumentMismatch ✅ 🔌 A function name and argument count exist, but its argument types do not select exactly one overload.
functionReturnTypeMismatch ✅ 🔌 The final statement of a SQL function doesn't return what the function is declared to return.
insertColumnMismatch ✅ 🔌 An INSERT has a different number of target columns than values.
invalidCast ✅ 🔌 An explicit cast is not permitted between the source and target types.
missingFromClauseEntry ✅ 🔌 A column is qualified with a name that is not in the FROM clause.
operatorTypeMismatch ✅ 🔌 An operator exists by name but cannot be resolved for the operand types, or has multiple equally suitable candidates.
unknownColumn ✅ 🔌 A column does not exist on the relation or record it is taken from.
unknownFunction ✅ 🔌 No function with this name accepts this number of arguments.
unknownRelation ✅ 🔌 A table, view, or materialized view does not exist.
unknownSchema ✅ 🔌 A schema does not exist.
unknownType ✅ 🔌 A type does not exist.