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 can fix the issues it reports. Its page says whether the fix is safe.
- 🔌 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 operations are not allowed within a transaction. |
| banDuplicateColumnAssignments ✅ 🔧 | A column is assigned or declared more than once. |
| banNonTransactionalStatementInTransaction ✅ | Statements that Postgres can't run inside a transaction block. |
| banUncommittedTransaction ✅ 🛠 🔧 | A transaction is started but never committed or rolled back. |
| identifierTooLong ✅ 🔧 | Identifiers longer than 63 bytes are silently truncated. |
| 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. |
| banAlterIdentity 🛠 | Changing the identity of a column may break inserts from existing clients. |
| banDeleteWithoutWhere ✅ | A DELETE statement without a WHERE clause will remove all rows from the table. |
| banDropColumn ✅ 🛠 | Dropping a column may break existing clients. |
| banDropConstraint 🛠 | Dropping a constraint may break existing clients. |
| banDropDatabase 🛠 | Dropping a database may break existing clients (and everything else, really). |
| banDropDefault 🛠 | Dropping a column or domain default may break existing clients. |
| banDropDomain 🛠 | Dropping a domain may break existing clients. |
| banDropExtension 🛠 | Dropping an extension may break existing clients. |
| banDropFunction 🛠 | Dropping a function, procedure, routine, or aggregate may break existing clients. |
| banDropGeneratedExpression 🛠 | Dropping the expression of a generated column may break existing clients. |
| 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. |
| banDropSequence 🛠 | Dropping a sequence may break existing clients. |
| banDropTable ✅ 🛠 | Dropping a table may break existing clients. |
| banDropType 🛠 | Dropping a type, cast, operator, operator class, or operator family may break existing clients. |
| banDropView 🛠 | Dropping a view or materialized view may break existing clients. |
| banSetSchema 🛠 | Moving an object to another schema 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. |
| renamingObject 🛠 | Renaming an object or an enum value may break existing clients. |
| 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. |
| banAlterDomainWithAddConstraint 🛠 | Adding a constraint to a domain has poor support for online migrations. |
| banCreateDomainWithConstraint 🛠 | Domains with constraints have poor support for online migrations. |
| 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. |
| requireEnumValueOrdering 🛠 | ALTER TYPE ... ADD VALUE without BEFORE or AFTER appends the value to the end of the enum. |
| requireTableSchema | Table names in DDL should be schema-qualified. |
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. |