@axinom/mosaic-db-common 0.14.1-rc.6 → 0.15.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.
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.15.0](https://dev.azure.com/axinom/CMS/_git/Navy/branchCompare?baseVersion=GT@axinom/mosaic-db-common@0.14.0&targetVersion=GT@axinom/mosaic-db-common@0.15.0) (2022-04-01)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* ax-define functions length checking ([6c4a4ae](https://dev.azure.com/axinom/CMS/_git/Navy/commit/6c4a4ae4b188a710e7d8defdab10304bc334844c)), closes [#34155](https://dev.azure.com/axinom/CMS/_workitems/edit/34155)
|
|
12
|
+
* ax-define timestamp propogation ([b9a69b8](https://dev.azure.com/axinom/CMS/_git/Navy/commit/b9a69b859e449e54aef4dc20a0bad3be69ccf676)), closes [#34380](https://dev.azure.com/axinom/CMS/_workitems/edit/34380)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* ax-define user trigger creation fails ([e989bd2](https://dev.azure.com/axinom/CMS/_git/Navy/commit/e989bd2dd9bc951f6d3b8492d7c0f43fd07f4a1d))
|
|
18
|
+
* generate-vscode-sql-snippets can now be launched ([5908776](https://dev.azure.com/axinom/CMS/_git/Navy/commit/59087766f02332de6ca3a78d0d4143ad27a74780)), closes [#34157](https://dev.azure.com/axinom/CMS/_workitems/edit/34157)
|
|
19
|
+
* minor PR comment adjustment ([b5ad8f4](https://dev.azure.com/axinom/CMS/_git/Navy/commit/b5ad8f446c35322e797e804c018255e6d051f8c2)), closes [#34157](https://dev.azure.com/axinom/CMS/_workitems/edit/34157)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
6
23
|
## [0.14.0](https://dev.azure.com/axinom/CMS/_git/Navy/branchCompare?baseVersion=GT@axinom/mosaic-db-common@0.13.0&targetVersion=GT@axinom/mosaic-db-common@0.14.0) (2022-03-16)
|
|
7
24
|
|
|
8
25
|
|
|
@@ -28,3 +28,32 @@ BEGIN
|
|
|
28
28
|
RAISE EXCEPTION '%', format(error_message, VARIADIC placeholder_values) using errcode = error_code;
|
|
29
29
|
END;
|
|
30
30
|
$$;
|
|
31
|
+
|
|
32
|
+
/*-snippet
|
|
33
|
+
{
|
|
34
|
+
"body": [
|
|
35
|
+
"PERFORM ax_utils.validate_identifier_length('${1:identifier}');"
|
|
36
|
+
],
|
|
37
|
+
"description": [
|
|
38
|
+
"Raises an exception if the provided identifer exceeds the PostgreSQL max length (63 bytes).",
|
|
39
|
+
"This can be useful to prevent name truncation, identifier collisions and silently dropped constraints.",
|
|
40
|
+
"If called from a function, the calling function will be referenced in the error message.",
|
|
41
|
+
"An optional second 'hint' parameter can be provided which will be appended to the error message."
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
snippet-*/
|
|
45
|
+
CREATE OR REPLACE FUNCTION ax_utils.validate_identifier_length(identifier text, hint text default '') RETURNS void
|
|
46
|
+
LANGUAGE plpgsql
|
|
47
|
+
AS $$
|
|
48
|
+
DECLARE
|
|
49
|
+
stack text; fcesig text; callinfo text;
|
|
50
|
+
BEGIN
|
|
51
|
+
IF LENGTH(identifier) > 63 THEN
|
|
52
|
+
-- get sig of calling function
|
|
53
|
+
GET DIAGNOSTICS stack = PG_CONTEXT;
|
|
54
|
+
fcesig := substring(substring(stack from 'function (.*)') from 'function (.*?) line');
|
|
55
|
+
callinfo := CASE WHEN fcesig IS NULL THEN '' ELSE 'Invalid identifier in ' || fcesig::regprocedure::text || '. ' END;
|
|
56
|
+
perform ax_utils.raise_error('%sIdentifier "%s" exceeds 63 bytes. %s', 'SETUP', callinfo, identifier, hint);
|
|
57
|
+
END IF;
|
|
58
|
+
END;
|
|
59
|
+
$$;
|
|
@@ -71,9 +71,10 @@ $$;
|
|
|
71
71
|
"SELECT ax_define.define_timestamps_trigger('${1:table_name}', '${2:app_public}');"
|
|
72
72
|
],
|
|
73
73
|
"description": [
|
|
74
|
-
"Defines timestamp trigger which automatically populates 'created_date' and 'updated_date' columns
|
|
75
|
-
"
|
|
76
|
-
"Use this one if you really need to define triggers separately
|
|
74
|
+
"Defines timestamp trigger which automatically populates 'created_date' and 'updated_date' columns.",
|
|
75
|
+
"It is recommended to use 'ax_define.define_audit_date_fields_on_table' to define both columns and triggers in one go.",
|
|
76
|
+
"Use this one if you really need to define triggers separately.\n",
|
|
77
|
+
"N.B! The trigger function is not compatible with JSON columns. Use JSONB instead."
|
|
77
78
|
]
|
|
78
79
|
}
|
|
79
80
|
snippet-*/
|
|
@@ -82,6 +83,7 @@ CREATE OR REPLACE FUNCTION ax_define.define_timestamps_trigger(tableName text, s
|
|
|
82
83
|
AS $$
|
|
83
84
|
BEGIN
|
|
84
85
|
PERFORM ax_define.drop_timestamps_trigger(tableName, schemaName);
|
|
86
|
+
-- Full row comparison is not compatible with column type JSON. Expect error: "could not identify an equality operator for type json"
|
|
85
87
|
EXECUTE 'CREATE trigger _100_timestamps BEFORE UPDATE ON ' || schemaName || '.' || tableName ||
|
|
86
88
|
' for each ROW when (old.* is distinct from new.*) EXECUTE PROCEDURE ax_utils.tg__timestamps();';
|
|
87
89
|
END;
|
|
@@ -102,6 +104,7 @@ CREATE OR REPLACE FUNCTION ax_define.drop_users_trigger(tableName text, schemaNa
|
|
|
102
104
|
AS $$
|
|
103
105
|
BEGIN
|
|
104
106
|
EXECUTE 'DROP trigger IF EXISTS _200_username on ' || schemaName || '.' || tableName || ';';
|
|
107
|
+
EXECUTE 'DROP trigger IF EXISTS _200_username_before_insert on ' || schemaName || '.' || tableName || ';';
|
|
105
108
|
END;
|
|
106
109
|
$$;
|
|
107
110
|
|
|
@@ -111,9 +114,10 @@ $$;
|
|
|
111
114
|
"SELECT ax_define.define_users_trigger('${1:table_name}', '${2:app_public}');"
|
|
112
115
|
],
|
|
113
116
|
"description": [
|
|
114
|
-
"Defines users trigger which automatically populates 'created_user' and 'updated_user' columns
|
|
115
|
-
"
|
|
116
|
-
"Use this one if you really need to define triggers separately
|
|
117
|
+
"Defines users trigger which automatically populates 'created_user' and 'updated_user' columns.",
|
|
118
|
+
"It is recommended to use 'ax_define.define_audit_user_fields_on_table' to define both columns and triggers in one go.",
|
|
119
|
+
"Use this one if you really need to define triggers separately.\n",
|
|
120
|
+
"N.B! The trigger function is not compatible with JSON columns. Use JSONB instead."
|
|
117
121
|
]
|
|
118
122
|
}
|
|
119
123
|
snippet-*/
|
|
@@ -122,8 +126,12 @@ CREATE OR REPLACE FUNCTION ax_define.define_users_trigger(tableName text, schema
|
|
|
122
126
|
AS $$
|
|
123
127
|
BEGIN
|
|
124
128
|
PERFORM ax_define.drop_users_trigger(tableName, schemaName);
|
|
125
|
-
|
|
129
|
+
-- Full row comparison is not compatible with column type JSON. Expect error: "could not identify an equality operator for type json"
|
|
130
|
+
EXECUTE 'CREATE trigger _200_username BEFORE UPDATE ON ' || schemaName || '.' || tableName ||
|
|
126
131
|
' for each ROW when (old.* is distinct from new.*) EXECUTE PROCEDURE ax_utils.tg__username();';
|
|
132
|
+
-- INSERT trigger's WHEN condition cannot reference OLD values
|
|
133
|
+
EXECUTE 'CREATE trigger _200_username_before_insert BEFORE INSERT ON ' || schemaName || '.' || tableName ||
|
|
134
|
+
' EXECUTE PROCEDURE ax_utils.tg__username();';
|
|
127
135
|
END;
|
|
128
136
|
$$;
|
|
129
137
|
|
|
@@ -216,15 +224,18 @@ $$
|
|
|
216
224
|
"SELECT ax_define.drop_index('${1:column_name}', '${2:table_name}');"
|
|
217
225
|
],
|
|
218
226
|
"description": [
|
|
219
|
-
"Drops previously defined index. Applies to both regular and unique indexes
|
|
227
|
+
"Drops previously defined index. Applies to both regular and unique indexes.\n",
|
|
228
|
+
"Third (optional) parameter: the unique name for this index. If the value is NULL then the name will be generated from the table & field name."
|
|
220
229
|
]
|
|
221
230
|
}
|
|
222
231
|
snippet-*/
|
|
223
|
-
CREATE OR REPLACE FUNCTION ax_define.drop_index(fieldName text, tableName text) RETURNS void
|
|
232
|
+
CREATE OR REPLACE FUNCTION ax_define.drop_index(fieldName text, tableName text, indexName text default NULL) RETURNS void
|
|
224
233
|
LANGUAGE plpgsql
|
|
225
234
|
AS $$
|
|
226
235
|
BEGIN
|
|
227
|
-
|
|
236
|
+
SELECT COALESCE(indexName, 'idx_' || tableName || '_' || fieldName) INTO indexName;
|
|
237
|
+
PERFORM ax_utils.validate_identifier_length(indexName, 'If the auto-generated name is too long then an "indexName" argument must be provided.');
|
|
238
|
+
EXECUTE 'DROP INDEX IF EXISTS ' || indexName || ' cascade;';
|
|
228
239
|
END;
|
|
229
240
|
$$;
|
|
230
241
|
|
|
@@ -235,16 +246,18 @@ $$;
|
|
|
235
246
|
],
|
|
236
247
|
"description": [
|
|
237
248
|
"Defines a regular index on a column.\n",
|
|
238
|
-
"
|
|
249
|
+
"Fourth (optional) parameter: a unique name for this index. If the value is NULL then a name will be generated from the table & field name."
|
|
239
250
|
]
|
|
240
251
|
}
|
|
241
252
|
snippet-*/
|
|
242
|
-
CREATE OR REPLACE FUNCTION ax_define.define_index(fieldName text, tableName text, schemaName text) RETURNS void
|
|
253
|
+
CREATE OR REPLACE FUNCTION ax_define.define_index(fieldName text, tableName text, schemaName text, indexName text default NULL) RETURNS void
|
|
243
254
|
LANGUAGE plpgsql
|
|
244
255
|
AS $$
|
|
245
256
|
BEGIN
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
SELECT COALESCE(indexName, 'idx_' || tableName || '_' || fieldName) INTO indexName;
|
|
258
|
+
PERFORM ax_utils.validate_identifier_length(indexName, 'If the auto-generated name is too long then an "indexName" argument must be provided.');
|
|
259
|
+
PERFORM ax_define.drop_index(fieldName, tableName, indexName);
|
|
260
|
+
EXECUTE 'CREATE INDEX ' || indexName || ' ON ' || schemaName || '.' || tableName || ' (' || fieldName || ');';
|
|
248
261
|
END;
|
|
249
262
|
$$;
|
|
250
263
|
|
|
@@ -254,17 +267,20 @@ $$;
|
|
|
254
267
|
"SELECT ax_define.drop_multiple_field_index('{\"${1:column_name_one}\", \"${2:column_name_two}\"}','${3:table_name}');"
|
|
255
268
|
],
|
|
256
269
|
"description": [
|
|
257
|
-
"Drops previously defined multi-field index
|
|
270
|
+
"Drops previously defined multi-field index.\n",
|
|
271
|
+
"Third (optional) parameter: the unique name for this index. If the value is NULL then the name will be generated from the table & field names."
|
|
258
272
|
]
|
|
259
273
|
}
|
|
260
274
|
snippet-*/
|
|
261
|
-
CREATE OR REPLACE FUNCTION ax_define.drop_multiple_field_index(fieldNames text[], tableName text) RETURNS void
|
|
275
|
+
CREATE OR REPLACE FUNCTION ax_define.drop_multiple_field_index(fieldNames text[], tableName text, indexName text default NULL) RETURNS void
|
|
262
276
|
LANGUAGE plpgsql
|
|
263
277
|
AS $$
|
|
264
278
|
DECLARE
|
|
265
279
|
fieldNamesConcat text = array_to_string(array_agg(fieldNames), '_');
|
|
266
280
|
BEGIN
|
|
267
|
-
|
|
281
|
+
SELECT COALESCE(indexName, 'idx_' || tableName || '_' || fieldNamesConcat) INTO indexName;
|
|
282
|
+
PERFORM ax_utils.validate_identifier_length(indexName, 'If the auto-generated name is too long then an "indexName" argument must be provided.');
|
|
283
|
+
EXECUTE 'DROP INDEX IF EXISTS ' || indexName || ' cascade;';
|
|
268
284
|
END;
|
|
269
285
|
$$;
|
|
270
286
|
|
|
@@ -275,19 +291,21 @@ $$;
|
|
|
275
291
|
],
|
|
276
292
|
"description": [
|
|
277
293
|
"Defines a regular index on multiple columns.\n",
|
|
278
|
-
"
|
|
294
|
+
"Fourth (optional) parameter: a unique name for this index. If the value is NULL then a name will be generated from the table & field names."
|
|
279
295
|
]
|
|
280
296
|
}
|
|
281
297
|
snippet-*/
|
|
282
|
-
CREATE OR REPLACE FUNCTION ax_define.define_multiple_field_index(fieldNames text[], tableName text, schemaName text) RETURNS void
|
|
298
|
+
CREATE OR REPLACE FUNCTION ax_define.define_multiple_field_index(fieldNames text[], tableName text, schemaName text, indexName text default NULL) RETURNS void
|
|
283
299
|
LANGUAGE plpgsql
|
|
284
300
|
AS $$
|
|
285
301
|
DECLARE
|
|
286
302
|
fieldNamesConcat text = array_to_string(array_agg(fieldNames), '_');
|
|
287
303
|
fieldList text = array_to_string(array_agg(fieldNames), ', ');
|
|
288
304
|
BEGIN
|
|
289
|
-
|
|
290
|
-
|
|
305
|
+
SELECT COALESCE(indexName, 'idx_' || tableName || '_' || fieldNamesConcat) INTO indexName;
|
|
306
|
+
PERFORM ax_utils.validate_identifier_length(indexName, 'If the auto-generated name is too long then an "indexName" argument must be provided.');
|
|
307
|
+
PERFORM ax_define.drop_multiple_field_index(fieldNames, tableName, indexName);
|
|
308
|
+
EXECUTE 'CREATE INDEX ' || indexName || ' ON ' || schemaName || '.' || tableName || ' (' || fieldList || ');';
|
|
291
309
|
END;
|
|
292
310
|
$$;
|
|
293
311
|
|
|
@@ -298,16 +316,21 @@ $$;
|
|
|
298
316
|
"SELECT ax_define.drop_indexes_with_id('${1:column_name}', '${2:table_name}');"
|
|
299
317
|
],
|
|
300
318
|
"description": [
|
|
301
|
-
"Drops previously defined ASC/DESC indexes based on id and one other column
|
|
319
|
+
"Drops previously defined ASC/DESC indexes based on id and one other column.\n",
|
|
320
|
+
"Third & fourth (optional) parameters: unique names for the indexes. If either value is NULL then the name will be generated from the table & field name."
|
|
302
321
|
]
|
|
303
322
|
}
|
|
304
323
|
snippet-*/
|
|
305
|
-
CREATE OR REPLACE FUNCTION ax_define.drop_indexes_with_id(fieldName text, tableName text) RETURNS void
|
|
324
|
+
CREATE OR REPLACE FUNCTION ax_define.drop_indexes_with_id(fieldName text, tableName text, indexNameAsc text default NULL, indexNameDesc text default NULL) RETURNS void
|
|
306
325
|
LANGUAGE plpgsql
|
|
307
326
|
AS $$
|
|
308
327
|
BEGIN
|
|
309
|
-
|
|
310
|
-
|
|
328
|
+
SELECT COALESCE(indexNameAsc, 'idx_' || tableName || '_' || fieldName || '_asc_with_id') INTO indexNameAsc;
|
|
329
|
+
SELECT COALESCE(indexNameDesc, 'idx_' || tableName || '_' || fieldName || '_desc_with_id') INTO indexNameDesc;
|
|
330
|
+
PERFORM ax_utils.validate_identifier_length(indexNameAsc, 'If the auto-generated name is too long then an "indexNameAsc" argument must be provided.');
|
|
331
|
+
PERFORM ax_utils.validate_identifier_length(indexNameDesc, 'If the auto-generated name is too long then an "indexNameDesc" argument must be provided.');
|
|
332
|
+
EXECUTE 'DROP INDEX IF EXISTS ' || indexNameAsc || ' cascade;';
|
|
333
|
+
EXECUTE 'DROP INDEX IF EXISTS ' || indexNameDesc || ' cascade;';
|
|
311
334
|
END;
|
|
312
335
|
$$;
|
|
313
336
|
|
|
@@ -319,15 +342,19 @@ $$;
|
|
|
319
342
|
"description": [
|
|
320
343
|
"Defines ASC/DESC indexes based on id and one other column.",
|
|
321
344
|
"Use these indexes if there is a plan to perform sorting by this column.\n",
|
|
322
|
-
"
|
|
345
|
+
"Fourth & fifth (optional) parameters: unique names for the indexes. If either value is NULL then a name will be generated from the table & field name."
|
|
323
346
|
]
|
|
324
347
|
}
|
|
325
348
|
snippet-*/
|
|
326
|
-
CREATE OR REPLACE FUNCTION ax_define.define_indexes_with_id(fieldName text, tableName text, schemaName text) RETURNS void
|
|
349
|
+
CREATE OR REPLACE FUNCTION ax_define.define_indexes_with_id(fieldName text, tableName text, schemaName text, indexNameAsc text default NULL, indexNameDesc text default NULL) RETURNS void
|
|
327
350
|
LANGUAGE plpgsql
|
|
328
351
|
AS $$
|
|
329
352
|
BEGIN
|
|
330
|
-
|
|
353
|
+
SELECT COALESCE(indexNameAsc, 'idx_' || tableName || '_' || fieldName || '_asc_with_id') INTO indexNameAsc;
|
|
354
|
+
SELECT COALESCE(indexNameDesc, 'idx_' || tableName || '_' || fieldName || '_desc_with_id') INTO indexNameDesc;
|
|
355
|
+
PERFORM ax_utils.validate_identifier_length(indexNameAsc, 'If the auto-generated name is too long then an "indexNameAsc" argument must be provided.');
|
|
356
|
+
PERFORM ax_utils.validate_identifier_length(indexNameDesc, 'If the auto-generated name is too long then an "indexNameDesc" argument must be provided.');
|
|
357
|
+
PERFORM ax_define.drop_indexes_with_id(fieldName, tableName, indexNameAsc, indexNameDesc);
|
|
331
358
|
EXECUTE 'CREATE INDEX idx_' || tableName || '_' || fieldName || '_asc_with_id ON ' || schemaName || '.' || tableName || ' (' || fieldName || ' ASC, id ASC);';
|
|
332
359
|
EXECUTE 'CREATE INDEX idx_' || tableName || '_' || fieldName || '_desc_with_id ON ' || schemaName || '.' || tableName || ' (' || fieldName || ' DESC, id ASC);';
|
|
333
360
|
END;
|
|
@@ -341,16 +368,18 @@ $$;
|
|
|
341
368
|
"description": [
|
|
342
369
|
"Defines a UNIQUE index on a column.",
|
|
343
370
|
"You can drop a unique index by using the normal 'ax_define.drop_index' function.\n",
|
|
344
|
-
"
|
|
371
|
+
"Fourth (optional) parameter: a unique name for the index. If the value is NULL then a name will be generated from the table & field name."
|
|
345
372
|
]
|
|
346
373
|
}
|
|
347
374
|
snippet-*/
|
|
348
|
-
CREATE OR REPLACE FUNCTION ax_define.define_unique_index(fieldName text, tableName text, schemaName text) RETURNS void
|
|
375
|
+
CREATE OR REPLACE FUNCTION ax_define.define_unique_index(fieldName text, tableName text, schemaName text, indexName text default NULL) RETURNS void
|
|
349
376
|
LANGUAGE plpgsql
|
|
350
377
|
AS $$
|
|
351
378
|
BEGIN
|
|
352
|
-
|
|
353
|
-
|
|
379
|
+
SELECT COALESCE(indexName, 'idx_' || tableName || '_' || fieldName) INTO indexName;
|
|
380
|
+
PERFORM ax_utils.validate_identifier_length(indexName, 'If the auto-generated name is too long then an "indexName" argument must be provided.');
|
|
381
|
+
PERFORM ax_define.drop_index(fieldName, tableName, indexName);
|
|
382
|
+
EXECUTE 'CREATE UNIQUE INDEX ' || indexName || ' ON ' || schemaName || '.' || tableName || ' (' || fieldName || ');';
|
|
354
383
|
END;
|
|
355
384
|
$$;
|
|
356
385
|
|
|
@@ -360,15 +389,18 @@ $$;
|
|
|
360
389
|
"SELECT ax_define.drop_unique_constraint('${1:column_name}', '${2:table_name}', '${3:app_public}');"
|
|
361
390
|
],
|
|
362
391
|
"description": [
|
|
363
|
-
"Drops a UNIQUE constraint based on a column
|
|
392
|
+
"Drops a UNIQUE constraint based on a column.\n",
|
|
393
|
+
"Fourth (optional) parameter: the unique name for the contraint. If the value is NULL then the name will be generated from the table & field name."
|
|
364
394
|
]
|
|
365
395
|
}
|
|
366
396
|
snippet-*/
|
|
367
|
-
CREATE OR REPLACE FUNCTION ax_define.drop_unique_constraint(fieldName text, tableName text, schemaName text) RETURNS void
|
|
397
|
+
CREATE OR REPLACE FUNCTION ax_define.drop_unique_constraint(fieldName text, tableName text, schemaName text, constraintName text default NULL) RETURNS void
|
|
368
398
|
LANGUAGE plpgsql
|
|
369
399
|
AS $$
|
|
370
400
|
BEGIN
|
|
371
|
-
|
|
401
|
+
SELECT COALESCE(constraintName, tableName || '_' || fieldName || '_is_unique') INTO constraintName;
|
|
402
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
403
|
+
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' DROP CONSTRAINT IF EXISTS ' || constraintName || ';';
|
|
372
404
|
END;
|
|
373
405
|
$$;
|
|
374
406
|
|
|
@@ -378,16 +410,19 @@ $$;
|
|
|
378
410
|
"SELECT ax_define.define_unique_constraint('${1:column_name}', '${2:table_name}', '${3:app_public}');"
|
|
379
411
|
],
|
|
380
412
|
"description": [
|
|
381
|
-
"Defines a UNIQUE constraint on a column
|
|
413
|
+
"Defines a UNIQUE constraint on a column.\n",
|
|
414
|
+
"Fourth (optional) parameter: a unique name for the contraint. If the value is NULL then a name will be generated from the table & field name."
|
|
382
415
|
]
|
|
383
416
|
}
|
|
384
417
|
snippet-*/
|
|
385
|
-
CREATE OR REPLACE FUNCTION ax_define.define_unique_constraint(fieldName text, tableName text, schemaName text) RETURNS void
|
|
418
|
+
CREATE OR REPLACE FUNCTION ax_define.define_unique_constraint(fieldName text, tableName text, schemaName text, constraintName text default NULL) RETURNS void
|
|
386
419
|
LANGUAGE plpgsql
|
|
387
420
|
AS $$
|
|
388
421
|
BEGIN
|
|
389
|
-
|
|
390
|
-
|
|
422
|
+
SELECT COALESCE(constraintName, tableName || '_' || fieldName || '_is_unique') INTO constraintName;
|
|
423
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
424
|
+
PERFORM ax_define.drop_unique_constraint(fieldName, tableName, schemaName, constraintName);
|
|
425
|
+
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' ||constraintName || ' UNIQUE (' || fieldName || ');';
|
|
391
426
|
END;
|
|
392
427
|
$$;
|
|
393
428
|
|
|
@@ -399,16 +434,19 @@ $$;
|
|
|
399
434
|
"description": [
|
|
400
435
|
"Defines a deferred UNIQUE constraint on a column.\n",
|
|
401
436
|
"Uniqueness is only checked when the transaction is committed.",
|
|
402
|
-
"If you want to drop it - use 'ax_define.drop_unique_constraint'
|
|
437
|
+
"If you want to drop it - use 'ax_define.drop_unique_constraint'.\n",
|
|
438
|
+
"Fourth (optional) parameter: a unique name for the contraint. If the value is NULL then a name will be generated from the table & field name."
|
|
403
439
|
]
|
|
404
440
|
}
|
|
405
441
|
snippet-*/
|
|
406
|
-
CREATE OR REPLACE FUNCTION ax_define.define_deferred_unique_constraint(fieldName text, tableName text, schemaName text) RETURNS void
|
|
442
|
+
CREATE OR REPLACE FUNCTION ax_define.define_deferred_unique_constraint(fieldName text, tableName text, schemaName text, constraintName text default NULL) RETURNS void
|
|
407
443
|
LANGUAGE plpgsql
|
|
408
444
|
AS $$
|
|
409
445
|
BEGIN
|
|
410
|
-
|
|
411
|
-
|
|
446
|
+
SELECT COALESCE(constraintName, tableName || '_' || fieldName || '_is_unique') INTO constraintName;
|
|
447
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
448
|
+
PERFORM ax_define.drop_unique_constraint(fieldName, tableName, schemaName, constraintName);
|
|
449
|
+
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' || constraintName || ' UNIQUE (' || fieldName || ') DEFERRABLE INITIALLY DEFERRED;';
|
|
412
450
|
END;
|
|
413
451
|
$$;
|
|
414
452
|
|
|
@@ -418,15 +456,18 @@ $$;
|
|
|
418
456
|
"SELECT ax_define.drop_like_index('${1:column_name}', '${2:table_name}');"
|
|
419
457
|
],
|
|
420
458
|
"description": [
|
|
421
|
-
"Drops a gin_trgm_ops index (for LIKE/ILIKE searches) based on a column
|
|
459
|
+
"Drops a gin_trgm_ops index (for LIKE/ILIKE searches) based on a column.\n",
|
|
460
|
+
"Third (optional) parameter: the unique name for the index. If the value is NULL then the name will be generated from the table & field name."
|
|
422
461
|
]
|
|
423
462
|
}
|
|
424
463
|
snippet-*/
|
|
425
|
-
CREATE OR REPLACE FUNCTION ax_define.drop_like_index(fieldName text, tableName text) RETURNS void
|
|
464
|
+
CREATE OR REPLACE FUNCTION ax_define.drop_like_index(fieldName text, tableName text, indexName text default NULL) RETURNS void
|
|
426
465
|
LANGUAGE plpgsql
|
|
427
466
|
AS $$
|
|
428
467
|
BEGIN
|
|
429
|
-
|
|
468
|
+
SELECT COALESCE(indexName, 'idx_trgm_' || tableName || '_' || fieldName) INTO indexName;
|
|
469
|
+
PERFORM ax_utils.validate_identifier_length(indexName, 'If the auto-generated name is too long then an "indexName" argument must be provided.');
|
|
470
|
+
EXECUTE 'DROP INDEX IF EXISTS ' || indexName || ' cascade;';
|
|
430
471
|
END;
|
|
431
472
|
$$;
|
|
432
473
|
|
|
@@ -438,16 +479,19 @@ $$;
|
|
|
438
479
|
"description": [
|
|
439
480
|
"Defines a gin_trgm_ops index (for LIKE/ILIKE searches) on a column.",
|
|
440
481
|
"Read more here: https://niallburkley.com/blog/index-columns-for-like-in-postgres/\n",
|
|
441
|
-
"
|
|
482
|
+
"Fourth (optional) parameter: a unique name for the index. If the value is NULL then a name will be generated from the table & field name."
|
|
442
483
|
]
|
|
443
484
|
}
|
|
444
485
|
snippet-*/
|
|
445
|
-
|
|
486
|
+
|
|
487
|
+
CREATE OR REPLACE FUNCTION ax_define.define_like_index(fieldName text, tableName text, schemaName text, indexName text default NULL) RETURNS void
|
|
446
488
|
LANGUAGE plpgsql
|
|
447
489
|
AS $$
|
|
448
490
|
BEGIN
|
|
449
|
-
|
|
450
|
-
|
|
491
|
+
SELECT COALESCE(indexName, 'idx_trgm_' || tableName || '_' || fieldName) INTO indexName;
|
|
492
|
+
PERFORM ax_utils.validate_identifier_length(indexName, 'If the auto-generated name is too long then an "indexName" argument must be provided.');
|
|
493
|
+
PERFORM ax_define.drop_like_index(fieldName, tableName, indexName);
|
|
494
|
+
EXECUTE 'CREATE INDEX ' || indexName || ' ON ' || schemaName || '.' || tableName || ' USING gin (' || fieldName || ' gin_trgm_ops);';
|
|
451
495
|
END;
|
|
452
496
|
$$;
|
|
453
497
|
|
|
@@ -576,7 +620,8 @@ $$;
|
|
|
576
620
|
"Also defines triggers that populate these columns on row create/update.\n",
|
|
577
621
|
"The third parameter is a placeholder for the default username value.",
|
|
578
622
|
"Please make sure that this placeholder is defined in the 'graphile-migrate' settings object,",
|
|
579
|
-
"in the placeholders property with the same name e.g. ':DEFAULT_USERNAME'
|
|
623
|
+
"in the placeholders property with the same name e.g. ':DEFAULT_USERNAME'.\n",
|
|
624
|
+
"N.B! The trigger functions are not compatible with JSON columns. Use JSONB instead."
|
|
580
625
|
]
|
|
581
626
|
}
|
|
582
627
|
snippet-*/
|
|
@@ -863,7 +908,8 @@ $$;
|
|
|
863
908
|
"If the column does not exist - it is created with type text.\n",
|
|
864
909
|
"The sixth parameter is optional. Do not define if the column already exists or if the new column should not have a default value.",
|
|
865
910
|
"The seventh parameter is optional and decides if the column should be required or optional.",
|
|
866
|
-
"The default value is 'NOT NULL'. Use 'NULL' if the new column should be optional."
|
|
911
|
+
"The default value is 'NOT NULL'. Use 'NULL' if the new column should be optional.",
|
|
912
|
+
"The eighth parameter is optional: a unique name for the generated constraint. If the value is NULL then a name will be generated from the table & column name."
|
|
867
913
|
]
|
|
868
914
|
}
|
|
869
915
|
snippet-*/
|
|
@@ -874,12 +920,14 @@ CREATE OR REPLACE FUNCTION ax_define.bind_enum_schema_table_to_table(
|
|
|
874
920
|
enumName text, -- 'example_status'
|
|
875
921
|
enumSchemaName text, -- 'app_public'
|
|
876
922
|
defaultEnumValue text default '', -- 'DASH'
|
|
877
|
-
notNullOptions text default 'NOT NULL' -- 'NOT NULL'
|
|
923
|
+
notNullOptions text default 'NOT NULL', -- 'NOT NULL'
|
|
924
|
+
constraintName text default NULL
|
|
878
925
|
) RETURNS void LANGUAGE plpgsql AS $$
|
|
879
926
|
DECLARE
|
|
880
|
-
fk_constraint_name TEXT = tableName || '_' || columnName || '_fkey';
|
|
881
927
|
default_setting TEXT = '';
|
|
882
928
|
BEGIN
|
|
929
|
+
SELECT COALESCE(constraintName, tableName || '_' || columnName || '_fkey') INTO constraintName;
|
|
930
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
883
931
|
IF NOT coalesce(defaultEnumValue, '') = '' THEN
|
|
884
932
|
default_setting = 'DEFAULT ''' || defaultEnumValue || '''::text';
|
|
885
933
|
END IF;
|
|
@@ -888,7 +936,7 @@ BEGIN
|
|
|
888
936
|
END IF;
|
|
889
937
|
|
|
890
938
|
-- Set the column that uses enum value as a foreign key
|
|
891
|
-
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' ||
|
|
939
|
+
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' || constraintName || ' FOREIGN KEY ('|| columnName ||') REFERENCES ' || enumSchemaName || '.' || enumName || '(value);';
|
|
892
940
|
END;
|
|
893
941
|
$$;
|
|
894
942
|
|
|
@@ -1012,3 +1060,117 @@ BEGIN
|
|
|
1012
1060
|
|
|
1013
1061
|
END;
|
|
1014
1062
|
$function$;
|
|
1063
|
+
|
|
1064
|
+
/*-snippet
|
|
1065
|
+
{
|
|
1066
|
+
"body": [
|
|
1067
|
+
"SELECT ax_define.drop_timestamp_propagation('${1:fk_column}', '${2:table_name}', '${3:app_public}', '${4:id}', '${5:parent_table}', '${6:app_public}');"
|
|
1068
|
+
],
|
|
1069
|
+
"description": [
|
|
1070
|
+
"Removes a function and trigger created by 'ax_define.define_timestamp_propagation' in an idempotent way.\n",
|
|
1071
|
+
"The call signature mirrors 'ax_define.define_timestamp_propagation' and should be applied with the same arguments.\n",
|
|
1072
|
+
"Seventh (optional) parameter: the unique name for the generated function. If the value is NULL then the name will be generated from the table names."
|
|
1073
|
+
]
|
|
1074
|
+
}
|
|
1075
|
+
snippet-*/
|
|
1076
|
+
CREATE OR REPLACE FUNCTION ax_define.drop_timestamp_propagation(
|
|
1077
|
+
idColumnName text,
|
|
1078
|
+
tableName text,
|
|
1079
|
+
schemaName text,
|
|
1080
|
+
foreignIdColumnName text,
|
|
1081
|
+
foreignTableName text,
|
|
1082
|
+
foreignSchemaName text,
|
|
1083
|
+
functionName text default NULL
|
|
1084
|
+
) RETURNS void
|
|
1085
|
+
LANGUAGE plpgsql
|
|
1086
|
+
AS $$
|
|
1087
|
+
BEGIN
|
|
1088
|
+
SELECT COALESCE(functionName, 'tg_' || tableName || '__' || foreignTableName || '_ts_propagation') INTO functionName;
|
|
1089
|
+
PERFORM ax_utils.validate_identifier_length(functionName, 'If the auto-generated name is too long then a "functionName" argument must be provided.');
|
|
1090
|
+
EXECUTE 'DROP TRIGGER IF EXISTS _200_propogate_timestamps on ' || schemaName || '.' || tableName;
|
|
1091
|
+
EXECUTE 'DROP FUNCTION IF EXISTS ' || schemaName || '.' || functionName || '()';
|
|
1092
|
+
END;
|
|
1093
|
+
$$;
|
|
1094
|
+
|
|
1095
|
+
/*-snippet
|
|
1096
|
+
{
|
|
1097
|
+
"body": [
|
|
1098
|
+
"SELECT ax_define.define_timestamp_propagation('${1:fk_column}', '${2:table_name}', '${3:app_public}', '${4:id}', '${5:parent_table}', '${6:app_public}');"
|
|
1099
|
+
],
|
|
1100
|
+
"description": [
|
|
1101
|
+
"Defines a function and trigger to propagate 'updated_date' changes to related entities.\n",
|
|
1102
|
+
"Propagation can trigger 'UPDATED' triggers on the target entity including chained timestamp propagation.\n",
|
|
1103
|
+
"First parameter: a column name from the \"table_name\" table that the trigger should be associated with.",
|
|
1104
|
+
"Fourth parameter: a column name from the \"parent_table\" that 'updated_date' should be propagated to.",
|
|
1105
|
+
"Seventh (optional) parameter: a unique name for the generated function. If the value is NULL then a name will be generated from the table names.\n",
|
|
1106
|
+
"NB!. This function uses SECURITY DEFINER permission. It will always be executed as DB_OWNER."
|
|
1107
|
+
]
|
|
1108
|
+
}
|
|
1109
|
+
snippet-*/
|
|
1110
|
+
CREATE OR REPLACE FUNCTION ax_define.define_timestamp_propagation(
|
|
1111
|
+
idColumnName text,
|
|
1112
|
+
tableName text,
|
|
1113
|
+
schemaName text,
|
|
1114
|
+
foreignIdColumnName text,
|
|
1115
|
+
foreignTableName text,
|
|
1116
|
+
foreignSchemaName text,
|
|
1117
|
+
functionName text default NULL
|
|
1118
|
+
) RETURNS void
|
|
1119
|
+
LANGUAGE plpgsql
|
|
1120
|
+
AS $a$
|
|
1121
|
+
BEGIN
|
|
1122
|
+
SELECT COALESCE(functionName, 'tg_' || tableName || '__' || foreignTableName || '_ts_propagation') INTO functionName;
|
|
1123
|
+
PERFORM ax_utils.validate_identifier_length(functionName, 'If the auto-generated name is too long then a "functionName" argument must be provided.');
|
|
1124
|
+
-- Set updated_date=now() on the foreign table. This will propogate UPDATE triggers.
|
|
1125
|
+
--
|
|
1126
|
+
-- A new function is created for each table to do this.
|
|
1127
|
+
-- It *may* be possible to use a stock function with trigger arguments but its not easy as NEW and OLD cannot be accessed with dynamic column names. A possible
|
|
1128
|
+
-- solution to that is described here: https://itectec.com/database/postgresql-assignment-of-a-column-with-dynamic-column-name/. But even there the advise is
|
|
1129
|
+
-- to: "Just write a new trigger function for each table. Less hassle, better performance. Byte the bullet on code duplication:"
|
|
1130
|
+
--
|
|
1131
|
+
-- WARNING: This function uses "SECURITY DEFINER". This is required to ensure that update to the target table is allowed. This means that the function is
|
|
1132
|
+
-- executed with role "DB_OWNER". Any propogated trigger functions will also execute with role "DB_OWNER".
|
|
1133
|
+
EXECUTE '
|
|
1134
|
+
CREATE OR REPLACE FUNCTION ' || schemaName || '.' || functionName || '() RETURNS TRIGGER
|
|
1135
|
+
LANGUAGE plpgsql
|
|
1136
|
+
SECURITY DEFINER
|
|
1137
|
+
SET search_path = pg_temp
|
|
1138
|
+
AS $b$
|
|
1139
|
+
BEGIN
|
|
1140
|
+
-- if updated_date exists on source and its not changed, then exit here
|
|
1141
|
+
-- this prevents multiple propogation from different children in one transaction
|
|
1142
|
+
if (to_jsonb(NEW) ? ''updated_date'') THEN
|
|
1143
|
+
-- must be a seperate condition to prevent exception if column does not exist
|
|
1144
|
+
IF (OLD.updated_date = NEW.updated_date) THEN
|
|
1145
|
+
RETURN NULL;
|
|
1146
|
+
END IF;
|
|
1147
|
+
END IF;
|
|
1148
|
+
|
|
1149
|
+
-- UPDATE (where relationship is unchanged, or changed to another entity in which case a change is triggered on both the old and new relation)
|
|
1150
|
+
IF (OLD.' || idColumnName || ' IS NOT NULL AND NEW.' || idColumnName || ' IS NOT NULL) THEN
|
|
1151
|
+
UPDATE ' || foreignSchemaName || '.' || foreignTableName || ' SET updated_date=now()
|
|
1152
|
+
WHERE (' || foreignIdColumnName || ' = OLD.' || idColumnName || ') OR (' || foreignIdColumnName || ' = NEW.' || idColumnName || ');
|
|
1153
|
+
|
|
1154
|
+
-- INSERT (or UPDATE which sets nullable relationship)
|
|
1155
|
+
ELSIF (NEW.' || idColumnName || ' IS NOT NULL) THEN
|
|
1156
|
+
UPDATE ' || foreignSchemaName || '.' || foreignTableName || ' SET updated_date=now()
|
|
1157
|
+
WHERE ' || foreignIdColumnName || ' = NEW.' || idColumnName || ';
|
|
1158
|
+
|
|
1159
|
+
-- DELETE (or UPDATE which removes nullable relationship)
|
|
1160
|
+
ELSIF (OLD.' || idColumnName || ' IS NOT NULL) THEN
|
|
1161
|
+
UPDATE ' || foreignSchemaName || '.' || foreignTableName || ' SET updated_date=now()
|
|
1162
|
+
WHERE ' || foreignIdColumnName || ' = OLD.' || idColumnName || ';
|
|
1163
|
+
|
|
1164
|
+
END IF;
|
|
1165
|
+
RETURN NULL;
|
|
1166
|
+
END $b$;
|
|
1167
|
+
REVOKE EXECUTE ON FUNCTION ' || schemaName || '.' || functionName || '() FROM public;
|
|
1168
|
+
';
|
|
1169
|
+
|
|
1170
|
+
-- Function runs *AFTER* INSERT, UPDATE, DELETE. Propogated queries can still raise an error and rollback the transaction
|
|
1171
|
+
EXECUTE 'DROP TRIGGER IF EXISTS _200_propogate_timestamps on ' || schemaName || '.' || tableName;
|
|
1172
|
+
EXECUTE 'CREATE trigger _200_propogate_timestamps
|
|
1173
|
+
AFTER INSERT OR UPDATE OR DELETE ON ' || schemaName || '.' || tableName || '
|
|
1174
|
+
FOR EACH ROW EXECUTE PROCEDURE ' || schemaName || '.' || functionName || '();';
|
|
1175
|
+
END;
|
|
1176
|
+
$a$;
|
|
@@ -21,9 +21,7 @@ CREATE OR REPLACE FUNCTION ax_define.define_multitenancy_on_table(
|
|
|
21
21
|
AS $$
|
|
22
22
|
BEGIN
|
|
23
23
|
SELECT COALESCE(constraintName, tableName || '_unique_id_tenant_environment') INTO constraintName;
|
|
24
|
-
|
|
25
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.define_multitenancy_on_table". Constraint name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then a "constraintName" argument must be provided.', 'SETUP', constraintName);
|
|
26
|
-
END IF;
|
|
24
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
27
25
|
EXECUTE '
|
|
28
26
|
DO $do$ BEGIN
|
|
29
27
|
BEGIN
|
|
@@ -85,9 +83,7 @@ CREATE OR REPLACE FUNCTION ax_define.define_multitenancy_relation(
|
|
|
85
83
|
AS $$
|
|
86
84
|
BEGIN
|
|
87
85
|
SELECT COALESCE(constraintName, 'multitenancy_relation_' || tableName || '_to_' || foreignTableName) INTO constraintName;
|
|
88
|
-
|
|
89
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.define_multitenancy_relation". Constraint name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then a "constraintName" argument must be provided.', 'SETUP', constraintName);
|
|
90
|
-
END IF;
|
|
86
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
91
87
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' DROP CONSTRAINT IF EXISTS ' || constraintName || ';';
|
|
92
88
|
EXECUTE '
|
|
93
89
|
ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' || constraintName || '
|
|
@@ -112,9 +108,7 @@ CREATE OR REPLACE FUNCTION ax_define.define_multitenancy_unique_field_constraint
|
|
|
112
108
|
AS $$
|
|
113
109
|
BEGIN
|
|
114
110
|
SELECT COALESCE(constraintName, tableName || '_' || fieldName || '_is_unique') INTO constraintName;
|
|
115
|
-
|
|
116
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.define_multitenancy_unique_constraint". Constraint name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then a "constraintName" argument must be provided.', 'SETUP', constraintName);
|
|
117
|
-
END IF;
|
|
111
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
118
112
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' DROP CONSTRAINT IF EXISTS ' || constraintName || ';';
|
|
119
113
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' || constraintName || ' UNIQUE (' || fieldName || ', tenant_id, environment_id);';
|
|
120
114
|
END;
|
|
@@ -139,9 +133,7 @@ DECLARE
|
|
|
139
133
|
fieldList text = array_to_string(array_agg(fieldNames), ', ');
|
|
140
134
|
BEGIN
|
|
141
135
|
SELECT COALESCE(constraintName, tableName || '_' || fieldNamesConcat || '_is_unique') INTO constraintName;
|
|
142
|
-
|
|
143
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.define_multitenancy_multi_unique_constraint". Constraint name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then a "constraintName" argument must be provided.', 'SETUP', constraintName);
|
|
144
|
-
END IF;
|
|
136
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
145
137
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' DROP CONSTRAINT IF EXISTS ' || constraintName || ';';
|
|
146
138
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' || constraintName || ' UNIQUE (' || fieldList || ', tenant_id, environment_id);';
|
|
147
139
|
END;
|
|
@@ -163,9 +155,7 @@ CREATE OR REPLACE FUNCTION ax_define.define_multitenancy_unique_constraint(table
|
|
|
163
155
|
AS $$
|
|
164
156
|
BEGIN
|
|
165
157
|
SELECT COALESCE(constraintName, tableName || '_multitenancy_entry_is_unique') INTO constraintName;
|
|
166
|
-
|
|
167
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.define_multitenancy_unique_constraint". Constraint name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then a "constraintName" argument must be provided.', 'SETUP', constraintName);
|
|
168
|
-
END IF;
|
|
158
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
169
159
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' DROP CONSTRAINT IF EXISTS ' || constraintName || ';';
|
|
170
160
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' || constraintName || ' UNIQUE (tenant_id, environment_id);';
|
|
171
161
|
END;
|
|
@@ -188,9 +178,7 @@ CREATE OR REPLACE FUNCTION ax_define.define_multitenancy_primary_key(fieldNames
|
|
|
188
178
|
AS $$
|
|
189
179
|
BEGIN
|
|
190
180
|
SELECT COALESCE(constraintName, tableName || '_pkey') INTO constraintName;
|
|
191
|
-
|
|
192
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.define_multitenancy_primary_key". Constraint name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then a "constraintName" argument must be provided.', 'SETUP', constraintName);
|
|
193
|
-
END IF;
|
|
181
|
+
PERFORM ax_utils.validate_identifier_length(constraintName, 'If the auto-generated name is too long then a "constraintName" argument must be provided.');
|
|
194
182
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' DROP CONSTRAINT IF EXISTS ' || constraintName || ';';
|
|
195
183
|
EXECUTE 'ALTER TABLE ' || schemaName || '.' || tableName || ' ADD CONSTRAINT ' || constraintName || ' PRIMARY KEY (' || fieldNames || ',tenant_id, environment_id);';
|
|
196
184
|
END;
|
|
@@ -212,9 +200,7 @@ CREATE OR REPLACE FUNCTION ax_define.define_multitenancy_index(fieldName text, t
|
|
|
212
200
|
AS $$
|
|
213
201
|
BEGIN
|
|
214
202
|
SELECT COALESCE(indexName, 'idx_' || tableName || '_' || fieldName || '_mt') INTO indexName;
|
|
215
|
-
|
|
216
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.define_multitenancy_index". Index name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then an "indexName" argument must be provided.', 'SETUP', indexName);
|
|
217
|
-
END IF;
|
|
203
|
+
PERFORM ax_utils.validate_identifier_length(indexName, 'If the auto-generated name is too long then an "indexName" argument must be provided.');
|
|
218
204
|
EXECUTE 'DROP INDEX IF EXISTS ' || indexName || ' cascade;';
|
|
219
205
|
EXECUTE 'CREATE INDEX ' || indexName || ' ON ' || schemaName || '.' || tableName || ' (' || fieldName || ', tenant_id, environment_id);';
|
|
220
206
|
END;
|
|
@@ -285,9 +271,7 @@ CREATE OR REPLACE FUNCTION ax_define.drop_multitenancy_timestamp_propagation(
|
|
|
285
271
|
AS $$
|
|
286
272
|
BEGIN
|
|
287
273
|
SELECT COALESCE(functionName, 'tg_' || tableName || '__' || foreignTableName || '_mt_ts_propagation') INTO functionName;
|
|
288
|
-
|
|
289
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.drop_multitenancy_timestamp_propagation". Function name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then a "functionName" argument must be provided.', 'SETUP', functionName);
|
|
290
|
-
END IF;
|
|
274
|
+
PERFORM ax_utils.validate_identifier_length(functionName, 'If the auto-generated name is too long then a "functionName" argument must be provided.');
|
|
291
275
|
EXECUTE 'DROP TRIGGER IF EXISTS _200_propogate_timestamps on ' || schemaName || '.' || tableName;
|
|
292
276
|
EXECUTE 'DROP FUNCTION IF EXISTS ' || schemaName || '.' || functionName || '()';
|
|
293
277
|
END;
|
|
@@ -304,7 +288,8 @@ $$;
|
|
|
304
288
|
"The call signature mirrors ax_define.define_multitenancy_relation and can generally be applied with the same arguments.\n",
|
|
305
289
|
"First parameter: a column name from the \"table_name\" table that the trigger should be associated with.",
|
|
306
290
|
"Fourth parameter: a column name from the \"parent_table\" that 'updated_date' should be propagated to.",
|
|
307
|
-
"Seventh (optional) parameter: a unique name for the generated function. If the value is NULL then a name will be generated from the table names
|
|
291
|
+
"Seventh (optional) parameter: a unique name for the generated function. If the value is NULL then a name will be generated from the table names.\n",
|
|
292
|
+
"NB!. This function uses SECURITY DEFINER permission. It will always be executed as DB_OWNER."
|
|
308
293
|
]
|
|
309
294
|
}
|
|
310
295
|
snippet-*/
|
|
@@ -321,9 +306,7 @@ CREATE OR REPLACE FUNCTION ax_define.define_multitenancy_timestamp_propagation(
|
|
|
321
306
|
AS $a$
|
|
322
307
|
BEGIN
|
|
323
308
|
SELECT COALESCE(functionName, 'tg_' || tableName || '__' || foreignTableName || '_mt_ts_propagation') INTO functionName;
|
|
324
|
-
|
|
325
|
-
perform ax_utils.raise_error('Invalid parameters provided to "ax_define.define_multitenancy_timestamp_propagation". Function name "%s" exceeds 63 bytes. If the auto-generated name exceeds 63 bytes then a "functionName" argument must be provided.', 'SETUP', functionName);
|
|
326
|
-
END IF;
|
|
309
|
+
PERFORM ax_utils.validate_identifier_length(functionName, 'If the auto-generated name is too long then a "functionName" argument must be provided.');
|
|
327
310
|
-- Set updated_date=now() on the foreign table. This will propogate UPDATE triggers.
|
|
328
311
|
--
|
|
329
312
|
-- A new function is created for each table to do this.
|
|
@@ -341,18 +324,26 @@ BEGIN
|
|
|
341
324
|
SET search_path = pg_temp
|
|
342
325
|
AS $b$
|
|
343
326
|
BEGIN
|
|
344
|
-
if
|
|
345
|
-
|
|
327
|
+
-- if updated_date exists on source and its not changed, then exit here
|
|
328
|
+
-- this prevents multiple propogation from different children in one transaction
|
|
329
|
+
if (to_jsonb(NEW) ? ''updated_date'') THEN
|
|
330
|
+
-- must be a seperate condition to prevent exception if column does not exist
|
|
331
|
+
IF (OLD.updated_date = NEW.updated_date) THEN
|
|
332
|
+
RETURN NULL;
|
|
333
|
+
END IF;
|
|
346
334
|
END IF;
|
|
335
|
+
|
|
347
336
|
-- UPDATE (where relationship is unchanged, or changed to another entity in which case a change is triggered on both the old and new relation)
|
|
348
337
|
IF (OLD.' || idColumnName || ' IS NOT NULL AND NEW.' || idColumnName || ' IS NOT NULL) THEN
|
|
349
338
|
UPDATE ' || foreignSchemaName || '.' || foreignTableName || ' SET updated_date=now()
|
|
350
339
|
WHERE (' || foreignIdColumnName || ' = OLD.' || idColumnName || ' AND tenant_id = OLD.tenant_id AND environment_id = OLD.environment_id)
|
|
351
340
|
OR (' || foreignIdColumnName || ' = NEW.' || idColumnName || ' AND tenant_id = NEW.tenant_id AND environment_id = NEW.environment_id);
|
|
341
|
+
|
|
352
342
|
-- INSERT (or UPDATE which sets nullable relationship)
|
|
353
343
|
ELSIF (NEW.' || idColumnName || ' IS NOT NULL) THEN
|
|
354
344
|
UPDATE ' || foreignSchemaName || '.' || foreignTableName || ' SET updated_date=now()
|
|
355
345
|
WHERE ' || foreignIdColumnName || ' = NEW.' || idColumnName || ' AND tenant_id = NEW.tenant_id AND environment_id = NEW.environment_id;
|
|
346
|
+
|
|
356
347
|
-- DELETE (or UPDATE which removes nullable relationship)
|
|
357
348
|
ELSIF (OLD.' || idColumnName || ' IS NOT NULL) THEN
|
|
358
349
|
UPDATE ' || foreignSchemaName || '.' || foreignTableName || ' SET updated_date=now()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-db-common",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "This library encapsulates database-related functionality to develop Mosaic based services.",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "2d8cec2958a2cc6f80ee764d21e66b4fe1729bb5"
|
|
52
52
|
}
|