pgsql-test 2.11.4 → 2.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/admin.js +68 -22
  2. package/esm/admin.js +68 -22
  3. package/package.json +7 -7
package/admin.js CHANGED
@@ -97,7 +97,33 @@ class DbAdmin {
97
97
  }
98
98
  async grantRole(role, user, dbName) {
99
99
  const db = dbName ?? this.config.database;
100
- const sql = `GRANT ${role} TO ${user};`;
100
+ const sql = `
101
+ DO $$
102
+ DECLARE
103
+ v_user TEXT := '${user.replace(/'/g, "''")}';
104
+ v_role TEXT := '${role.replace(/'/g, "''")}';
105
+ BEGIN
106
+ -- Pre-check to avoid unnecessary GRANTs; still catch TOCTOU under concurrency
107
+ IF NOT EXISTS (
108
+ SELECT 1 FROM pg_auth_members am
109
+ JOIN pg_roles r1 ON am.roleid = r1.oid
110
+ JOIN pg_roles r2 ON am.member = r2.oid
111
+ WHERE r1.rolname = v_role AND r2.rolname = v_user
112
+ ) THEN
113
+ BEGIN
114
+ EXECUTE format('GRANT %I TO %I', v_role, v_user);
115
+ EXCEPTION
116
+ WHEN unique_violation THEN
117
+ -- Concurrent membership grant; safe to ignore
118
+ NULL;
119
+ WHEN undefined_object THEN
120
+ -- Role or user missing; emit notice and continue
121
+ RAISE NOTICE 'Missing role when granting % to %', v_role, v_user;
122
+ END;
123
+ END IF;
124
+ END
125
+ $$;
126
+ `;
101
127
  await this.streamSql(sql, db);
102
128
  }
103
129
  async grantConnect(role, dbName) {
@@ -126,35 +152,55 @@ class DbAdmin {
126
152
  -- Role already exists; optionally sync attributes here with ALTER ROLE
127
153
  NULL;
128
154
  END;
129
-
130
- -- Grant anonymous role if not already granted
155
+
156
+ -- CI/CD concurrency note: GRANT role membership can race on pg_auth_members unique index
157
+ -- We pre-check membership and still catch unique_violation to handle TOCTOU safely.
131
158
  IF NOT EXISTS (
132
- SELECT 1 FROM pg_auth_members am
133
- JOIN pg_roles r1 ON am.roleid = r1.oid
134
- JOIN pg_roles r2 ON am.member = r2.oid
135
- WHERE r1.rolname = '${anonRole}' AND r2.rolname = '${user}'
159
+ SELECT 1 FROM pg_auth_members am
160
+ JOIN pg_roles r1 ON am.roleid = r1.oid
161
+ JOIN pg_roles r2 ON am.member = r2.oid
162
+ WHERE r1.rolname = '${anonRole.replace(/'/g, "''")}' AND r2.rolname = v_user
136
163
  ) THEN
137
- GRANT ${anonRole} TO ${user};
164
+ BEGIN
165
+ EXECUTE format('GRANT %I TO %I', '${anonRole.replace(/'/g, "''")}', v_user);
166
+ EXCEPTION
167
+ WHEN unique_violation THEN
168
+ NULL;
169
+ WHEN undefined_object THEN
170
+ RAISE NOTICE 'Missing role when granting % to %', '${anonRole.replace(/'/g, "''")}', v_user;
171
+ END;
138
172
  END IF;
139
-
140
- -- Grant authenticated role if not already granted
173
+
141
174
  IF NOT EXISTS (
142
- SELECT 1 FROM pg_auth_members am
143
- JOIN pg_roles r1 ON am.roleid = r1.oid
144
- JOIN pg_roles r2 ON am.member = r2.oid
145
- WHERE r1.rolname = '${authRole}' AND r2.rolname = '${user}'
175
+ SELECT 1 FROM pg_auth_members am
176
+ JOIN pg_roles r1 ON am.roleid = r1.oid
177
+ JOIN pg_roles r2 ON am.member = r2.oid
178
+ WHERE r1.rolname = '${authRole.replace(/'/g, "''")}' AND r2.rolname = v_user
146
179
  ) THEN
147
- GRANT ${authRole} TO ${user};
180
+ BEGIN
181
+ EXECUTE format('GRANT %I TO %I', '${authRole.replace(/'/g, "''")}', v_user);
182
+ EXCEPTION
183
+ WHEN unique_violation THEN
184
+ NULL;
185
+ WHEN undefined_object THEN
186
+ RAISE NOTICE 'Missing role when granting % to %', '${authRole.replace(/'/g, "''")}', v_user;
187
+ END;
148
188
  END IF;
149
-
150
- -- Grant administrator role if not already granted
189
+
151
190
  IF NOT EXISTS (
152
- SELECT 1 FROM pg_auth_members am
153
- JOIN pg_roles r1 ON am.roleid = r1.oid
154
- JOIN pg_roles r2 ON am.member = r2.oid
155
- WHERE r1.rolname = '${adminRole}' AND r2.rolname = '${user}'
191
+ SELECT 1 FROM pg_auth_members am
192
+ JOIN pg_roles r1 ON am.roleid = r1.oid
193
+ JOIN pg_roles r2 ON am.member = r2.oid
194
+ WHERE r1.rolname = '${adminRole.replace(/'/g, "''")}' AND r2.rolname = v_user
156
195
  ) THEN
157
- GRANT ${adminRole} TO ${user};
196
+ BEGIN
197
+ EXECUTE format('GRANT %I TO %I', '${adminRole.replace(/'/g, "''")}', v_user);
198
+ EXCEPTION
199
+ WHEN unique_violation THEN
200
+ NULL;
201
+ WHEN undefined_object THEN
202
+ RAISE NOTICE 'Missing role when granting % to %', '${adminRole.replace(/'/g, "''")}', v_user;
203
+ END;
158
204
  END IF;
159
205
  END $$;
160
206
  `.trim();
package/esm/admin.js CHANGED
@@ -94,7 +94,33 @@ export class DbAdmin {
94
94
  }
95
95
  async grantRole(role, user, dbName) {
96
96
  const db = dbName ?? this.config.database;
97
- const sql = `GRANT ${role} TO ${user};`;
97
+ const sql = `
98
+ DO $$
99
+ DECLARE
100
+ v_user TEXT := '${user.replace(/'/g, "''")}';
101
+ v_role TEXT := '${role.replace(/'/g, "''")}';
102
+ BEGIN
103
+ -- Pre-check to avoid unnecessary GRANTs; still catch TOCTOU under concurrency
104
+ IF NOT EXISTS (
105
+ SELECT 1 FROM pg_auth_members am
106
+ JOIN pg_roles r1 ON am.roleid = r1.oid
107
+ JOIN pg_roles r2 ON am.member = r2.oid
108
+ WHERE r1.rolname = v_role AND r2.rolname = v_user
109
+ ) THEN
110
+ BEGIN
111
+ EXECUTE format('GRANT %I TO %I', v_role, v_user);
112
+ EXCEPTION
113
+ WHEN unique_violation THEN
114
+ -- Concurrent membership grant; safe to ignore
115
+ NULL;
116
+ WHEN undefined_object THEN
117
+ -- Role or user missing; emit notice and continue
118
+ RAISE NOTICE 'Missing role when granting % to %', v_role, v_user;
119
+ END;
120
+ END IF;
121
+ END
122
+ $$;
123
+ `;
98
124
  await this.streamSql(sql, db);
99
125
  }
100
126
  async grantConnect(role, dbName) {
@@ -123,35 +149,55 @@ export class DbAdmin {
123
149
  -- Role already exists; optionally sync attributes here with ALTER ROLE
124
150
  NULL;
125
151
  END;
126
-
127
- -- Grant anonymous role if not already granted
152
+
153
+ -- CI/CD concurrency note: GRANT role membership can race on pg_auth_members unique index
154
+ -- We pre-check membership and still catch unique_violation to handle TOCTOU safely.
128
155
  IF NOT EXISTS (
129
- SELECT 1 FROM pg_auth_members am
130
- JOIN pg_roles r1 ON am.roleid = r1.oid
131
- JOIN pg_roles r2 ON am.member = r2.oid
132
- WHERE r1.rolname = '${anonRole}' AND r2.rolname = '${user}'
156
+ SELECT 1 FROM pg_auth_members am
157
+ JOIN pg_roles r1 ON am.roleid = r1.oid
158
+ JOIN pg_roles r2 ON am.member = r2.oid
159
+ WHERE r1.rolname = '${anonRole.replace(/'/g, "''")}' AND r2.rolname = v_user
133
160
  ) THEN
134
- GRANT ${anonRole} TO ${user};
161
+ BEGIN
162
+ EXECUTE format('GRANT %I TO %I', '${anonRole.replace(/'/g, "''")}', v_user);
163
+ EXCEPTION
164
+ WHEN unique_violation THEN
165
+ NULL;
166
+ WHEN undefined_object THEN
167
+ RAISE NOTICE 'Missing role when granting % to %', '${anonRole.replace(/'/g, "''")}', v_user;
168
+ END;
135
169
  END IF;
136
-
137
- -- Grant authenticated role if not already granted
170
+
138
171
  IF NOT EXISTS (
139
- SELECT 1 FROM pg_auth_members am
140
- JOIN pg_roles r1 ON am.roleid = r1.oid
141
- JOIN pg_roles r2 ON am.member = r2.oid
142
- WHERE r1.rolname = '${authRole}' AND r2.rolname = '${user}'
172
+ SELECT 1 FROM pg_auth_members am
173
+ JOIN pg_roles r1 ON am.roleid = r1.oid
174
+ JOIN pg_roles r2 ON am.member = r2.oid
175
+ WHERE r1.rolname = '${authRole.replace(/'/g, "''")}' AND r2.rolname = v_user
143
176
  ) THEN
144
- GRANT ${authRole} TO ${user};
177
+ BEGIN
178
+ EXECUTE format('GRANT %I TO %I', '${authRole.replace(/'/g, "''")}', v_user);
179
+ EXCEPTION
180
+ WHEN unique_violation THEN
181
+ NULL;
182
+ WHEN undefined_object THEN
183
+ RAISE NOTICE 'Missing role when granting % to %', '${authRole.replace(/'/g, "''")}', v_user;
184
+ END;
145
185
  END IF;
146
-
147
- -- Grant administrator role if not already granted
186
+
148
187
  IF NOT EXISTS (
149
- SELECT 1 FROM pg_auth_members am
150
- JOIN pg_roles r1 ON am.roleid = r1.oid
151
- JOIN pg_roles r2 ON am.member = r2.oid
152
- WHERE r1.rolname = '${adminRole}' AND r2.rolname = '${user}'
188
+ SELECT 1 FROM pg_auth_members am
189
+ JOIN pg_roles r1 ON am.roleid = r1.oid
190
+ JOIN pg_roles r2 ON am.member = r2.oid
191
+ WHERE r1.rolname = '${adminRole.replace(/'/g, "''")}' AND r2.rolname = v_user
153
192
  ) THEN
154
- GRANT ${adminRole} TO ${user};
193
+ BEGIN
194
+ EXECUTE format('GRANT %I TO %I', '${adminRole.replace(/'/g, "''")}', v_user);
195
+ EXCEPTION
196
+ WHEN unique_violation THEN
197
+ NULL;
198
+ WHEN undefined_object THEN
199
+ RAISE NOTICE 'Missing role when granting % to %', '${adminRole.replace(/'/g, "''")}', v_user;
200
+ END;
155
201
  END IF;
156
202
  END $$;
157
203
  `.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgsql-test",
3
- "version": "2.11.4",
3
+ "version": "2.11.6",
4
4
  "author": "Dan Lynch <pyramation@gmail.com>",
5
5
  "description": "pgsql-test offers isolated, role-aware, and rollback-friendly PostgreSQL environments for integration tests — giving developers realistic test coverage without external state pollution",
6
6
  "main": "index.js",
@@ -60,14 +60,14 @@
60
60
  "@types/pg-copy-streams": "^1.2.5"
61
61
  },
62
62
  "dependencies": {
63
- "@launchql/core": "^2.11.4",
64
- "@launchql/env": "^2.4.1",
65
- "@launchql/server-utils": "^2.4.1",
66
- "@launchql/types": "^2.6.0",
63
+ "@launchql/core": "^2.11.6",
64
+ "@launchql/env": "^2.4.2",
65
+ "@launchql/server-utils": "^2.4.2",
66
+ "@launchql/types": "^2.6.1",
67
67
  "pg": "^8.16.0",
68
- "pg-cache": "^1.3.2",
68
+ "pg-cache": "^1.3.3",
69
69
  "pg-copy-streams": "^6.0.6",
70
70
  "pg-env": "^1.1.0"
71
71
  },
72
- "gitHead": "57f8c45ad88b59a054b4d8a95d53cf7ef16b8d8a"
72
+ "gitHead": "0b26f726ac2ca19886af7553a06e95ce826d515b"
73
73
  }