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. |