@pgpm/verify 0.4.0

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 (66) hide show
  1. package/LICENSE +22 -0
  2. package/Makefile +6 -0
  3. package/README.md +5 -0
  4. package/__tests__/ext-verify.test.ts +490 -0
  5. package/deploy/procedures/get_entity_from_str.sql +21 -0
  6. package/deploy/procedures/get_schema_from_str.sql +21 -0
  7. package/deploy/procedures/list_indexes.sql +29 -0
  8. package/deploy/procedures/list_memberships.sql +31 -0
  9. package/deploy/procedures/verify_constraint.sql +27 -0
  10. package/deploy/procedures/verify_domain.sql +32 -0
  11. package/deploy/procedures/verify_extension.sql +24 -0
  12. package/deploy/procedures/verify_function.sql +37 -0
  13. package/deploy/procedures/verify_index.sql +23 -0
  14. package/deploy/procedures/verify_membership.sql +27 -0
  15. package/deploy/procedures/verify_policy.sql +35 -0
  16. package/deploy/procedures/verify_role.sql +24 -0
  17. package/deploy/procedures/verify_schema.sql +24 -0
  18. package/deploy/procedures/verify_security.sql +33 -0
  19. package/deploy/procedures/verify_table.sql +30 -0
  20. package/deploy/procedures/verify_table_grant.sql +32 -0
  21. package/deploy/procedures/verify_trigger.sql +32 -0
  22. package/deploy/procedures/verify_type.sql +32 -0
  23. package/deploy/procedures/verify_view.sql +30 -0
  24. package/jest.config.js +15 -0
  25. package/launchql-verify.control +8 -0
  26. package/launchql.plan +24 -0
  27. package/package.json +25 -0
  28. package/revert/procedures/get_entity_from_str.sql +7 -0
  29. package/revert/procedures/get_schema_from_str.sql +7 -0
  30. package/revert/procedures/list_indexes.sql +7 -0
  31. package/revert/procedures/list_memberships.sql +7 -0
  32. package/revert/procedures/verify_constraint.sql +7 -0
  33. package/revert/procedures/verify_domain.sql +7 -0
  34. package/revert/procedures/verify_extension.sql +7 -0
  35. package/revert/procedures/verify_function.sql +7 -0
  36. package/revert/procedures/verify_index.sql +7 -0
  37. package/revert/procedures/verify_membership.sql +7 -0
  38. package/revert/procedures/verify_policy.sql +7 -0
  39. package/revert/procedures/verify_role.sql +7 -0
  40. package/revert/procedures/verify_schema.sql +7 -0
  41. package/revert/procedures/verify_security.sql +7 -0
  42. package/revert/procedures/verify_table.sql +7 -0
  43. package/revert/procedures/verify_table_grant.sql +7 -0
  44. package/revert/procedures/verify_trigger.sql +7 -0
  45. package/revert/procedures/verify_type.sql +7 -0
  46. package/revert/procedures/verify_view.sql +7 -0
  47. package/sql/launchql-verify--0.4.6.sql +358 -0
  48. package/verify/procedures/get_entity_from_str.sql +7 -0
  49. package/verify/procedures/get_schema_from_str.sql +7 -0
  50. package/verify/procedures/list_indexes.sql +7 -0
  51. package/verify/procedures/list_memberships.sql +7 -0
  52. package/verify/procedures/verify_constraint.sql +7 -0
  53. package/verify/procedures/verify_domain.sql +7 -0
  54. package/verify/procedures/verify_extension.sql +7 -0
  55. package/verify/procedures/verify_function.sql +7 -0
  56. package/verify/procedures/verify_index.sql +7 -0
  57. package/verify/procedures/verify_membership.sql +7 -0
  58. package/verify/procedures/verify_policy.sql +7 -0
  59. package/verify/procedures/verify_role.sql +7 -0
  60. package/verify/procedures/verify_schema.sql +7 -0
  61. package/verify/procedures/verify_security.sql +7 -0
  62. package/verify/procedures/verify_table.sql +7 -0
  63. package/verify/procedures/verify_table_grant.sql +7 -0
  64. package/verify/procedures/verify_trigger.sql +7 -0
  65. package/verify/procedures/verify_type.sql +7 -0
  66. package/verify/procedures/verify_view.sql +7 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
4
+ Copyright (c) 2025 Interweb, Inc.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/Makefile ADDED
@@ -0,0 +1,6 @@
1
+ EXTENSION = launchql-verify
2
+ DATA = sql/launchql-verify--0.4.6.sql
3
+
4
+ PG_CONFIG = pg_config
5
+ PGXS := $(shell $(PG_CONFIG) --pgxs)
6
+ include $(PGXS)
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @pgpm/verify
2
+
3
+ Verification utilities for LaunchQL deploy/verify/revert workflow.
4
+
5
+ Provides essential verification functions used by other modules to validate database changes, including verify_table, verify_index, verify_function, and other assertion utilities.
@@ -0,0 +1,490 @@
1
+ import { getConnections, PgTestClient } from 'pgsql-test';
2
+ import { snapshot } from 'graphile-test';
3
+
4
+ let pg: PgTestClient;
5
+ let teardown: () => Promise<void>;
6
+
7
+ describe('ext-verify utilities', () => {
8
+ beforeAll(async () => {
9
+ ({ pg, teardown } = await getConnections());
10
+ });
11
+
12
+ afterAll(async () => {
13
+ await teardown();
14
+ });
15
+
16
+ beforeEach(async () => {
17
+ await pg.beforeEach();
18
+ });
19
+
20
+ afterEach(async () => {
21
+ await pg.afterEach();
22
+ });
23
+
24
+ describe('helper functions', () => {
25
+ it('get_entity_from_str should extract entity name', async () => {
26
+ const tests = [
27
+ ['public.users', 'users'],
28
+ ['users', 'users'],
29
+ ['schema.table_name', 'table_name'],
30
+ ['my_function', 'my_function']
31
+ ];
32
+
33
+ for (const [input, expected] of tests) {
34
+ const [result] = await pg.any(
35
+ `SELECT get_entity_from_str($1) as entity`,
36
+ [input]
37
+ );
38
+ expect(result.entity).toBe(expected);
39
+ }
40
+ });
41
+
42
+ it('get_schema_from_str should extract schema name', async () => {
43
+ const tests = [
44
+ ['public.users', 'public'],
45
+ ['users', 'public'],
46
+ ['my_schema.table_name', 'my_schema'],
47
+ ['function_name', 'public']
48
+ ];
49
+
50
+ for (const [input, expected] of tests) {
51
+ const [result] = await pg.any(
52
+ `SELECT get_schema_from_str($1) as schema_name`,
53
+ [input]
54
+ );
55
+ expect(result.schema_name).toBe(expected);
56
+ }
57
+ });
58
+ });
59
+
60
+ describe('schema verification', () => {
61
+ it('verify_schema should return true for existing schema', async () => {
62
+ const [result] = await pg.any(
63
+ `SELECT verify_schema('public') as verified`
64
+ );
65
+ expect(result.verified).toBe(true);
66
+ });
67
+
68
+ it('verify_schema should throw for non-existent schema', async () => {
69
+ await expect(
70
+ pg.any(`SELECT verify_schema('nonexistent_schema')`)
71
+ ).rejects.toThrow('Nonexistent schema');
72
+ });
73
+ });
74
+
75
+ describe('table verification', () => {
76
+ beforeEach(async () => {
77
+ // Create a test table
78
+ await pg.any(`
79
+ CREATE TABLE test_table (
80
+ id serial PRIMARY KEY,
81
+ name text NOT NULL,
82
+ email text UNIQUE
83
+ )
84
+ `);
85
+ });
86
+
87
+ it('verify_table should return true for existing table', async () => {
88
+ const [result] = await pg.any(
89
+ `SELECT verify_table('test_table') as verified`
90
+ );
91
+ expect(result.verified).toBe(true);
92
+ });
93
+
94
+ it('verify_table should work with schema-qualified names', async () => {
95
+ const [result] = await pg.any(
96
+ `SELECT verify_table('public.test_table') as verified`
97
+ );
98
+ expect(result.verified).toBe(true);
99
+ });
100
+
101
+ it('verify_table should throw for non-existent table', async () => {
102
+ await expect(
103
+ pg.any(`SELECT verify_table('nonexistent_table')`)
104
+ ).rejects.toThrow('Nonexistent table');
105
+ });
106
+ });
107
+
108
+ describe('constraint verification', () => {
109
+ beforeEach(async () => {
110
+ await pg.any(`
111
+ CREATE TABLE test_constraints (
112
+ id serial PRIMARY KEY,
113
+ name text NOT NULL,
114
+ email text UNIQUE,
115
+ age integer CHECK (age > 0)
116
+ )
117
+ `);
118
+ });
119
+
120
+ it('verify_constraint should return true for existing constraint', async () => {
121
+ // Get the actual constraint name (PostgreSQL generates names)
122
+ const [constraint] = await pg.any(`
123
+ SELECT conname
124
+ FROM pg_constraint c
125
+ JOIN pg_class t ON c.conrelid = t.oid
126
+ WHERE t.relname = 'test_constraints' AND contype = 'u'
127
+ LIMIT 1
128
+ `);
129
+
130
+ if (constraint) {
131
+ const [result] = await pg.any(
132
+ `SELECT verify_constraint('test_constraints', $1) as verified`,
133
+ [constraint.conname]
134
+ );
135
+ expect(result.verified).toBe(true);
136
+ }
137
+ });
138
+
139
+ it('verify_constraint should throw for non-existent constraint', async () => {
140
+ await expect(
141
+ pg.any(`SELECT verify_constraint('test_constraints', 'nonexistent_constraint')`)
142
+ ).rejects.toThrow('Nonexistent constraint');
143
+ });
144
+ });
145
+
146
+ describe('function verification', () => {
147
+ beforeEach(async () => {
148
+ await pg.any(`
149
+ CREATE OR REPLACE FUNCTION test_function(x integer)
150
+ RETURNS integer AS $$
151
+ BEGIN
152
+ RETURN x * 2;
153
+ END;
154
+ $$ LANGUAGE plpgsql;
155
+ `);
156
+ });
157
+
158
+ it('verify_function should return true for existing function', async () => {
159
+ const [result] = await pg.any(
160
+ `SELECT verify_function('test_function') as verified`
161
+ );
162
+ expect(result.verified).toBe(true);
163
+ });
164
+
165
+ it('verify_function should work with schema-qualified names', async () => {
166
+ const [result] = await pg.any(
167
+ `SELECT verify_function('public.test_function') as verified`
168
+ );
169
+ expect(result.verified).toBe(true);
170
+ });
171
+
172
+ it('verify_function should throw for non-existent function', async () => {
173
+ await expect(
174
+ pg.any(`SELECT verify_function('nonexistent_function')`)
175
+ ).rejects.toThrow('Nonexistent function');
176
+ });
177
+ });
178
+
179
+ describe('view verification', () => {
180
+ beforeEach(async () => {
181
+ await pg.any(`
182
+ CREATE TABLE test_view_base (
183
+ id serial PRIMARY KEY,
184
+ name text
185
+ )
186
+ `);
187
+
188
+ await pg.any(`
189
+ CREATE VIEW test_view AS
190
+ SELECT id, name FROM test_view_base WHERE name IS NOT NULL
191
+ `);
192
+ });
193
+
194
+ it('verify_view should return true for existing view', async () => {
195
+ const [result] = await pg.any(
196
+ `SELECT verify_view('test_view') as verified`
197
+ );
198
+ expect(result.verified).toBe(true);
199
+ });
200
+
201
+ it('verify_view should work with schema-qualified names', async () => {
202
+ const [result] = await pg.any(
203
+ `SELECT verify_view('public.test_view') as verified`
204
+ );
205
+ expect(result.verified).toBe(true);
206
+ });
207
+
208
+ it('verify_view should throw for non-existent view', async () => {
209
+ await expect(
210
+ pg.any(`SELECT verify_view('nonexistent_view')`)
211
+ ).rejects.toThrow('Nonexistent view');
212
+ });
213
+ });
214
+
215
+ describe('index verification', () => {
216
+ beforeEach(async () => {
217
+ await pg.any(`
218
+ CREATE TABLE test_index_table (
219
+ id serial PRIMARY KEY,
220
+ name text,
221
+ email text
222
+ )
223
+ `);
224
+
225
+ await pg.any(`
226
+ CREATE INDEX test_custom_index ON test_index_table (name)
227
+ `);
228
+ });
229
+
230
+ it('verify_index should return true for existing index', async () => {
231
+ const [result] = await pg.any(
232
+ `SELECT verify_index('test_index_table', 'test_custom_index') as verified`
233
+ );
234
+ expect(result.verified).toBe(true);
235
+ });
236
+
237
+ it('verify_index should throw for non-existent index', async () => {
238
+ await expect(
239
+ pg.any(`SELECT verify_index('test_index_table', 'nonexistent_index')`)
240
+ ).rejects.toThrow('Nonexistent index');
241
+ });
242
+
243
+ it('list_indexes should return index information', async () => {
244
+ const results = await pg.any(
245
+ `SELECT * FROM list_indexes('test_index_table', 'test_custom_index')`
246
+ );
247
+
248
+ expect(results).toHaveLength(1);
249
+ expect(results[0].schema_name).toBe('public');
250
+ expect(results[0].table_name).toBe('test_index_table');
251
+ expect(results[0].index_name).toBe('test_custom_index');
252
+ });
253
+ });
254
+
255
+ describe('trigger verification', () => {
256
+ beforeEach(async () => {
257
+ await pg.any(`
258
+ CREATE TABLE test_trigger_table (
259
+ id serial PRIMARY KEY,
260
+ name text,
261
+ updated_at timestamp DEFAULT now()
262
+ )
263
+ `);
264
+
265
+ await pg.any(`
266
+ CREATE OR REPLACE FUNCTION update_timestamp()
267
+ RETURNS trigger AS $$
268
+ BEGIN
269
+ NEW.updated_at = now();
270
+ RETURN NEW;
271
+ END;
272
+ $$ LANGUAGE plpgsql;
273
+ `);
274
+
275
+ await pg.any(`
276
+ CREATE TRIGGER test_update_trigger
277
+ BEFORE UPDATE ON test_trigger_table
278
+ FOR EACH ROW EXECUTE FUNCTION update_timestamp()
279
+ `);
280
+ });
281
+
282
+ it('verify_trigger should return true for existing trigger', async () => {
283
+ const [result] = await pg.any(
284
+ `SELECT verify_trigger('test_update_trigger') as verified`
285
+ );
286
+ expect(result.verified).toBe(true);
287
+ });
288
+
289
+ it('verify_trigger should throw for non-existent trigger', async () => {
290
+ await expect(
291
+ pg.any(`SELECT verify_trigger('nonexistent_trigger')`)
292
+ ).rejects.toThrow('Nonexistent trigger');
293
+ });
294
+ });
295
+
296
+ describe('type verification', () => {
297
+ beforeEach(async () => {
298
+ await pg.any(`
299
+ CREATE TYPE test_enum AS ENUM ('active', 'inactive', 'pending')
300
+ `);
301
+
302
+ await pg.any(`
303
+ CREATE TYPE test_composite AS (
304
+ name text,
305
+ value integer
306
+ )
307
+ `);
308
+ });
309
+
310
+ it('verify_type should return true for existing enum type', async () => {
311
+ const [result] = await pg.any(
312
+ `SELECT verify_type('test_enum') as verified`
313
+ );
314
+ expect(result.verified).toBe(true);
315
+ });
316
+
317
+ it('verify_type should return true for existing composite type', async () => {
318
+ const [result] = await pg.any(
319
+ `SELECT verify_type('test_composite') as verified`
320
+ );
321
+ expect(result.verified).toBe(true);
322
+ });
323
+
324
+ it('verify_type should throw for non-existent type', async () => {
325
+ await expect(
326
+ pg.any(`SELECT verify_type('nonexistent_type')`)
327
+ ).rejects.toThrow('Nonexistent type');
328
+ });
329
+ });
330
+
331
+ describe('domain verification', () => {
332
+ beforeEach(async () => {
333
+ await pg.any(`
334
+ CREATE DOMAIN test_domain AS text CHECK (length(value) > 0)
335
+ `);
336
+ });
337
+
338
+ it('verify_domain should return true for existing domain', async () => {
339
+ const [result] = await pg.any(
340
+ `SELECT verify_domain('test_domain') as verified`
341
+ );
342
+ expect(result.verified).toBe(true);
343
+ });
344
+
345
+ it('verify_domain should throw for non-existent domain', async () => {
346
+ await expect(
347
+ pg.any(`SELECT verify_domain('nonexistent_domain')`)
348
+ ).rejects.toThrow('Nonexistent type');
349
+ });
350
+ });
351
+
352
+ describe('role and membership verification', () => {
353
+ it('verify_role should return true for current user', async () => {
354
+ // Get current user
355
+ const [currentUser] = await pg.any(`SELECT current_user as username`);
356
+
357
+ const [result] = await pg.any(
358
+ `SELECT verify_role($1) as verified`,
359
+ [currentUser.username]
360
+ );
361
+ expect(result.verified).toBe(true);
362
+ });
363
+
364
+ it('verify_role should throw for non-existent role', async () => {
365
+ await expect(
366
+ pg.any(`SELECT verify_role('nonexistent_user_12345')`)
367
+ ).rejects.toThrow('Nonexistent user');
368
+ });
369
+
370
+ it('list_memberships should return role memberships', async () => {
371
+ const [currentUser] = await pg.any(`SELECT current_user as username`);
372
+
373
+ const results = await pg.any(
374
+ `SELECT * FROM list_memberships($1)`,
375
+ [currentUser.username]
376
+ );
377
+
378
+ // Should at least include the user themselves
379
+ expect(results.length).toBeGreaterThan(0);
380
+ const usernames = results.map(r => r.rolname);
381
+ expect(usernames).toContain(currentUser.username);
382
+ });
383
+ });
384
+
385
+ describe('grant verification', () => {
386
+ beforeEach(async () => {
387
+ await pg.any(`
388
+ CREATE TABLE test_grants_table (
389
+ id serial PRIMARY KEY,
390
+ data text
391
+ )
392
+ `);
393
+ });
394
+
395
+ it('verify_table_grant should work for existing grants', async () => {
396
+ // Grant SELECT to current user (if not already granted)
397
+ const [currentUser] = await pg.any(`SELECT current_user as username`);
398
+
399
+ // The table owner should have all privileges
400
+ const [result] = await pg.any(
401
+ `SELECT verify_table_grant('test_grants_table', 'INSERT', $1) as verified`,
402
+ [currentUser.username]
403
+ );
404
+ expect(result.verified).toBe(true);
405
+ });
406
+ });
407
+
408
+ describe('extension verification', () => {
409
+ it('verify_extension should return true for available extensions', async () => {
410
+ // Most PostgreSQL installations have the plpgsql extension available
411
+ const availableExtensions = await pg.any(`
412
+ SELECT name FROM pg_available_extensions
413
+ WHERE name IN ('plpgsql', 'uuid-ossp', 'pgcrypto')
414
+ LIMIT 1
415
+ `);
416
+
417
+ if (availableExtensions.length > 0) {
418
+ const [result] = await pg.any(
419
+ `SELECT verify_extension($1) as verified`,
420
+ [availableExtensions[0].name]
421
+ );
422
+ expect(result.verified).toBe(true);
423
+ } else {
424
+ // Skip this test if no common extensions are available
425
+ expect(true).toBe(true);
426
+ }
427
+ });
428
+
429
+ it('verify_extension should throw for non-existent extension', async () => {
430
+ await expect(
431
+ pg.any(`SELECT verify_extension('definitely_nonexistent_extension_12345')`)
432
+ ).rejects.toThrow('Nonexistent extension');
433
+ });
434
+ });
435
+
436
+ describe('security verification', () => {
437
+ beforeEach(async () => {
438
+ await pg.any(`
439
+ CREATE TABLE test_security_table (
440
+ id serial PRIMARY KEY,
441
+ user_id text,
442
+ data text
443
+ )
444
+ `);
445
+
446
+ // Enable row level security
447
+ await pg.any(`ALTER TABLE test_security_table ENABLE ROW LEVEL SECURITY`);
448
+ });
449
+
450
+ it('verify_security should return true for tables with RLS enabled', async () => {
451
+ const [result] = await pg.any(
452
+ `SELECT verify_security('test_security_table') as verified`
453
+ );
454
+ expect(result.verified).toBe(true);
455
+ });
456
+ });
457
+
458
+ describe('policy verification', () => {
459
+ beforeEach(async () => {
460
+ await pg.any(`
461
+ CREATE TABLE test_policy_table (
462
+ id serial PRIMARY KEY,
463
+ user_id text,
464
+ data text
465
+ )
466
+ `);
467
+
468
+ await pg.any(`ALTER TABLE test_policy_table ENABLE ROW LEVEL SECURITY`);
469
+
470
+ await pg.any(`
471
+ CREATE POLICY test_policy ON test_policy_table
472
+ FOR SELECT
473
+ USING (user_id = current_user)
474
+ `);
475
+ });
476
+
477
+ it('verify_policy should return true for existing policy', async () => {
478
+ const [result] = await pg.any(
479
+ `SELECT verify_policy('test_policy', 'test_policy_table') as verified`
480
+ );
481
+ expect(result.verified).toBe(true);
482
+ });
483
+
484
+ it('verify_policy should throw for non-existent policy', async () => {
485
+ await expect(
486
+ pg.any(`SELECT verify_policy('nonexistent_policy', 'test_policy_table')`)
487
+ ).rejects.toThrow('Nonexistent policy');
488
+ });
489
+ });
490
+ });
@@ -0,0 +1,21 @@
1
+ -- Deploy procedures/get_entity_from_str to pg
2
+
3
+ BEGIN;
4
+ CREATE FUNCTION get_entity_from_str (qualified_name text)
5
+ RETURNS text
6
+ AS $$
7
+ DECLARE
8
+ parts text[];
9
+ BEGIN
10
+ SELECT
11
+ parse_ident(qualified_name) INTO parts;
12
+ IF cardinality(parts) > 1 THEN
13
+ RETURN parts[2];
14
+ ELSE
15
+ RETURN parts[1];
16
+ END IF;
17
+ END;
18
+ $$
19
+ LANGUAGE plpgsql
20
+ STRICT;
21
+ COMMIT;
@@ -0,0 +1,21 @@
1
+ -- Deploy procedures/get_schema_from_str to pg
2
+ BEGIN;
3
+ CREATE FUNCTION get_schema_from_str (qualified_name text)
4
+ RETURNS text
5
+ AS $$
6
+ DECLARE
7
+ parts text[];
8
+ BEGIN
9
+ SELECT
10
+ parse_ident(qualified_name) INTO parts;
11
+ IF cardinality(parts) > 1 THEN
12
+ RETURN parts[1];
13
+ ELSE
14
+ RETURN 'public';
15
+ END IF;
16
+ END;
17
+ $$
18
+ LANGUAGE plpgsql
19
+ STRICT;
20
+ COMMIT;
21
+
@@ -0,0 +1,29 @@
1
+ -- Deploy procedures/list_indexes to pg
2
+ -- requires: procedures/get_entity_from_str
3
+ -- requires: procedures/get_schema_from_str
4
+
5
+ BEGIN;
6
+
7
+ CREATE FUNCTION list_indexes (_table text, _index text)
8
+ RETURNS TABLE (schema_name text, table_name text, index_name text)
9
+ AS $$
10
+ SELECT
11
+ n.nspname::text AS schema_name,
12
+ t.relname::text AS table_name,
13
+ i.relname::text AS index_name
14
+ FROM
15
+ pg_class t,
16
+ pg_class i,
17
+ pg_index ix,
18
+ pg_catalog.pg_namespace n
19
+ WHERE
20
+ t.oid = ix.indrelid
21
+ AND i.oid = ix.indexrelid
22
+ AND n.oid = i.relnamespace
23
+ AND n.nspname = get_schema_from_str(_table)
24
+ AND i.relname = _index
25
+ AND t.relname = get_entity_from_str(_table);
26
+ $$
27
+ LANGUAGE 'sql' IMMUTABLE;
28
+
29
+ COMMIT;
@@ -0,0 +1,31 @@
1
+ -- Deploy procedures/list_memberships to pg
2
+
3
+ BEGIN;
4
+
5
+ CREATE FUNCTION list_memberships (_user text)
6
+ RETURNS TABLE (rolname text)
7
+ AS $$ WITH RECURSIVE cte AS (
8
+ SELECT
9
+ oid
10
+ FROM
11
+ pg_roles
12
+ WHERE
13
+ rolname = _user
14
+ UNION ALL
15
+ SELECT
16
+ m.roleid
17
+ FROM
18
+ cte
19
+ JOIN pg_auth_members m ON m.member = cte.oid
20
+ )
21
+ SELECT
22
+ pg_roles.rolname::text AS rolname
23
+ FROM
24
+ cte c,
25
+ pg_roles
26
+ WHERE
27
+ pg_roles.oid = c.oid;
28
+ $$
29
+ LANGUAGE 'sql' IMMUTABLE;
30
+
31
+ COMMIT;
@@ -0,0 +1,27 @@
1
+ -- Deploy procedures/verify_constraint to pg
2
+ BEGIN;
3
+ -- https://stackoverflow.com/questions/20087259/how-to-find-whether-unique-key-constraint-exists-for-given-columns
4
+ CREATE FUNCTION verify_constraint (_table text, _name text)
5
+ RETURNS boolean
6
+ AS $$
7
+ BEGIN
8
+ IF EXISTS (
9
+ SELECT
10
+ c.conname,
11
+ pg_get_constraintdef(c.oid)
12
+ FROM
13
+ pg_constraint c
14
+ WHERE
15
+ conname = _name
16
+ AND c.conrelid = _table::regclass) THEN
17
+ RETURN TRUE;
18
+ ELSE
19
+ RAISE EXCEPTION 'Nonexistent constraint --> %', _name
20
+ USING HINT = 'Please check';
21
+ END IF;
22
+ END;
23
+ $$
24
+ LANGUAGE 'plpgsql'
25
+ IMMUTABLE;
26
+ COMMIT;
27
+
@@ -0,0 +1,32 @@
1
+ -- Deploy procedures/verify_domain to pg
2
+
3
+ -- requires: procedures/get_entity_from_str
4
+ -- requires: procedures/get_schema_from_str
5
+
6
+ BEGIN;
7
+ CREATE FUNCTION verify_domain (_type text)
8
+ RETURNS boolean
9
+ AS $$
10
+ BEGIN
11
+ IF EXISTS (
12
+ SELECT
13
+ pg_type.typname,
14
+ n.nspname
15
+ FROM
16
+ pg_type,
17
+ pg_catalog.pg_namespace n
18
+ WHERE
19
+ typtype = 'd'
20
+ AND typname = get_entity_from_str (_type)
21
+ AND nspname = get_schema_from_str (_type)) THEN
22
+ RETURN TRUE;
23
+ ELSE
24
+ RAISE EXCEPTION 'Nonexistent type --> %', _type
25
+ USING HINT = 'Please check';
26
+ END IF;
27
+ END;
28
+ $$
29
+ LANGUAGE 'plpgsql'
30
+ IMMUTABLE;
31
+ COMMIT;
32
+