Skip to content

Commit 4df7051

Browse files
Fix "Error: could not execute revoke query: pq: tuple concurrently updated" (cyrilgdn#169)
* Show "tuple concurrently updated" error The "tuple concurrently updated" error occurs when multiple connections do grants and revokes at the same time. Also see - https://www.postgresql.org/message-id/CAHyXU0y3M1Znv=MJ7u1y3pSMutALcoryQx9-v8c-WkaTdvvQ7Q@mail.gmail.com - https://community.pivotal.io/s/article/Query-Fails-with-ERROR--tuple-concurrently-updated?language=en_US * Restrict grant/revoke to one at a time By using a mutex we ensure that we only execute a single GRANT or REVOKE at the same time fixing the error "Error: could not execute revoke query: pq: tuple concurrently updated" which was caused by doing multiple grants and revokes at the same time. * Update issues number Now that the PR is created we have an issue number! * Use pgLockRole instead of a mutex By using the pgLockRole locking methods (which uses `pg_advisory_xact_lock`) we avoid locking to the provider and instead let postgres handle this.
1 parent 37a092e commit 4df7051

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

examples/issues/169/dev.tfrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# See https://www.terraform.io/cli/config/config-file#development-overrides-for-provider-developers
2+
# Use `go build -o ./examples/issues/169/postgresql/terraform-provider-postgresql` in the project root to build the provider.
3+
# Then run terraform in this example directory.
4+
5+
provider_installation {
6+
dev_overrides {
7+
"cyrilgdn/postgresql" = "./postgresql"
8+
}
9+
direct {}
10+
}

examples/issues/169/test.tf

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# This tests reproduces an issue for the following error message.
2+
# ```
3+
# terraform.tfstate
4+
#
5+
# │ Error: could not execute revoke query: pq: tuple concurrently updated
6+
#
7+
# │ with postgresql_grant.public_revoke_database["test3"],
8+
# │ on test.tf line 40, in resource "postgresql_grant" "public_revoke_database":
9+
# │ 40: resource "postgresql_grant" "public_revoke_database" {
10+
#
11+
#
12+
# ```
13+
14+
terraform {
15+
required_version = ">= 1.0"
16+
17+
required_providers {
18+
postgresql = {
19+
source = "cyrilgdn/postgresql"
20+
version = ">=1.14"
21+
}
22+
}
23+
}
24+
25+
locals {
26+
databases = toset([for idx in range(4) : format("test%d", idx)])
27+
}
28+
29+
provider "postgresql" {
30+
superuser = false
31+
}
32+
33+
resource "postgresql_database" "db" {
34+
for_each = local.databases
35+
name = each.key
36+
37+
# Use template1 instead of template0 (see https://www.postgresql.org/docs/current/manage-ag-templatedbs.html)
38+
template = "template1"
39+
}
40+
41+
resource "postgresql_role" "demo" {
42+
name = "demo"
43+
login = true
44+
password = "Happy-Holidays!"
45+
}
46+
47+
locals {
48+
# Create a local that is depends on postgresql_database to ensure it's created
49+
dbs = { for database in local.databases : database => postgresql_database.db[database].name }
50+
}
51+
52+
# Revoke default accesses for PUBLIC role to the databases
53+
resource "postgresql_grant" "public_revoke_database" {
54+
for_each = local.dbs
55+
database = each.value
56+
role = "public"
57+
object_type = "database"
58+
privileges = []
59+
60+
with_grant_option = true
61+
}
62+
63+
# Revoke default accesses for PUBLIC role to the public schema
64+
resource "postgresql_grant" "public_revoke_schema" {
65+
for_each = local.dbs
66+
database = each.value
67+
role = "public"
68+
schema = "public"
69+
object_type = "schema"
70+
privileges = []
71+
72+
with_grant_option = true
73+
}
74+
75+
resource "postgresql_grant" "demo_db_connect" {
76+
for_each = local.dbs
77+
database = each.value
78+
role = postgresql_role.demo.name
79+
schema = "public"
80+
object_type = "database"
81+
privileges = ["CONNECT"]
82+
}

postgresql/resource_postgresql_grant.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ func resourcePostgreSQLGrantCreate(db *DBConnection, d *schema.ResourceData) err
149149
}
150150
defer deferredRollback(txn)
151151

152+
role := d.Get("role").(string)
153+
if err := pgLockRole(txn, role); err != nil {
154+
return err
155+
}
156+
152157
owners, err := getRolesToGrant(txn, d)
153158
if err != nil {
154159
return err
@@ -197,6 +202,11 @@ func resourcePostgreSQLGrantDelete(db *DBConnection, d *schema.ResourceData) err
197202
}
198203
defer deferredRollback(txn)
199204

205+
role := d.Get("role").(string)
206+
if err := pgLockRole(txn, role); err != nil {
207+
return err
208+
}
209+
200210
owners, err := getRolesToGrant(txn, d)
201211
if err != nil {
202212
return err

0 commit comments

Comments
 (0)