@tricoteuses/senat 2.22.16 → 2.23.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/README.md +168 -0
- package/lib/aggregates.d.ts +52 -0
- package/lib/aggregates.js +930 -0
- package/lib/aggregates.mjs +713 -0
- package/lib/aggregates.ts +833 -0
- package/lib/config.d.ts +10 -0
- package/lib/config.js +16 -0
- package/lib/config.mjs +16 -0
- package/lib/config.ts +26 -0
- package/lib/databases.d.ts +2 -0
- package/lib/databases.js +26 -0
- package/lib/databases.mjs +57 -0
- package/lib/databases.ts +71 -0
- package/lib/datasets.d.ts +34 -0
- package/lib/datasets.js +233 -0
- package/lib/datasets.mjs +78 -0
- package/lib/datasets.ts +118 -0
- package/lib/fields.d.ts +10 -0
- package/lib/fields.js +68 -0
- package/lib/fields.mjs +22 -0
- package/lib/fields.ts +29 -0
- package/lib/git.d.ts +26 -0
- package/lib/git.js +167 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +1 -0
- package/lib/index.mjs +7 -0
- package/lib/index.ts +64 -0
- package/lib/inserters.d.ts +98 -0
- package/lib/inserters.js +500 -0
- package/lib/inserters.mjs +360 -0
- package/lib/inserters.ts +521 -0
- package/lib/legislatures.json +38 -0
- package/lib/loaders.d.ts +58 -0
- package/lib/loaders.js +286 -0
- package/lib/loaders.mjs +158 -0
- package/lib/loaders.ts +271 -0
- package/lib/model/agenda.d.ts +6 -0
- package/lib/model/agenda.js +148 -0
- package/lib/model/ameli.d.ts +51 -0
- package/lib/model/ameli.js +149 -0
- package/lib/model/ameli.mjs +84 -0
- package/lib/model/ameli.ts +100 -0
- package/lib/model/commission.d.ts +18 -0
- package/lib/model/commission.js +269 -0
- package/lib/model/debats.d.ts +67 -0
- package/lib/model/debats.js +95 -0
- package/lib/model/debats.mjs +43 -0
- package/lib/model/debats.ts +68 -0
- package/lib/model/documents.d.ts +12 -0
- package/lib/model/documents.js +151 -0
- package/lib/model/dosleg.d.ts +7 -0
- package/lib/model/dosleg.js +326 -0
- package/lib/model/dosleg.mjs +196 -0
- package/lib/model/dosleg.ts +240 -0
- package/lib/model/index.d.ts +7 -0
- package/lib/model/index.js +7 -0
- package/lib/model/index.mjs +5 -0
- package/lib/model/index.ts +15 -0
- package/lib/model/questions.d.ts +45 -0
- package/lib/model/questions.js +89 -0
- package/lib/model/questions.mjs +71 -0
- package/lib/model/questions.ts +93 -0
- package/lib/model/scrutins.d.ts +13 -0
- package/lib/model/scrutins.js +114 -0
- package/lib/model/seance.d.ts +3 -0
- package/lib/model/seance.js +267 -0
- package/lib/model/sens.d.ts +146 -0
- package/lib/model/sens.js +454 -0
- package/lib/model/sens.mjs +415 -0
- package/lib/model/sens.ts +516 -0
- package/lib/model/texte.d.ts +7 -0
- package/lib/model/texte.js +256 -0
- package/lib/model/texte.mjs +208 -0
- package/lib/model/texte.ts +229 -0
- package/lib/model/util.d.ts +9 -0
- package/lib/model/util.js +38 -0
- package/lib/model/util.mjs +19 -0
- package/lib/model/util.ts +32 -0
- package/lib/parsers/texte.d.ts +7 -0
- package/lib/parsers/texte.js +228 -0
- package/lib/raw_types/ameli.d.ts +914 -0
- package/lib/raw_types/ameli.js +5 -0
- package/lib/raw_types/ameli.mjs +163 -0
- package/lib/raw_types/debats.d.ts +207 -0
- package/lib/raw_types/debats.js +5 -0
- package/lib/raw_types/debats.mjs +58 -0
- package/lib/raw_types/dosleg.d.ts +1619 -0
- package/lib/raw_types/dosleg.js +5 -0
- package/lib/raw_types/dosleg.mjs +438 -0
- package/lib/raw_types/questions.d.ts +419 -0
- package/lib/raw_types/questions.js +5 -0
- package/lib/raw_types/questions.mjs +11 -0
- package/lib/raw_types/senat.d.ts +11368 -0
- package/lib/raw_types/senat.js +5 -0
- package/lib/raw_types/sens.d.ts +8248 -0
- package/lib/raw_types/sens.js +5 -0
- package/lib/raw_types/sens.mjs +508 -0
- package/lib/raw_types_kysely/ameli.d.ts +915 -0
- package/lib/raw_types_kysely/ameli.js +7 -0
- package/lib/raw_types_kysely/ameli.mjs +5 -0
- package/lib/raw_types_kysely/ameli.ts +951 -0
- package/lib/raw_types_kysely/debats.d.ts +207 -0
- package/lib/raw_types_kysely/debats.js +7 -0
- package/lib/raw_types_kysely/debats.mjs +5 -0
- package/lib/raw_types_kysely/debats.ts +222 -0
- package/lib/raw_types_kysely/dosleg.d.ts +3532 -0
- package/lib/raw_types_kysely/dosleg.js +7 -0
- package/lib/raw_types_kysely/dosleg.mjs +5 -0
- package/lib/raw_types_kysely/dosleg.ts +3621 -0
- package/lib/raw_types_kysely/questions.d.ts +414 -0
- package/lib/raw_types_kysely/questions.js +7 -0
- package/lib/raw_types_kysely/questions.mjs +5 -0
- package/lib/raw_types_kysely/questions.ts +426 -0
- package/lib/raw_types_kysely/sens.d.ts +4394 -0
- package/lib/raw_types_kysely/sens.js +7 -0
- package/lib/raw_types_kysely/sens.mjs +5 -0
- package/lib/raw_types_kysely/sens.ts +4499 -0
- package/lib/raw_types_schemats/ameli.d.ts +539 -0
- package/lib/raw_types_schemats/ameli.js +2 -0
- package/lib/raw_types_schemats/ameli.mjs +2 -0
- package/lib/raw_types_schemats/ameli.ts +601 -0
- package/lib/raw_types_schemats/debats.d.ts +127 -0
- package/lib/raw_types_schemats/debats.js +2 -0
- package/lib/raw_types_schemats/debats.mjs +2 -0
- package/lib/raw_types_schemats/debats.ts +145 -0
- package/lib/raw_types_schemats/dosleg.d.ts +977 -0
- package/lib/raw_types_schemats/dosleg.js +2 -0
- package/lib/raw_types_schemats/dosleg.mjs +2 -0
- package/lib/raw_types_schemats/dosleg.ts +2193 -0
- package/lib/raw_types_schemats/questions.d.ts +235 -0
- package/lib/raw_types_schemats/questions.js +2 -0
- package/lib/raw_types_schemats/questions.mjs +2 -0
- package/lib/raw_types_schemats/questions.ts +249 -0
- package/lib/raw_types_schemats/sens.d.ts +6915 -0
- package/lib/raw_types_schemats/sens.js +2 -0
- package/lib/raw_types_schemats/sens.mjs +2 -0
- package/lib/raw_types_schemats/sens.ts +2907 -0
- package/lib/scripts/convert_data.d.ts +1 -0
- package/lib/scripts/convert_data.js +354 -0
- package/lib/scripts/convert_data.mjs +181 -0
- package/lib/scripts/convert_data.ts +243 -0
- package/lib/scripts/data-download.d.ts +1 -0
- package/lib/scripts/data-download.js +12 -0
- package/lib/scripts/datautil.d.ts +8 -0
- package/lib/scripts/datautil.js +34 -0
- package/lib/scripts/datautil.mjs +16 -0
- package/lib/scripts/datautil.ts +19 -0
- package/lib/scripts/images/transparent_150x192.jpg +0 -0
- package/lib/scripts/images/transparent_155x225.jpg +0 -0
- package/lib/scripts/parse_textes.d.ts +1 -0
- package/lib/scripts/parse_textes.js +44 -0
- package/lib/scripts/parse_textes.mjs +46 -0
- package/lib/scripts/parse_textes.ts +65 -0
- package/lib/scripts/retrieve_agenda.d.ts +1 -0
- package/lib/scripts/retrieve_agenda.js +132 -0
- package/lib/scripts/retrieve_cr_commission.d.ts +1 -0
- package/lib/scripts/retrieve_cr_commission.js +364 -0
- package/lib/scripts/retrieve_cr_seance.d.ts +6 -0
- package/lib/scripts/retrieve_cr_seance.js +347 -0
- package/lib/scripts/retrieve_documents.d.ts +3 -0
- package/lib/scripts/retrieve_documents.js +219 -0
- package/lib/scripts/retrieve_documents.mjs +249 -0
- package/lib/scripts/retrieve_documents.ts +298 -0
- package/lib/scripts/retrieve_open_data.d.ts +1 -0
- package/lib/scripts/retrieve_open_data.js +315 -0
- package/lib/scripts/retrieve_open_data.mjs +217 -0
- package/lib/scripts/retrieve_open_data.ts +268 -0
- package/lib/scripts/retrieve_senateurs_photos.d.ts +1 -0
- package/lib/scripts/retrieve_senateurs_photos.js +147 -0
- package/lib/scripts/retrieve_senateurs_photos.mjs +147 -0
- package/lib/scripts/retrieve_senateurs_photos.ts +177 -0
- package/lib/scripts/retrieve_videos.d.ts +1 -0
- package/lib/scripts/retrieve_videos.js +461 -0
- package/lib/scripts/shared/cli_helpers.d.ts +95 -0
- package/lib/scripts/shared/cli_helpers.js +91 -0
- package/lib/scripts/shared/cli_helpers.ts +36 -0
- package/lib/scripts/shared/util.d.ts +4 -0
- package/lib/scripts/shared/util.js +35 -0
- package/lib/scripts/shared/util.ts +33 -0
- package/lib/scripts/test_iter_load.d.ts +1 -0
- package/lib/scripts/test_iter_load.js +12 -0
- package/lib/src/config.d.ts +22 -0
- package/lib/src/config.js +17 -7
- package/lib/src/conversion_textes.js +5 -1
- package/lib/src/databases.d.ts +2 -1
- package/lib/src/databases_postgres.d.ts +4 -0
- package/lib/src/databases_postgres.js +23 -0
- package/lib/src/datasets.d.ts +4 -0
- package/lib/src/datasets.js +16 -2
- package/lib/src/git.d.ts +1 -0
- package/lib/src/git.js +45 -11
- package/lib/src/loaders.js +10 -4
- package/lib/src/model/agenda.js +2 -2
- package/lib/src/model/ameli.d.ts +64 -52
- package/lib/src/model/ameli.js +147 -145
- package/lib/src/model/ameli_postgres.d.ts +67 -0
- package/lib/src/model/ameli_postgres.js +150 -0
- package/lib/src/model/commission.d.ts +3 -2
- package/lib/src/model/commission.js +2 -2
- package/lib/src/model/debats.d.ts +38 -66
- package/lib/src/model/debats.js +110 -93
- package/lib/src/model/documents.d.ts +32 -12
- package/lib/src/model/documents.js +171 -130
- package/lib/src/model/dosleg.d.ts +142 -5
- package/lib/src/model/dosleg.js +298 -156
- package/lib/src/model/questions.d.ts +54 -45
- package/lib/src/model/questions.js +89 -87
- package/lib/src/model/scrutins.d.ts +48 -13
- package/lib/src/model/scrutins.js +118 -111
- package/lib/src/model/seance.js +3 -3
- package/lib/src/model/sens.d.ts +109 -179
- package/lib/src/model/sens.js +384 -484
- package/lib/src/model/util.d.ts +0 -8
- package/lib/src/model/util.js +0 -23
- package/lib/src/parsers/texte.js +7 -7
- package/lib/src/raw_types_schemats/ameli.d.ts +4 -2
- package/lib/src/raw_types_schemats/debats.d.ts +2 -2
- package/lib/src/raw_types_schemats/dosleg.d.ts +2 -2
- package/lib/src/raw_types_schemats/questions.d.ts +2 -2
- package/lib/src/raw_types_schemats/sens.d.ts +10 -4216
- package/lib/src/scripts/convert_data.js +7 -6
- package/lib/src/scripts/convert_xml_to_html.js +2 -2
- package/lib/src/scripts/data-download.js +3 -2
- package/lib/src/scripts/retrieve_agenda.js +21 -9
- package/lib/src/scripts/retrieve_cr_commission.js +17 -17
- package/lib/src/scripts/retrieve_cr_seance.d.ts +14 -1
- package/lib/src/scripts/retrieve_cr_seance.js +10 -11
- package/lib/src/scripts/retrieve_documents.d.ts +11 -2
- package/lib/src/scripts/retrieve_documents.js +25 -14
- package/lib/src/scripts/retrieve_open_data.js +400 -145
- package/lib/src/scripts/retrieve_senateurs_photos.js +25 -11
- package/lib/src/scripts/retrieve_videos.js +12 -11
- package/lib/src/scripts/shared/cli_helpers.d.ts +1 -6
- package/lib/src/scripts/shared/cli_helpers.js +9 -8
- package/lib/src/scripts/shared/incremental_import_sql.d.ts +2 -0
- package/lib/src/scripts/shared/incremental_import_sql.js +894 -0
- package/lib/src/scripts/shared/prefixed_tables.d.ts +7 -0
- package/lib/src/scripts/shared/prefixed_tables.js +30 -0
- package/lib/src/scripts/shared/schema_version.d.ts +3 -0
- package/lib/src/scripts/shared/schema_version.js +97 -0
- package/lib/src/scripts/shared/staging_import.d.ts +3 -0
- package/lib/src/scripts/shared/staging_import.js +80 -0
- package/lib/src/scripts/shared/staging_metadata_sql.d.ts +1 -0
- package/lib/src/scripts/shared/staging_metadata_sql.js +221 -0
- package/lib/src/scripts/validate_prefixed_tables.d.ts +1 -0
- package/lib/src/scripts/validate_prefixed_tables.js +102 -0
- package/lib/src/types/texte.d.ts +1 -1
- package/lib/src/utils/cr_spliting.d.ts +9 -6
- package/lib/src/utils/cr_spliting.js +6 -101
- package/lib/src/utils/reunion_odj_building.d.ts +7 -3
- package/lib/src/utils/reunion_parsing.d.ts +2 -1
- package/lib/src/utils/reunion_parsing.js +2 -2
- package/lib/src/videos/match.js +8 -5
- package/lib/src/videos/pipeline.d.ts +6 -2
- package/lib/src/videos/pipeline.js +21 -8
- package/lib/src/videos/search.js +6 -2
- package/lib/strings.d.ts +1 -0
- package/lib/strings.js +18 -0
- package/lib/strings.mjs +18 -0
- package/lib/strings.ts +26 -0
- package/lib/tests/incrementalImportSql.test.d.ts +1 -0
- package/lib/tests/incrementalImportSql.test.js +155 -0
- package/lib/tests/prefixedTables.test.d.ts +1 -0
- package/lib/tests/prefixedTables.test.js +29 -0
- package/lib/tests/schemaVersion.test.d.ts +1 -0
- package/lib/tests/schemaVersion.test.js +23 -0
- package/lib/tests/validatePrefixedTables.test.d.ts +1 -0
- package/lib/tests/validatePrefixedTables.test.js +14 -0
- package/lib/types/agenda.d.ts +44 -0
- package/lib/types/agenda.js +1 -0
- package/lib/types/ameli.d.ts +5 -0
- package/lib/types/ameli.js +1 -0
- package/lib/types/ameli.mjs +13 -0
- package/lib/types/ameli.ts +21 -0
- package/lib/types/compte_rendu.d.ts +83 -0
- package/lib/types/compte_rendu.js +1 -0
- package/lib/types/debats.d.ts +2 -0
- package/lib/types/debats.js +1 -0
- package/lib/types/debats.mjs +2 -0
- package/lib/types/debats.ts +6 -0
- package/lib/types/dosleg.d.ts +70 -0
- package/lib/types/dosleg.js +1 -0
- package/lib/types/dosleg.mjs +151 -0
- package/lib/types/dosleg.ts +284 -0
- package/lib/types/questions.d.ts +2 -0
- package/lib/types/questions.js +1 -0
- package/lib/types/questions.mjs +1 -0
- package/lib/types/questions.ts +3 -0
- package/lib/types/sens.d.ts +10 -0
- package/lib/types/sens.js +1 -0
- package/lib/types/sens.mjs +1 -0
- package/lib/types/sens.ts +12 -0
- package/lib/types/sessions.d.ts +5 -0
- package/lib/types/sessions.js +84 -0
- package/lib/types/sessions.mjs +43 -0
- package/lib/types/sessions.ts +42 -0
- package/lib/types/texte.d.ts +74 -0
- package/lib/types/texte.js +16 -0
- package/lib/types/texte.mjs +16 -0
- package/lib/types/texte.ts +76 -0
- package/lib/typings/windows-1252.d.js +2 -0
- package/lib/typings/windows-1252.d.mjs +2 -0
- package/lib/typings/windows-1252.d.ts +11 -0
- package/lib/utils/cr_spliting.d.ts +28 -0
- package/lib/utils/cr_spliting.js +265 -0
- package/lib/utils/date.d.ts +10 -0
- package/lib/utils/date.js +100 -0
- package/lib/utils/nvs-timecode.d.ts +7 -0
- package/lib/utils/nvs-timecode.js +79 -0
- package/lib/utils/reunion_grouping.d.ts +9 -0
- package/lib/utils/reunion_grouping.js +361 -0
- package/lib/utils/reunion_odj_building.d.ts +5 -0
- package/lib/utils/reunion_odj_building.js +154 -0
- package/lib/utils/reunion_parsing.d.ts +23 -0
- package/lib/utils/reunion_parsing.js +209 -0
- package/lib/utils/scoring.d.ts +14 -0
- package/lib/utils/scoring.js +147 -0
- package/lib/utils/string_cleaning.d.ts +7 -0
- package/lib/utils/string_cleaning.js +57 -0
- package/lib/validators/config.d.ts +9 -0
- package/lib/validators/config.js +10 -0
- package/lib/validators/config.mjs +54 -0
- package/lib/validators/config.ts +79 -0
- package/lib/validators/senat.d.ts +0 -0
- package/lib/validators/senat.js +28 -0
- package/lib/validators/senat.mjs +24 -0
- package/lib/validators/senat.ts +26 -0
- package/package.json +6 -10
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
import { prefixedName, senatSchemaName, stagingSchemaName } from "./prefixed_tables";
|
|
2
|
+
function escapeSqlLiteral(value) {
|
|
3
|
+
return value.replace(/'/g, "''");
|
|
4
|
+
}
|
|
5
|
+
function buildConfiguredMergeKeysValues(datasetName, mergeKeys) {
|
|
6
|
+
if (!mergeKeys || Object.keys(mergeKeys).length === 0) {
|
|
7
|
+
return "SELECT NULL::text AS tablename, NULL::text[] AS key_columns WHERE false";
|
|
8
|
+
}
|
|
9
|
+
return Object.entries(mergeKeys)
|
|
10
|
+
.sort(([leftTable], [rightTable]) => leftTable.localeCompare(rightTable))
|
|
11
|
+
.map(([tableName, columns]) => {
|
|
12
|
+
const sqlColumns = columns.map((columnName) => `'${escapeSqlLiteral(columnName)}'`).join(", ");
|
|
13
|
+
return `SELECT '${escapeSqlLiteral(prefixedName(datasetName, tableName))}' AS tablename, ARRAY[${sqlColumns}]::text[] AS key_columns`;
|
|
14
|
+
})
|
|
15
|
+
.join("\nUNION ALL\n");
|
|
16
|
+
}
|
|
17
|
+
function buildConfiguredTableNamesValues(datasetName, tableNames) {
|
|
18
|
+
if (!tableNames || tableNames.length === 0) {
|
|
19
|
+
return "SELECT NULL::text AS tablename WHERE false";
|
|
20
|
+
}
|
|
21
|
+
return [...tableNames]
|
|
22
|
+
.sort((leftTable, rightTable) => leftTable.localeCompare(rightTable))
|
|
23
|
+
.map((tableName) => `SELECT '${escapeSqlLiteral(prefixedName(datasetName, tableName))}' AS tablename`)
|
|
24
|
+
.join("\nUNION ALL\n");
|
|
25
|
+
}
|
|
26
|
+
export function buildNormalizeStagingSchemaSql(datasetName) {
|
|
27
|
+
const stagingSchema = stagingSchemaName(datasetName);
|
|
28
|
+
const prefix = `${datasetName}_`;
|
|
29
|
+
const escapedPrefix = escapeSqlLiteral(prefix);
|
|
30
|
+
const escapedStagingSchema = escapeSqlLiteral(stagingSchema);
|
|
31
|
+
return `
|
|
32
|
+
DO $$
|
|
33
|
+
DECLARE
|
|
34
|
+
relation record;
|
|
35
|
+
table_row record;
|
|
36
|
+
BEGIN
|
|
37
|
+
FOR relation IN
|
|
38
|
+
SELECT c.relname
|
|
39
|
+
FROM pg_class c
|
|
40
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
41
|
+
WHERE n.nspname = '${escapedStagingSchema}'
|
|
42
|
+
AND c.relkind = 'i'
|
|
43
|
+
AND c.relname NOT LIKE '${escapedPrefix}' || '%'
|
|
44
|
+
LOOP
|
|
45
|
+
EXECUTE format(
|
|
46
|
+
'ALTER INDEX %I.%I RENAME TO %I',
|
|
47
|
+
'${stagingSchema}',
|
|
48
|
+
relation.relname,
|
|
49
|
+
'${prefix}' || relation.relname
|
|
50
|
+
);
|
|
51
|
+
END LOOP;
|
|
52
|
+
|
|
53
|
+
FOR relation IN
|
|
54
|
+
SELECT c.relname
|
|
55
|
+
FROM pg_class c
|
|
56
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
57
|
+
WHERE n.nspname = '${escapedStagingSchema}'
|
|
58
|
+
AND c.relkind = 'S'
|
|
59
|
+
AND c.relname NOT LIKE '${escapedPrefix}' || '%'
|
|
60
|
+
LOOP
|
|
61
|
+
EXECUTE format(
|
|
62
|
+
'ALTER SEQUENCE %I.%I RENAME TO %I',
|
|
63
|
+
'${stagingSchema}',
|
|
64
|
+
relation.relname,
|
|
65
|
+
'${prefix}' || relation.relname
|
|
66
|
+
);
|
|
67
|
+
END LOOP;
|
|
68
|
+
|
|
69
|
+
FOR table_row IN
|
|
70
|
+
SELECT tablename
|
|
71
|
+
FROM pg_tables
|
|
72
|
+
WHERE schemaname = '${escapedStagingSchema}'
|
|
73
|
+
ORDER BY tablename
|
|
74
|
+
LOOP
|
|
75
|
+
IF table_row.tablename NOT LIKE '${escapedPrefix}' || '%' THEN
|
|
76
|
+
EXECUTE format(
|
|
77
|
+
'ALTER TABLE %I.%I RENAME TO %I',
|
|
78
|
+
'${stagingSchema}',
|
|
79
|
+
table_row.tablename,
|
|
80
|
+
'${prefix}' || table_row.tablename
|
|
81
|
+
);
|
|
82
|
+
END IF;
|
|
83
|
+
END LOOP;
|
|
84
|
+
END $$;
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
export function buildIncrementalDatasetImportSql(datasetName, dbUser, mergeKeys, rowMultisetMergeTables) {
|
|
88
|
+
const stagingSchema = stagingSchemaName(datasetName);
|
|
89
|
+
const prefix = `${datasetName}_`;
|
|
90
|
+
const escapedPrefix = escapeSqlLiteral(prefix);
|
|
91
|
+
const escapedStagingSchema = escapeSqlLiteral(stagingSchema);
|
|
92
|
+
const configuredMergeKeysValues = buildConfiguredMergeKeysValues(datasetName, mergeKeys);
|
|
93
|
+
const configuredRowMultisetTablesValues = buildConfiguredTableNamesValues(datasetName, rowMultisetMergeTables);
|
|
94
|
+
const stagingRelationsQuery = [
|
|
95
|
+
"SELECT c.relname AS tablename",
|
|
96
|
+
"FROM pg_class c",
|
|
97
|
+
"JOIN pg_namespace n ON n.oid = c.relnamespace",
|
|
98
|
+
`WHERE n.nspname = '${escapedStagingSchema}'`,
|
|
99
|
+
" AND c.relkind IN ('r', 'p', 'f')",
|
|
100
|
+
"ORDER BY c.relname",
|
|
101
|
+
].join("\n");
|
|
102
|
+
return `
|
|
103
|
+
DO $$
|
|
104
|
+
DECLARE
|
|
105
|
+
table_row record;
|
|
106
|
+
column_row record;
|
|
107
|
+
sequence_row record;
|
|
108
|
+
constraint_row record;
|
|
109
|
+
index_row record;
|
|
110
|
+
sequence_owner record;
|
|
111
|
+
sequence_state record;
|
|
112
|
+
staging_default text;
|
|
113
|
+
target_default text;
|
|
114
|
+
column_definition text;
|
|
115
|
+
column_list text;
|
|
116
|
+
staging_column_list text;
|
|
117
|
+
key_columns text[];
|
|
118
|
+
key_schema_name text;
|
|
119
|
+
configured_key_columns text[];
|
|
120
|
+
matching_key_column_count integer;
|
|
121
|
+
use_row_multiset_merge boolean;
|
|
122
|
+
join_condition text;
|
|
123
|
+
update_assignments text;
|
|
124
|
+
change_condition text;
|
|
125
|
+
target_row_signature_expression text;
|
|
126
|
+
staging_row_signature_expression text;
|
|
127
|
+
sync_strategy text;
|
|
128
|
+
staging_table_comment text;
|
|
129
|
+
target_table_comment text;
|
|
130
|
+
BEGIN
|
|
131
|
+
PERFORM set_config('search_path', '${senatSchemaName}, pg_catalog', true);
|
|
132
|
+
|
|
133
|
+
FOR table_row IN
|
|
134
|
+
${stagingRelationsQuery}
|
|
135
|
+
LOOP
|
|
136
|
+
IF NOT EXISTS (
|
|
137
|
+
SELECT 1
|
|
138
|
+
FROM pg_tables
|
|
139
|
+
WHERE schemaname = '${senatSchemaName}'
|
|
140
|
+
AND tablename = table_row.tablename
|
|
141
|
+
) THEN
|
|
142
|
+
EXECUTE format(
|
|
143
|
+
'CREATE TABLE ${senatSchemaName}.%I (LIKE %I.%I INCLUDING IDENTITY INCLUDING GENERATED INCLUDING DEFAULTS)',
|
|
144
|
+
table_row.tablename,
|
|
145
|
+
'${stagingSchema}',
|
|
146
|
+
table_row.tablename
|
|
147
|
+
);
|
|
148
|
+
END IF;
|
|
149
|
+
END LOOP;
|
|
150
|
+
|
|
151
|
+
FOR sequence_row IN
|
|
152
|
+
SELECT
|
|
153
|
+
sequencename,
|
|
154
|
+
data_type,
|
|
155
|
+
start_value,
|
|
156
|
+
min_value,
|
|
157
|
+
max_value,
|
|
158
|
+
increment_by,
|
|
159
|
+
cycle,
|
|
160
|
+
cache_size
|
|
161
|
+
FROM pg_sequences
|
|
162
|
+
WHERE schemaname = '${escapedStagingSchema}'
|
|
163
|
+
ORDER BY sequencename
|
|
164
|
+
LOOP
|
|
165
|
+
IF NOT EXISTS (
|
|
166
|
+
SELECT 1
|
|
167
|
+
FROM pg_class c
|
|
168
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
169
|
+
WHERE n.nspname = '${senatSchemaName}'
|
|
170
|
+
AND c.relkind = 'S'
|
|
171
|
+
AND c.relname = sequence_row.sequencename
|
|
172
|
+
) THEN
|
|
173
|
+
EXECUTE format('CREATE SEQUENCE ${senatSchemaName}.%I', sequence_row.sequencename);
|
|
174
|
+
END IF;
|
|
175
|
+
|
|
176
|
+
EXECUTE format(
|
|
177
|
+
'ALTER SEQUENCE ${senatSchemaName}.%I AS %s INCREMENT BY %s MINVALUE %s MAXVALUE %s START WITH %s CACHE %s %s',
|
|
178
|
+
sequence_row.sequencename,
|
|
179
|
+
sequence_row.data_type,
|
|
180
|
+
sequence_row.increment_by,
|
|
181
|
+
sequence_row.min_value,
|
|
182
|
+
sequence_row.max_value,
|
|
183
|
+
sequence_row.start_value,
|
|
184
|
+
sequence_row.cache_size,
|
|
185
|
+
CASE WHEN sequence_row.cycle THEN 'CYCLE' ELSE 'NO CYCLE' END
|
|
186
|
+
);
|
|
187
|
+
END LOOP;
|
|
188
|
+
|
|
189
|
+
FOR table_row IN
|
|
190
|
+
SELECT tablename
|
|
191
|
+
FROM pg_tables
|
|
192
|
+
WHERE schemaname = '${senatSchemaName}'
|
|
193
|
+
AND tablename LIKE '${escapedPrefix}' || '%'
|
|
194
|
+
ORDER BY tablename
|
|
195
|
+
LOOP
|
|
196
|
+
IF NOT EXISTS (
|
|
197
|
+
SELECT 1
|
|
198
|
+
FROM pg_class c
|
|
199
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
200
|
+
WHERE n.nspname = '${escapedStagingSchema}'
|
|
201
|
+
AND c.relkind IN ('r', 'p', 'f')
|
|
202
|
+
AND c.relname = table_row.tablename
|
|
203
|
+
) THEN
|
|
204
|
+
EXECUTE format('DROP TABLE IF EXISTS ${senatSchemaName}.%I CASCADE', table_row.tablename);
|
|
205
|
+
END IF;
|
|
206
|
+
END LOOP;
|
|
207
|
+
|
|
208
|
+
FOR table_row IN
|
|
209
|
+
${stagingRelationsQuery}
|
|
210
|
+
LOOP
|
|
211
|
+
key_columns := NULL;
|
|
212
|
+
key_schema_name := NULL;
|
|
213
|
+
configured_key_columns := NULL;
|
|
214
|
+
matching_key_column_count := 0;
|
|
215
|
+
use_row_multiset_merge := false;
|
|
216
|
+
join_condition := NULL;
|
|
217
|
+
update_assignments := NULL;
|
|
218
|
+
change_condition := NULL;
|
|
219
|
+
target_row_signature_expression := NULL;
|
|
220
|
+
staging_row_signature_expression := NULL;
|
|
221
|
+
|
|
222
|
+
SELECT configured.key_columns
|
|
223
|
+
INTO configured_key_columns
|
|
224
|
+
FROM (
|
|
225
|
+
${configuredMergeKeysValues}
|
|
226
|
+
) AS configured
|
|
227
|
+
WHERE configured.tablename = table_row.tablename;
|
|
228
|
+
|
|
229
|
+
IF configured_key_columns IS NOT NULL THEN
|
|
230
|
+
key_columns := configured_key_columns;
|
|
231
|
+
key_schema_name := 'configured';
|
|
232
|
+
|
|
233
|
+
SELECT string_agg(format('t.%1$I IS NOT DISTINCT FROM s.%1$I', key_column), ' AND ' ORDER BY ordinality)
|
|
234
|
+
INTO join_condition
|
|
235
|
+
FROM unnest(configured_key_columns) WITH ORDINALITY AS configured_key(key_column, ordinality);
|
|
236
|
+
ELSE
|
|
237
|
+
SELECT candidate.key_columns, candidate.join_condition, candidate.source_schema
|
|
238
|
+
INTO key_columns, join_condition, key_schema_name
|
|
239
|
+
FROM (
|
|
240
|
+
SELECT
|
|
241
|
+
array_agg(att.attname ORDER BY key_column.ordinality) AS key_columns,
|
|
242
|
+
string_agg(format('t.%1$I = s.%1$I', att.attname), ' AND ' ORDER BY key_column.ordinality) AS join_condition,
|
|
243
|
+
table_namespace.nspname AS source_schema,
|
|
244
|
+
CASE WHEN table_namespace.nspname = '${escapedStagingSchema}' THEN 0 ELSE 1 END AS schema_priority,
|
|
245
|
+
CASE WHEN index_data.indisprimary THEN 0 ELSE 1 END AS priority,
|
|
246
|
+
count(*) AS key_column_count,
|
|
247
|
+
index_data.indexrelid
|
|
248
|
+
FROM pg_index index_data
|
|
249
|
+
JOIN pg_class table_class ON table_class.oid = index_data.indrelid
|
|
250
|
+
JOIN pg_namespace table_namespace ON table_namespace.oid = table_class.relnamespace
|
|
251
|
+
JOIN LATERAL unnest(index_data.indkey::int2[]) WITH ORDINALITY AS key_column(attnum, ordinality) ON true
|
|
252
|
+
JOIN pg_attribute att
|
|
253
|
+
ON att.attrelid = table_class.oid
|
|
254
|
+
AND att.attnum = key_column.attnum
|
|
255
|
+
WHERE table_namespace.nspname IN ('${escapedStagingSchema}', '${senatSchemaName}')
|
|
256
|
+
AND table_class.relname = table_row.tablename
|
|
257
|
+
AND index_data.indisunique
|
|
258
|
+
AND index_data.indpred IS NULL
|
|
259
|
+
AND index_data.indexprs IS NULL
|
|
260
|
+
AND key_column.ordinality <= index_data.indnkeyatts
|
|
261
|
+
AND key_column.attnum > 0
|
|
262
|
+
AND EXISTS (
|
|
263
|
+
SELECT 1
|
|
264
|
+
FROM information_schema.columns staging_columns
|
|
265
|
+
WHERE staging_columns.table_schema = '${escapedStagingSchema}'
|
|
266
|
+
AND staging_columns.table_name = table_row.tablename
|
|
267
|
+
AND staging_columns.column_name = att.attname
|
|
268
|
+
)
|
|
269
|
+
GROUP BY index_data.indexrelid, index_data.indisprimary, table_namespace.nspname
|
|
270
|
+
HAVING index_data.indisprimary OR bool_and(att.attnotnull)
|
|
271
|
+
ORDER BY schema_priority, priority, key_column_count, index_data.indexrelid
|
|
272
|
+
LIMIT 1
|
|
273
|
+
) AS candidate;
|
|
274
|
+
END IF;
|
|
275
|
+
|
|
276
|
+
SELECT EXISTS (
|
|
277
|
+
SELECT 1
|
|
278
|
+
FROM (
|
|
279
|
+
${configuredRowMultisetTablesValues}
|
|
280
|
+
) AS configured
|
|
281
|
+
WHERE configured.tablename = table_row.tablename
|
|
282
|
+
)
|
|
283
|
+
INTO use_row_multiset_merge;
|
|
284
|
+
|
|
285
|
+
FOR constraint_row IN
|
|
286
|
+
SELECT con.conname
|
|
287
|
+
FROM pg_constraint con
|
|
288
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
289
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
290
|
+
WHERE n.nspname = '${senatSchemaName}'
|
|
291
|
+
AND c.relname = table_row.tablename
|
|
292
|
+
AND con.contype <> 'n'
|
|
293
|
+
LOOP
|
|
294
|
+
EXECUTE format(
|
|
295
|
+
'ALTER TABLE ${senatSchemaName}.%I DROP CONSTRAINT IF EXISTS %I CASCADE',
|
|
296
|
+
table_row.tablename,
|
|
297
|
+
constraint_row.conname
|
|
298
|
+
);
|
|
299
|
+
END LOOP;
|
|
300
|
+
|
|
301
|
+
FOR index_row IN
|
|
302
|
+
SELECT index_class.relname AS index_name
|
|
303
|
+
FROM pg_index idx
|
|
304
|
+
JOIN pg_class table_class ON table_class.oid = idx.indrelid
|
|
305
|
+
JOIN pg_namespace table_namespace ON table_namespace.oid = table_class.relnamespace
|
|
306
|
+
JOIN pg_class index_class ON index_class.oid = idx.indexrelid
|
|
307
|
+
LEFT JOIN pg_constraint con ON con.conindid = idx.indexrelid
|
|
308
|
+
WHERE table_namespace.nspname = '${senatSchemaName}'
|
|
309
|
+
AND table_class.relname = table_row.tablename
|
|
310
|
+
AND con.oid IS NULL
|
|
311
|
+
LOOP
|
|
312
|
+
EXECUTE format('DROP INDEX IF EXISTS ${senatSchemaName}.%I', index_row.index_name);
|
|
313
|
+
END LOOP;
|
|
314
|
+
|
|
315
|
+
FOR column_row IN
|
|
316
|
+
SELECT target_columns.column_name
|
|
317
|
+
FROM information_schema.columns target_columns
|
|
318
|
+
WHERE target_columns.table_schema = '${senatSchemaName}'
|
|
319
|
+
AND target_columns.table_name = table_row.tablename
|
|
320
|
+
AND NOT EXISTS (
|
|
321
|
+
SELECT 1
|
|
322
|
+
FROM information_schema.columns staging_columns
|
|
323
|
+
WHERE staging_columns.table_schema = '${escapedStagingSchema}'
|
|
324
|
+
AND staging_columns.table_name = table_row.tablename
|
|
325
|
+
AND staging_columns.column_name = target_columns.column_name
|
|
326
|
+
)
|
|
327
|
+
ORDER BY target_columns.ordinal_position DESC
|
|
328
|
+
LOOP
|
|
329
|
+
EXECUTE format(
|
|
330
|
+
'ALTER TABLE ${senatSchemaName}.%I DROP COLUMN IF EXISTS %I CASCADE',
|
|
331
|
+
table_row.tablename,
|
|
332
|
+
column_row.column_name
|
|
333
|
+
);
|
|
334
|
+
END LOOP;
|
|
335
|
+
|
|
336
|
+
FOR column_row IN
|
|
337
|
+
SELECT
|
|
338
|
+
staging_columns.column_name,
|
|
339
|
+
format_type(a.atttypid, a.atttypmod) AS data_type,
|
|
340
|
+
a.attnotnull AS not_null,
|
|
341
|
+
a.attidentity AS identity_kind,
|
|
342
|
+
a.attgenerated AS generated_kind,
|
|
343
|
+
pg_get_expr(ad.adbin, ad.adrelid) AS default_expression,
|
|
344
|
+
CASE
|
|
345
|
+
WHEN coll.oid IS NULL THEN NULL
|
|
346
|
+
ELSE format('%I.%I', coll_namespace.nspname, coll.collname)
|
|
347
|
+
END AS collation_name
|
|
348
|
+
FROM information_schema.columns staging_columns
|
|
349
|
+
JOIN pg_class c ON c.relname = staging_columns.table_name
|
|
350
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = staging_columns.table_schema
|
|
351
|
+
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attname = staging_columns.column_name
|
|
352
|
+
LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
|
|
353
|
+
LEFT JOIN pg_type typ ON typ.oid = a.atttypid
|
|
354
|
+
LEFT JOIN pg_collation coll ON coll.oid = a.attcollation AND coll.oid <> typ.typcollation
|
|
355
|
+
LEFT JOIN pg_namespace coll_namespace ON coll_namespace.oid = coll.collnamespace
|
|
356
|
+
WHERE staging_columns.table_schema = '${escapedStagingSchema}'
|
|
357
|
+
AND staging_columns.table_name = table_row.tablename
|
|
358
|
+
AND NOT EXISTS (
|
|
359
|
+
SELECT 1
|
|
360
|
+
FROM information_schema.columns target_columns
|
|
361
|
+
WHERE target_columns.table_schema = '${senatSchemaName}'
|
|
362
|
+
AND target_columns.table_name = table_row.tablename
|
|
363
|
+
AND target_columns.column_name = staging_columns.column_name
|
|
364
|
+
)
|
|
365
|
+
ORDER BY staging_columns.ordinal_position
|
|
366
|
+
LOOP
|
|
367
|
+
column_definition := column_row.data_type;
|
|
368
|
+
IF column_row.collation_name IS NOT NULL THEN
|
|
369
|
+
column_definition := column_definition || ' COLLATE ' || column_row.collation_name;
|
|
370
|
+
END IF;
|
|
371
|
+
IF column_row.generated_kind = 's' THEN
|
|
372
|
+
column_definition :=
|
|
373
|
+
column_definition ||
|
|
374
|
+
' GENERATED ALWAYS AS (' || replace(column_row.default_expression, '${stagingSchema}.', '${senatSchemaName}.') || ') STORED';
|
|
375
|
+
ELSIF column_row.identity_kind = 'a' THEN
|
|
376
|
+
column_definition := column_definition || ' GENERATED ALWAYS AS IDENTITY';
|
|
377
|
+
ELSIF column_row.identity_kind = 'd' THEN
|
|
378
|
+
column_definition := column_definition || ' GENERATED BY DEFAULT AS IDENTITY';
|
|
379
|
+
END IF;
|
|
380
|
+
|
|
381
|
+
EXECUTE format(
|
|
382
|
+
'ALTER TABLE ${senatSchemaName}.%I ADD COLUMN %I %s',
|
|
383
|
+
table_row.tablename,
|
|
384
|
+
column_row.column_name,
|
|
385
|
+
column_definition
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
IF column_row.generated_kind = ''
|
|
389
|
+
AND column_row.identity_kind = ''
|
|
390
|
+
AND column_row.default_expression IS NOT NULL THEN
|
|
391
|
+
EXECUTE format(
|
|
392
|
+
'ALTER TABLE ${senatSchemaName}.%I ALTER COLUMN %I SET DEFAULT %s',
|
|
393
|
+
table_row.tablename,
|
|
394
|
+
column_row.column_name,
|
|
395
|
+
replace(column_row.default_expression, '${stagingSchema}.', '${senatSchemaName}.')
|
|
396
|
+
);
|
|
397
|
+
END IF;
|
|
398
|
+
|
|
399
|
+
IF column_row.not_null THEN
|
|
400
|
+
EXECUTE format(
|
|
401
|
+
'ALTER TABLE ${senatSchemaName}.%I ALTER COLUMN %I SET NOT NULL',
|
|
402
|
+
table_row.tablename,
|
|
403
|
+
column_row.column_name
|
|
404
|
+
);
|
|
405
|
+
END IF;
|
|
406
|
+
END LOOP;
|
|
407
|
+
|
|
408
|
+
FOR column_row IN
|
|
409
|
+
SELECT
|
|
410
|
+
staging_columns.column_name,
|
|
411
|
+
format_type(staging_attribute.atttypid, staging_attribute.atttypmod) AS staging_data_type,
|
|
412
|
+
format_type(target_attribute.atttypid, target_attribute.atttypmod) AS target_data_type,
|
|
413
|
+
staging_attribute.attnotnull AS staging_not_null,
|
|
414
|
+
target_attribute.attnotnull AS target_not_null,
|
|
415
|
+
staging_attribute.attidentity AS staging_identity_kind,
|
|
416
|
+
target_attribute.attidentity AS target_identity_kind,
|
|
417
|
+
staging_attribute.attgenerated AS staging_generated_kind,
|
|
418
|
+
target_attribute.attgenerated AS target_generated_kind,
|
|
419
|
+
pg_get_expr(staging_default_expr.adbin, staging_default_expr.adrelid) AS staging_default_expression,
|
|
420
|
+
pg_get_expr(target_default_expr.adbin, target_default_expr.adrelid) AS target_default_expression,
|
|
421
|
+
CASE
|
|
422
|
+
WHEN staging_collation.oid IS NULL THEN NULL
|
|
423
|
+
ELSE format('%I.%I', staging_collation_namespace.nspname, staging_collation.collname)
|
|
424
|
+
END AS staging_collation_name,
|
|
425
|
+
CASE
|
|
426
|
+
WHEN target_collation.oid IS NULL THEN NULL
|
|
427
|
+
ELSE format('%I.%I', target_collation_namespace.nspname, target_collation.collname)
|
|
428
|
+
END AS target_collation_name
|
|
429
|
+
FROM information_schema.columns staging_columns
|
|
430
|
+
JOIN information_schema.columns target_columns
|
|
431
|
+
ON target_columns.table_schema = '${senatSchemaName}'
|
|
432
|
+
AND target_columns.table_name = staging_columns.table_name
|
|
433
|
+
AND target_columns.column_name = staging_columns.column_name
|
|
434
|
+
JOIN pg_class staging_class ON staging_class.relname = staging_columns.table_name
|
|
435
|
+
JOIN pg_namespace staging_namespace
|
|
436
|
+
ON staging_namespace.oid = staging_class.relnamespace
|
|
437
|
+
AND staging_namespace.nspname = staging_columns.table_schema
|
|
438
|
+
JOIN pg_attribute staging_attribute
|
|
439
|
+
ON staging_attribute.attrelid = staging_class.oid
|
|
440
|
+
AND staging_attribute.attname = staging_columns.column_name
|
|
441
|
+
LEFT JOIN pg_attrdef staging_default_expr
|
|
442
|
+
ON staging_default_expr.adrelid = staging_attribute.attrelid
|
|
443
|
+
AND staging_default_expr.adnum = staging_attribute.attnum
|
|
444
|
+
LEFT JOIN pg_type staging_type ON staging_type.oid = staging_attribute.atttypid
|
|
445
|
+
LEFT JOIN pg_collation staging_collation
|
|
446
|
+
ON staging_collation.oid = staging_attribute.attcollation
|
|
447
|
+
AND staging_collation.oid <> staging_type.typcollation
|
|
448
|
+
LEFT JOIN pg_namespace staging_collation_namespace
|
|
449
|
+
ON staging_collation_namespace.oid = staging_collation.collnamespace
|
|
450
|
+
JOIN pg_class target_class ON target_class.relname = target_columns.table_name
|
|
451
|
+
JOIN pg_namespace target_namespace
|
|
452
|
+
ON target_namespace.oid = target_class.relnamespace
|
|
453
|
+
AND target_namespace.nspname = target_columns.table_schema
|
|
454
|
+
JOIN pg_attribute target_attribute
|
|
455
|
+
ON target_attribute.attrelid = target_class.oid
|
|
456
|
+
AND target_attribute.attname = target_columns.column_name
|
|
457
|
+
LEFT JOIN pg_attrdef target_default_expr
|
|
458
|
+
ON target_default_expr.adrelid = target_attribute.attrelid
|
|
459
|
+
AND target_default_expr.adnum = target_attribute.attnum
|
|
460
|
+
LEFT JOIN pg_type target_type ON target_type.oid = target_attribute.atttypid
|
|
461
|
+
LEFT JOIN pg_collation target_collation
|
|
462
|
+
ON target_collation.oid = target_attribute.attcollation
|
|
463
|
+
AND target_collation.oid <> target_type.typcollation
|
|
464
|
+
LEFT JOIN pg_namespace target_collation_namespace
|
|
465
|
+
ON target_collation_namespace.oid = target_collation.collnamespace
|
|
466
|
+
WHERE staging_columns.table_schema = '${escapedStagingSchema}'
|
|
467
|
+
AND staging_columns.table_name = table_row.tablename
|
|
468
|
+
ORDER BY staging_columns.ordinal_position
|
|
469
|
+
LOOP
|
|
470
|
+
IF column_row.staging_identity_kind <> column_row.target_identity_kind
|
|
471
|
+
OR column_row.staging_generated_kind <> column_row.target_generated_kind THEN
|
|
472
|
+
RAISE EXCEPTION
|
|
473
|
+
'Unsupported identity/generated column change for %.%',
|
|
474
|
+
table_row.tablename,
|
|
475
|
+
column_row.column_name;
|
|
476
|
+
END IF;
|
|
477
|
+
|
|
478
|
+
IF column_row.staging_data_type <> column_row.target_data_type
|
|
479
|
+
OR column_row.staging_collation_name IS DISTINCT FROM column_row.target_collation_name THEN
|
|
480
|
+
EXECUTE format(
|
|
481
|
+
'ALTER TABLE ${senatSchemaName}.%I ALTER COLUMN %I TYPE %s%s USING %I::%s',
|
|
482
|
+
table_row.tablename,
|
|
483
|
+
column_row.column_name,
|
|
484
|
+
column_row.staging_data_type,
|
|
485
|
+
CASE
|
|
486
|
+
WHEN column_row.staging_collation_name IS NULL THEN ''
|
|
487
|
+
ELSE ' COLLATE ' || column_row.staging_collation_name
|
|
488
|
+
END,
|
|
489
|
+
column_row.column_name,
|
|
490
|
+
column_row.staging_data_type
|
|
491
|
+
);
|
|
492
|
+
END IF;
|
|
493
|
+
|
|
494
|
+
staging_default := replace(
|
|
495
|
+
coalesce(column_row.staging_default_expression, ''),
|
|
496
|
+
'${stagingSchema}.',
|
|
497
|
+
'${senatSchemaName}.'
|
|
498
|
+
);
|
|
499
|
+
target_default := replace(
|
|
500
|
+
coalesce(column_row.target_default_expression, ''),
|
|
501
|
+
'${stagingSchema}.',
|
|
502
|
+
'${senatSchemaName}.'
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
IF staging_default <> target_default THEN
|
|
506
|
+
IF column_row.staging_default_expression IS NULL THEN
|
|
507
|
+
EXECUTE format(
|
|
508
|
+
'ALTER TABLE ${senatSchemaName}.%I ALTER COLUMN %I DROP DEFAULT',
|
|
509
|
+
table_row.tablename,
|
|
510
|
+
column_row.column_name
|
|
511
|
+
);
|
|
512
|
+
ELSE
|
|
513
|
+
EXECUTE format(
|
|
514
|
+
'ALTER TABLE ${senatSchemaName}.%I ALTER COLUMN %I SET DEFAULT %s',
|
|
515
|
+
table_row.tablename,
|
|
516
|
+
column_row.column_name,
|
|
517
|
+
staging_default
|
|
518
|
+
);
|
|
519
|
+
END IF;
|
|
520
|
+
END IF;
|
|
521
|
+
|
|
522
|
+
IF column_row.staging_not_null <> column_row.target_not_null THEN
|
|
523
|
+
EXECUTE format(
|
|
524
|
+
'ALTER TABLE ${senatSchemaName}.%I ALTER COLUMN %I %s NOT NULL',
|
|
525
|
+
table_row.tablename,
|
|
526
|
+
column_row.column_name,
|
|
527
|
+
CASE WHEN column_row.staging_not_null THEN 'SET' ELSE 'DROP' END
|
|
528
|
+
);
|
|
529
|
+
END IF;
|
|
530
|
+
END LOOP;
|
|
531
|
+
|
|
532
|
+
SELECT obj_description(c.oid, 'pg_class')
|
|
533
|
+
INTO staging_table_comment
|
|
534
|
+
FROM pg_class c
|
|
535
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
536
|
+
WHERE n.nspname = '${escapedStagingSchema}'
|
|
537
|
+
AND c.relkind IN ('r', 'p', 'f')
|
|
538
|
+
AND c.relname = table_row.tablename;
|
|
539
|
+
|
|
540
|
+
SELECT obj_description(c.oid, 'pg_class')
|
|
541
|
+
INTO target_table_comment
|
|
542
|
+
FROM pg_class c
|
|
543
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
544
|
+
WHERE n.nspname = '${senatSchemaName}'
|
|
545
|
+
AND c.relkind IN ('r', 'p')
|
|
546
|
+
AND c.relname = table_row.tablename;
|
|
547
|
+
|
|
548
|
+
IF staging_table_comment IS DISTINCT FROM target_table_comment THEN
|
|
549
|
+
EXECUTE format(
|
|
550
|
+
'COMMENT ON TABLE ${senatSchemaName}.%I IS %L',
|
|
551
|
+
table_row.tablename,
|
|
552
|
+
staging_table_comment
|
|
553
|
+
);
|
|
554
|
+
END IF;
|
|
555
|
+
|
|
556
|
+
FOR column_row IN
|
|
557
|
+
SELECT
|
|
558
|
+
staging_columns.column_name,
|
|
559
|
+
col_description(staging_class.oid, staging_attribute.attnum) AS staging_comment,
|
|
560
|
+
col_description(target_class.oid, target_attribute.attnum) AS target_comment
|
|
561
|
+
FROM information_schema.columns staging_columns
|
|
562
|
+
JOIN pg_class staging_class ON staging_class.relname = staging_columns.table_name
|
|
563
|
+
JOIN pg_namespace staging_namespace
|
|
564
|
+
ON staging_namespace.oid = staging_class.relnamespace
|
|
565
|
+
AND staging_namespace.nspname = staging_columns.table_schema
|
|
566
|
+
JOIN pg_attribute staging_attribute
|
|
567
|
+
ON staging_attribute.attrelid = staging_class.oid
|
|
568
|
+
AND staging_attribute.attname = staging_columns.column_name
|
|
569
|
+
JOIN information_schema.columns target_columns
|
|
570
|
+
ON target_columns.table_schema = '${senatSchemaName}'
|
|
571
|
+
AND target_columns.table_name = staging_columns.table_name
|
|
572
|
+
AND target_columns.column_name = staging_columns.column_name
|
|
573
|
+
JOIN pg_class target_class ON target_class.relname = target_columns.table_name
|
|
574
|
+
JOIN pg_namespace target_namespace
|
|
575
|
+
ON target_namespace.oid = target_class.relnamespace
|
|
576
|
+
AND target_namespace.nspname = target_columns.table_schema
|
|
577
|
+
JOIN pg_attribute target_attribute
|
|
578
|
+
ON target_attribute.attrelid = target_class.oid
|
|
579
|
+
AND target_attribute.attname = target_columns.column_name
|
|
580
|
+
WHERE staging_columns.table_schema = '${escapedStagingSchema}'
|
|
581
|
+
AND staging_columns.table_name = table_row.tablename
|
|
582
|
+
ORDER BY staging_columns.ordinal_position
|
|
583
|
+
LOOP
|
|
584
|
+
IF column_row.staging_comment IS DISTINCT FROM column_row.target_comment THEN
|
|
585
|
+
EXECUTE format(
|
|
586
|
+
'COMMENT ON COLUMN ${senatSchemaName}.%I.%I IS %L',
|
|
587
|
+
table_row.tablename,
|
|
588
|
+
column_row.column_name,
|
|
589
|
+
column_row.staging_comment
|
|
590
|
+
);
|
|
591
|
+
END IF;
|
|
592
|
+
END LOOP;
|
|
593
|
+
|
|
594
|
+
SELECT string_agg(format('%I', columns.column_name), ', ' ORDER BY columns.ordinal_position)
|
|
595
|
+
, string_agg(format('s.%I', columns.column_name), ', ' ORDER BY columns.ordinal_position)
|
|
596
|
+
INTO column_list, staging_column_list
|
|
597
|
+
FROM information_schema.columns columns
|
|
598
|
+
WHERE columns.table_schema = '${escapedStagingSchema}'
|
|
599
|
+
AND columns.table_name = table_row.tablename;
|
|
600
|
+
|
|
601
|
+
SELECT
|
|
602
|
+
string_agg(format('%1$I = s.%1$I', columns.column_name), ', ' ORDER BY columns.ordinal_position),
|
|
603
|
+
string_agg(format('t.%1$I IS DISTINCT FROM s.%1$I', columns.column_name), ' OR ' ORDER BY columns.ordinal_position)
|
|
604
|
+
INTO update_assignments, change_condition
|
|
605
|
+
FROM information_schema.columns columns
|
|
606
|
+
WHERE columns.table_schema = '${escapedStagingSchema}'
|
|
607
|
+
AND columns.table_name = table_row.tablename
|
|
608
|
+
AND NOT (columns.column_name = ANY(coalesce(key_columns, ARRAY[]::text[])));
|
|
609
|
+
|
|
610
|
+
IF key_columns IS NOT NULL THEN
|
|
611
|
+
SELECT count(*)
|
|
612
|
+
INTO matching_key_column_count
|
|
613
|
+
FROM unnest(key_columns) AS key_column(column_name)
|
|
614
|
+
JOIN information_schema.columns staging_columns
|
|
615
|
+
ON staging_columns.table_schema = '${escapedStagingSchema}'
|
|
616
|
+
AND staging_columns.table_name = table_row.tablename
|
|
617
|
+
AND staging_columns.column_name = key_column.column_name
|
|
618
|
+
JOIN information_schema.columns target_columns
|
|
619
|
+
ON target_columns.table_schema = '${senatSchemaName}'
|
|
620
|
+
AND target_columns.table_name = table_row.tablename
|
|
621
|
+
AND target_columns.column_name = key_column.column_name;
|
|
622
|
+
|
|
623
|
+
IF matching_key_column_count <> cardinality(key_columns) THEN
|
|
624
|
+
RAISE NOTICE
|
|
625
|
+
'Rejected sync key for %.% from % because only %/% columns are present in both staging and target: %',
|
|
626
|
+
'${senatSchemaName}',
|
|
627
|
+
table_row.tablename,
|
|
628
|
+
coalesce(key_schema_name, 'unknown'),
|
|
629
|
+
matching_key_column_count,
|
|
630
|
+
cardinality(key_columns),
|
|
631
|
+
array_to_string(key_columns, ', ');
|
|
632
|
+
key_columns := NULL;
|
|
633
|
+
key_schema_name := NULL;
|
|
634
|
+
join_condition := NULL;
|
|
635
|
+
END IF;
|
|
636
|
+
END IF;
|
|
637
|
+
|
|
638
|
+
SELECT
|
|
639
|
+
'jsonb_build_array(' || string_agg(format('t.%I', columns.column_name), ', ' ORDER BY columns.ordinal_position) || ')',
|
|
640
|
+
'jsonb_build_array(' || string_agg(format('s.%I', columns.column_name), ', ' ORDER BY columns.ordinal_position) || ')'
|
|
641
|
+
INTO target_row_signature_expression, staging_row_signature_expression
|
|
642
|
+
FROM information_schema.columns columns
|
|
643
|
+
WHERE columns.table_schema = '${escapedStagingSchema}'
|
|
644
|
+
AND columns.table_name = table_row.tablename;
|
|
645
|
+
|
|
646
|
+
IF use_row_multiset_merge THEN
|
|
647
|
+
sync_strategy := 'incremental_row_multiset';
|
|
648
|
+
RAISE NOTICE
|
|
649
|
+
'Sync strategy for %.%: %',
|
|
650
|
+
'${senatSchemaName}',
|
|
651
|
+
table_row.tablename,
|
|
652
|
+
sync_strategy;
|
|
653
|
+
|
|
654
|
+
EXECUTE format(
|
|
655
|
+
'WITH staging_counts AS (' ||
|
|
656
|
+
' SELECT %2$s AS row_signature, count(*) AS row_count' ||
|
|
657
|
+
' FROM %3$I.%1$I s' ||
|
|
658
|
+
' GROUP BY 1' ||
|
|
659
|
+
'), target_ranked AS (' ||
|
|
660
|
+
' SELECT ctid, %4$s AS row_signature, row_number() OVER (PARTITION BY %4$s) AS row_number' ||
|
|
661
|
+
' FROM ${senatSchemaName}.%1$I t' ||
|
|
662
|
+
')' ||
|
|
663
|
+
' DELETE FROM ${senatSchemaName}.%1$I t' ||
|
|
664
|
+
' USING (' ||
|
|
665
|
+
' SELECT candidate_row.ctid' ||
|
|
666
|
+
' FROM target_ranked candidate_row' ||
|
|
667
|
+
' LEFT JOIN staging_counts reference_row USING (row_signature)' ||
|
|
668
|
+
' WHERE candidate_row.row_number > coalesce(reference_row.row_count, 0)' ||
|
|
669
|
+
' ) rows_to_delete' ||
|
|
670
|
+
' WHERE t.ctid = rows_to_delete.ctid',
|
|
671
|
+
table_row.tablename,
|
|
672
|
+
staging_row_signature_expression,
|
|
673
|
+
'${stagingSchema}',
|
|
674
|
+
target_row_signature_expression
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
EXECUTE format(
|
|
678
|
+
'WITH target_counts AS (' ||
|
|
679
|
+
' SELECT %2$s AS row_signature, count(*) AS row_count' ||
|
|
680
|
+
' FROM ${senatSchemaName}.%1$I t' ||
|
|
681
|
+
' GROUP BY 1' ||
|
|
682
|
+
'), staging_ranked AS (' ||
|
|
683
|
+
' SELECT %3$s, %4$s AS row_signature, row_number() OVER (PARTITION BY %4$s) AS row_number' ||
|
|
684
|
+
' FROM %5$I.%1$I s' ||
|
|
685
|
+
')' ||
|
|
686
|
+
' INSERT INTO ${senatSchemaName}.%1$I (%3$s)' ||
|
|
687
|
+
' SELECT %3$s' ||
|
|
688
|
+
' FROM staging_ranked candidate_row' ||
|
|
689
|
+
' LEFT JOIN target_counts reference_row USING (row_signature)' ||
|
|
690
|
+
' WHERE candidate_row.row_number > coalesce(reference_row.row_count, 0)',
|
|
691
|
+
table_row.tablename,
|
|
692
|
+
target_row_signature_expression,
|
|
693
|
+
column_list,
|
|
694
|
+
staging_row_signature_expression,
|
|
695
|
+
'${stagingSchema}'
|
|
696
|
+
);
|
|
697
|
+
ELSIF key_columns IS NULL THEN
|
|
698
|
+
sync_strategy := 'full_refresh_fallback';
|
|
699
|
+
RAISE NOTICE
|
|
700
|
+
'Sync strategy for %.%: %',
|
|
701
|
+
'${senatSchemaName}',
|
|
702
|
+
table_row.tablename,
|
|
703
|
+
sync_strategy;
|
|
704
|
+
RAISE WARNING
|
|
705
|
+
'No primary key or all-NOT-NULL unique key found for %.%; falling back to full refresh',
|
|
706
|
+
'${senatSchemaName}',
|
|
707
|
+
table_row.tablename;
|
|
708
|
+
|
|
709
|
+
EXECUTE format('TRUNCATE TABLE ${senatSchemaName}.%I', table_row.tablename);
|
|
710
|
+
|
|
711
|
+
EXECUTE format(
|
|
712
|
+
'INSERT INTO ${senatSchemaName}.%I (%s) SELECT %s FROM %I.%I s',
|
|
713
|
+
table_row.tablename,
|
|
714
|
+
column_list,
|
|
715
|
+
staging_column_list,
|
|
716
|
+
'${stagingSchema}',
|
|
717
|
+
table_row.tablename
|
|
718
|
+
);
|
|
719
|
+
ELSE
|
|
720
|
+
sync_strategy := 'incremental';
|
|
721
|
+
RAISE NOTICE
|
|
722
|
+
'Sync strategy for %.%: % (key from %: %)',
|
|
723
|
+
'${senatSchemaName}',
|
|
724
|
+
table_row.tablename,
|
|
725
|
+
sync_strategy,
|
|
726
|
+
key_schema_name,
|
|
727
|
+
array_to_string(key_columns, ', ');
|
|
728
|
+
|
|
729
|
+
EXECUTE format(
|
|
730
|
+
'DELETE FROM ${senatSchemaName}.%1$I t WHERE NOT EXISTS (SELECT 1 FROM %2$I.%1$I s WHERE %3$s)',
|
|
731
|
+
table_row.tablename,
|
|
732
|
+
'${stagingSchema}',
|
|
733
|
+
join_condition
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
IF update_assignments IS NOT NULL THEN
|
|
737
|
+
EXECUTE format(
|
|
738
|
+
'UPDATE ${senatSchemaName}.%1$I t SET %2$s FROM %3$I.%1$I s WHERE %4$s AND (%5$s)',
|
|
739
|
+
table_row.tablename,
|
|
740
|
+
update_assignments,
|
|
741
|
+
'${stagingSchema}',
|
|
742
|
+
join_condition,
|
|
743
|
+
change_condition
|
|
744
|
+
);
|
|
745
|
+
END IF;
|
|
746
|
+
|
|
747
|
+
EXECUTE format(
|
|
748
|
+
'INSERT INTO ${senatSchemaName}.%1$I (%2$s) SELECT %3$s FROM %4$I.%1$I s WHERE NOT EXISTS (SELECT 1 FROM ${senatSchemaName}.%1$I t WHERE %5$s)',
|
|
749
|
+
table_row.tablename,
|
|
750
|
+
column_list,
|
|
751
|
+
staging_column_list,
|
|
752
|
+
'${stagingSchema}',
|
|
753
|
+
join_condition
|
|
754
|
+
);
|
|
755
|
+
END IF;
|
|
756
|
+
END LOOP;
|
|
757
|
+
|
|
758
|
+
FOR sequence_row IN
|
|
759
|
+
SELECT sequencename
|
|
760
|
+
FROM pg_sequences
|
|
761
|
+
WHERE schemaname = '${escapedStagingSchema}'
|
|
762
|
+
ORDER BY sequencename
|
|
763
|
+
LOOP
|
|
764
|
+
SELECT
|
|
765
|
+
dependent_table.relname AS table_name,
|
|
766
|
+
dependent_column.attname AS column_name
|
|
767
|
+
INTO sequence_owner
|
|
768
|
+
FROM pg_class sequence_class
|
|
769
|
+
JOIN pg_namespace sequence_namespace ON sequence_namespace.oid = sequence_class.relnamespace
|
|
770
|
+
JOIN pg_depend dependency
|
|
771
|
+
ON dependency.objid = sequence_class.oid
|
|
772
|
+
AND dependency.deptype = 'a'
|
|
773
|
+
JOIN pg_class dependent_table ON dependent_table.oid = dependency.refobjid
|
|
774
|
+
JOIN pg_namespace dependent_namespace ON dependent_namespace.oid = dependent_table.relnamespace
|
|
775
|
+
JOIN pg_attribute dependent_column
|
|
776
|
+
ON dependent_column.attrelid = dependent_table.oid
|
|
777
|
+
AND dependent_column.attnum = dependency.refobjsubid
|
|
778
|
+
WHERE sequence_namespace.nspname = '${escapedStagingSchema}'
|
|
779
|
+
AND dependent_namespace.nspname = '${escapedStagingSchema}'
|
|
780
|
+
AND sequence_class.relname = sequence_row.sequencename;
|
|
781
|
+
|
|
782
|
+
IF sequence_owner.table_name IS NULL THEN
|
|
783
|
+
EXECUTE format('ALTER SEQUENCE ${senatSchemaName}.%I OWNED BY NONE', sequence_row.sequencename);
|
|
784
|
+
ELSE
|
|
785
|
+
EXECUTE format(
|
|
786
|
+
'ALTER SEQUENCE ${senatSchemaName}.%I OWNED BY ${senatSchemaName}.%I.%I',
|
|
787
|
+
sequence_row.sequencename,
|
|
788
|
+
sequence_owner.table_name,
|
|
789
|
+
sequence_owner.column_name
|
|
790
|
+
);
|
|
791
|
+
END IF;
|
|
792
|
+
END LOOP;
|
|
793
|
+
|
|
794
|
+
FOR constraint_row IN
|
|
795
|
+
SELECT
|
|
796
|
+
table_class.relname AS table_name,
|
|
797
|
+
con.conname,
|
|
798
|
+
con.contype,
|
|
799
|
+
pg_get_constraintdef(con.oid, true) AS constraint_definition
|
|
800
|
+
FROM pg_constraint con
|
|
801
|
+
JOIN pg_class table_class ON table_class.oid = con.conrelid
|
|
802
|
+
JOIN pg_namespace table_namespace ON table_namespace.oid = table_class.relnamespace
|
|
803
|
+
WHERE table_namespace.nspname = '${escapedStagingSchema}'
|
|
804
|
+
AND con.contype <> 'n'
|
|
805
|
+
ORDER BY
|
|
806
|
+
CASE con.contype
|
|
807
|
+
WHEN 'p' THEN 1
|
|
808
|
+
WHEN 'u' THEN 2
|
|
809
|
+
WHEN 'c' THEN 3
|
|
810
|
+
WHEN 'f' THEN 4
|
|
811
|
+
ELSE 5
|
|
812
|
+
END,
|
|
813
|
+
table_class.relname,
|
|
814
|
+
con.conname
|
|
815
|
+
LOOP
|
|
816
|
+
EXECUTE format(
|
|
817
|
+
'ALTER TABLE ${senatSchemaName}.%I ADD CONSTRAINT %I %s',
|
|
818
|
+
constraint_row.table_name,
|
|
819
|
+
constraint_row.conname,
|
|
820
|
+
replace(
|
|
821
|
+
replace(
|
|
822
|
+
constraint_row.constraint_definition,
|
|
823
|
+
' REFERENCES ' || '${stagingSchema}' || '.',
|
|
824
|
+
' REFERENCES ${senatSchemaName}.'
|
|
825
|
+
),
|
|
826
|
+
' REFERENCES ONLY ' || '${stagingSchema}' || '.',
|
|
827
|
+
' REFERENCES ONLY ${senatSchemaName}.'
|
|
828
|
+
)
|
|
829
|
+
);
|
|
830
|
+
END LOOP;
|
|
831
|
+
|
|
832
|
+
FOR index_row IN
|
|
833
|
+
SELECT
|
|
834
|
+
table_class.relname AS table_name,
|
|
835
|
+
pg_get_indexdef(index_data.indexrelid) AS index_definition
|
|
836
|
+
FROM pg_index index_data
|
|
837
|
+
JOIN pg_class table_class ON table_class.oid = index_data.indrelid
|
|
838
|
+
JOIN pg_namespace table_namespace ON table_namespace.oid = table_class.relnamespace
|
|
839
|
+
LEFT JOIN pg_constraint con ON con.conindid = index_data.indexrelid
|
|
840
|
+
WHERE table_namespace.nspname = '${escapedStagingSchema}'
|
|
841
|
+
AND con.oid IS NULL
|
|
842
|
+
ORDER BY table_class.relname, pg_get_indexdef(index_data.indexrelid)
|
|
843
|
+
LOOP
|
|
844
|
+
EXECUTE replace(
|
|
845
|
+
index_row.index_definition,
|
|
846
|
+
' ON ${stagingSchema}.',
|
|
847
|
+
' ON ${senatSchemaName}.'
|
|
848
|
+
);
|
|
849
|
+
END LOOP;
|
|
850
|
+
|
|
851
|
+
FOR sequence_row IN
|
|
852
|
+
SELECT sequencename
|
|
853
|
+
FROM pg_sequences
|
|
854
|
+
WHERE schemaname = '${escapedStagingSchema}'
|
|
855
|
+
ORDER BY sequencename
|
|
856
|
+
LOOP
|
|
857
|
+
EXECUTE format(
|
|
858
|
+
'SELECT last_value, is_called FROM %I.%I',
|
|
859
|
+
'${stagingSchema}',
|
|
860
|
+
sequence_row.sequencename
|
|
861
|
+
)
|
|
862
|
+
INTO sequence_state;
|
|
863
|
+
|
|
864
|
+
EXECUTE format(
|
|
865
|
+
'SELECT setval(%L, %s, %L)',
|
|
866
|
+
'${senatSchemaName}.' || sequence_row.sequencename,
|
|
867
|
+
sequence_state.last_value,
|
|
868
|
+
sequence_state.is_called
|
|
869
|
+
);
|
|
870
|
+
END LOOP;
|
|
871
|
+
|
|
872
|
+
FOR sequence_row IN
|
|
873
|
+
SELECT c.relname AS sequencename
|
|
874
|
+
FROM pg_class c
|
|
875
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
876
|
+
WHERE n.nspname = '${senatSchemaName}'
|
|
877
|
+
AND c.relkind = 'S'
|
|
878
|
+
AND c.relname LIKE '${escapedPrefix}' || '%'
|
|
879
|
+
ORDER BY c.relname
|
|
880
|
+
LOOP
|
|
881
|
+
IF NOT EXISTS (
|
|
882
|
+
SELECT 1
|
|
883
|
+
FROM pg_sequences
|
|
884
|
+
WHERE schemaname = '${escapedStagingSchema}'
|
|
885
|
+
AND sequencename = sequence_row.sequencename
|
|
886
|
+
) THEN
|
|
887
|
+
EXECUTE format('DROP SEQUENCE IF EXISTS ${senatSchemaName}.%I CASCADE', sequence_row.sequencename);
|
|
888
|
+
END IF;
|
|
889
|
+
END LOOP;
|
|
890
|
+
END $$;
|
|
891
|
+
|
|
892
|
+
GRANT SELECT ON ALL TABLES IN SCHEMA ${senatSchemaName} TO ${dbUser};
|
|
893
|
+
`;
|
|
894
|
+
}
|