Skip to content

Commit

Permalink
Fix "Error: could not execute revoke query: pq: tuple concurrently up…
Browse files Browse the repository at this point in the history
…dated" (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.
  • Loading branch information
boekkooi-lengoo authored and SpencerBinXia committed Feb 11, 2022
1 parent 4a4a6b5 commit 465b9bd
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 0 deletions.
10 changes: 10 additions & 0 deletions examples/issues/169/dev.tfrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# See https://www.terraform.io/cli/config/config-file#development-overrides-for-provider-developers
# Use `go build -o ./examples/issues/169/postgresql/terraform-provider-postgresql` in the project root to build the provider.
# Then run terraform in this example directory.

provider_installation {
dev_overrides {
"cyrilgdn/postgresql" = "./postgresql"
}
direct {}
}
82 changes: 82 additions & 0 deletions examples/issues/169/test.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# This tests reproduces an issue for the following error message.
# ```
# terraform.tfstate
#
# │ Error: could not execute revoke query: pq: tuple concurrently updated
#
# │ with postgresql_grant.public_revoke_database["test3"],
# │ on test.tf line 40, in resource "postgresql_grant" "public_revoke_database":
# │ 40: resource "postgresql_grant" "public_revoke_database" {
#
#
# ```

terraform {
required_version = ">= 1.0"

required_providers {
postgresql = {
source = "cyrilgdn/postgresql"
version = ">=1.14"
}
}
}

locals {
databases = toset([for idx in range(4) : format("test%d", idx)])
}

provider "postgresql" {
superuser = false
}

resource "postgresql_database" "db" {
for_each = local.databases
name = each.key

# Use template1 instead of template0 (see https://www.postgresql.org/docs/current/manage-ag-templatedbs.html)
template = "template1"
}

resource "postgresql_role" "demo" {
name = "demo"
login = true
password = "Happy-Holidays!"
}

locals {
# Create a local that is depends on postgresql_database to ensure it's created
dbs = { for database in local.databases : database => postgresql_database.db[database].name }
}

# Revoke default accesses for PUBLIC role to the databases
resource "postgresql_grant" "public_revoke_database" {
for_each = local.dbs
database = each.value
role = "public"
object_type = "database"
privileges = []

with_grant_option = true
}

# Revoke default accesses for PUBLIC role to the public schema
resource "postgresql_grant" "public_revoke_schema" {
for_each = local.dbs
database = each.value
role = "public"
schema = "public"
object_type = "schema"
privileges = []

with_grant_option = true
}

resource "postgresql_grant" "demo_db_connect" {
for_each = local.dbs
database = each.value
role = postgresql_role.demo.name
schema = "public"
object_type = "database"
privileges = ["CONNECT"]
}
10 changes: 10 additions & 0 deletions postgresql/resource_postgresql_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ func resourcePostgreSQLGrantCreate(db *DBConnection, d *schema.ResourceData) err
}
defer deferredRollback(txn)

role := d.Get("role").(string)
if err := pgLockRole(txn, role); err != nil {
return err
}

owners, err := getRolesToGrant(txn, d)
if err != nil {
return err
Expand Down Expand Up @@ -197,6 +202,11 @@ func resourcePostgreSQLGrantDelete(db *DBConnection, d *schema.ResourceData) err
}
defer deferredRollback(txn)

role := d.Get("role").(string)
if err := pgLockRole(txn, role); err != nil {
return err
}

owners, err := getRolesToGrant(txn, d)
if err != nil {
return err
Expand Down

0 comments on commit 465b9bd

Please sign in to comment.