-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rule to prevent concurrent index creation inside transactions (#335)
Add a rule that warns about creating an index with the `CONCURRENTLY` option inside a transaction, since that's not allowed in Postgres. This will resolve #332
- Loading branch information
1 parent
7417733
commit 5e0b0a9
Showing
18 changed files
with
299 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[package] | ||
name = "squawk" | ||
version = "0.26.0" | ||
version = "0.27.0" | ||
authors = ["Steve Dignam <[email protected]>"] | ||
edition = "2018" | ||
license = "GPL-3.0" | ||
|
106 changes: 106 additions & 0 deletions
106
docs/docs/ban-concurrent-index-creation-in-transaction.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
--- | ||
id: ban-concurrent-index-creation-in-transaction | ||
title: ban-concurrent-index-creation-in-transaction | ||
--- | ||
|
||
## problem | ||
|
||
While regular index creation can happen inside a transaction, this is not allowed when the `CONCURRENTLY` option is used. | ||
|
||
https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY | ||
|
||
## solution | ||
|
||
Remove surrounding transaction markers if any, and check that the `CREATE INDEX` command is not implicitly wrapped in a transaction. | ||
|
||
Instead of: | ||
|
||
```sql | ||
BEGIN; | ||
-- <any other commands being run transactionally> | ||
CREATE INDEX CONCURRENTLY "email_idx" ON "app_user" ("email"); | ||
COMMIT; | ||
``` | ||
|
||
Use: | ||
|
||
```sql | ||
BEGIN; | ||
-- <any other commands being run transactionally> | ||
COMMIT; | ||
|
||
CREATE INDEX CONCURRENTLY "email_idx" ON "app_user" ("email"); | ||
``` | ||
|
||
If you use a migration tool, it may be configured to automatically wrap commands in transactions; if that's the case, check if it supports running commands in a non-transactional context. | ||
For example, with `alembic`: | ||
|
||
```python | ||
# migrations/*.py | ||
from alembic import op | ||
|
||
def schema_upgrades(): | ||
# <any other commands being run transactionally> | ||
|
||
# alembic allows non-transactional operations using autocommit | ||
with op.get_context().autocommit_block(): | ||
op.create_index( | ||
"email_idx", | ||
"app_user", | ||
["email"], | ||
schema="app", | ||
unique=False, | ||
postgresql_concurrently=True, | ||
) | ||
|
||
# <any other commands being run transactionally> | ||
|
||
def schema_downgrades(): | ||
# <any other downgrade commands> | ||
|
||
op.drop_index( | ||
"email_idx", | ||
schema="app", | ||
) | ||
|
||
# <any other downgrade commands> | ||
``` | ||
|
||
Or alternatively: | ||
|
||
```python | ||
# migrations/*.py | ||
from alembic import op | ||
|
||
def schema_upgrades(): | ||
# <any other commands being run transactionally> | ||
|
||
# you can also execute BEGIN/COMMIT to delineate transactions | ||
op.execute("COMMIT;") | ||
op.execute("SET statement_timeout = 0;") | ||
op.create_index( | ||
"email_idx", | ||
"app_user", | ||
["email"], | ||
schema="app", | ||
unique=False, | ||
postgresql_concurrently=True, | ||
) | ||
|
||
op.execute("BEGIN;") | ||
# <any other commands being run transactionally> | ||
|
||
def schema_downgrades(): | ||
# <any other downgrade commands> | ||
|
||
op.drop_index( | ||
"email_idx", | ||
schema="app", | ||
) | ||
|
||
# <any other downgrade commands> | ||
``` | ||
|
||
## links | ||
|
||
https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
linter/src/rules/ban_concurrent_index_creation_in_transaction.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
use crate::versions::Version; | ||
use crate::violations::{RuleViolation, RuleViolationKind}; | ||
|
||
use squawk_parser::ast::{RawStmt, Stmt, TransactionStmtKind}; | ||
|
||
#[must_use] | ||
pub fn ban_concurrent_index_creation_in_transaction( | ||
tree: &[RawStmt], | ||
_pg_version: Option<Version>, | ||
assume_in_transaction: bool, | ||
) -> Vec<RuleViolation> { | ||
let mut in_transaction = assume_in_transaction; | ||
let mut errs = vec![]; | ||
for raw_stmt in tree { | ||
match &raw_stmt.stmt { | ||
Stmt::TransactionStmt(stmt) => { | ||
if stmt.kind == TransactionStmtKind::Begin && !in_transaction { | ||
in_transaction = true; | ||
} | ||
if stmt.kind == TransactionStmtKind::Commit { | ||
in_transaction = false; | ||
} | ||
} | ||
Stmt::IndexStmt(stmt) => { | ||
if stmt.concurrent && in_transaction { | ||
errs.push(RuleViolation::new( | ||
RuleViolationKind::BanConcurrentIndexCreationInTransaction, | ||
raw_stmt.into(), | ||
None, | ||
)); | ||
} | ||
} | ||
_ => continue, | ||
} | ||
} | ||
errs | ||
} | ||
|
||
#[cfg(test)] | ||
mod test_rules { | ||
use insta::assert_debug_snapshot; | ||
|
||
use crate::{ | ||
check_sql_with_rule, | ||
violations::{RuleViolation, RuleViolationKind}, | ||
}; | ||
|
||
fn lint_sql(sql: &str) -> Vec<RuleViolation> { | ||
check_sql_with_rule( | ||
sql, | ||
&RuleViolationKind::BanConcurrentIndexCreationInTransaction, | ||
None, | ||
false, | ||
) | ||
.unwrap() | ||
} | ||
|
||
fn lint_sql_assuming_in_transaction(sql: &str) -> Vec<RuleViolation> { | ||
check_sql_with_rule( | ||
sql, | ||
&RuleViolationKind::BanConcurrentIndexCreationInTransaction, | ||
None, | ||
true, | ||
) | ||
.unwrap() | ||
} | ||
|
||
#[test] | ||
fn test_adding_index_concurrently_in_transaction() { | ||
let bad_sql = r#" | ||
-- instead of | ||
BEGIN; | ||
CREATE INDEX CONCURRENTLY "field_name_idx" ON "table_name" ("field_name"); | ||
COMMIT; | ||
"#; | ||
|
||
assert_debug_snapshot!(lint_sql(bad_sql)); | ||
|
||
let ok_sql = r#" | ||
-- run outside a transaction | ||
CREATE INDEX CONCURRENTLY "field_name_idx" ON "table_name" ("field_name"); | ||
"#; | ||
assert_debug_snapshot!(lint_sql(ok_sql)); | ||
} | ||
|
||
#[test] | ||
fn test_adding_index_concurrently_in_transaction_with_assume_in_transaction() { | ||
let bad_sql = r#" | ||
-- instead of | ||
CREATE INDEX CONCURRENTLY "field_name_idx" ON "table_name" ("field_name"); | ||
"#; | ||
|
||
assert_debug_snapshot!(lint_sql_assuming_in_transaction(bad_sql)); | ||
|
||
let ok_sql = r#" | ||
-- run outside a transaction | ||
COMMIT; | ||
CREATE INDEX CONCURRENTLY "field_name_idx" ON "table_name" ("field_name"); | ||
"#; | ||
assert_debug_snapshot!(lint_sql_assuming_in_transaction(ok_sql)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
...ndex_creation_in_transaction__test_rules__adding_index_concurrently_in_transaction-2.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
source: linter/src/rules/ban_concurrent_index_creation_in_transaction.rs | ||
expression: lint_sql(ok_sql) | ||
--- | ||
[] |
23 changes: 23 additions & 0 deletions
23
..._index_creation_in_transaction__test_rules__adding_index_concurrently_in_transaction.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--- | ||
source: linter/src/rules/ban_concurrent_index_creation_in_transaction.rs | ||
expression: lint_sql(bad_sql) | ||
--- | ||
[ | ||
RuleViolation { | ||
kind: BanConcurrentIndexCreationInTransaction, | ||
span: Span { | ||
start: 25, | ||
len: Some( | ||
76, | ||
), | ||
}, | ||
messages: [ | ||
Note( | ||
"Concurrent index creation is not allowed inside a transaction.", | ||
), | ||
Help( | ||
"Build the index outside any transactions.", | ||
), | ||
], | ||
}, | ||
] |
5 changes: 5 additions & 0 deletions
5
...n__test_rules__adding_index_concurrently_in_transaction_with_assume_in_transaction-2.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
source: linter/src/rules/ban_concurrent_index_creation_in_transaction.rs | ||
expression: lint_sql_assuming_in_transaction(ok_sql) | ||
--- | ||
[] |
23 changes: 23 additions & 0 deletions
23
...ion__test_rules__adding_index_concurrently_in_transaction_with_assume_in_transaction.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--- | ||
source: linter/src/rules/ban_concurrent_index_creation_in_transaction.rs | ||
expression: lint_sql_assuming_in_transaction(bad_sql) | ||
--- | ||
[ | ||
RuleViolation { | ||
kind: BanConcurrentIndexCreationInTransaction, | ||
span: Span { | ||
start: 0, | ||
len: Some( | ||
92, | ||
), | ||
}, | ||
messages: [ | ||
Note( | ||
"Concurrent index creation is not allowed inside a transaction.", | ||
), | ||
Help( | ||
"Build the index outside any transactions.", | ||
), | ||
], | ||
}, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "squawk-cli", | ||
"version": "0.26.0", | ||
"version": "0.27.0", | ||
"description": "linter for PostgreSQL, focused on migrations", | ||
"repository": "[email protected]:sbdchd/squawk.git", | ||
"author": "Steve Dignam <[email protected]>", | ||
|