@tricoteuses/senat 2.22.16 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/README.md +168 -0
  2. package/lib/aggregates.d.ts +52 -0
  3. package/lib/aggregates.js +930 -0
  4. package/lib/aggregates.mjs +713 -0
  5. package/lib/aggregates.ts +833 -0
  6. package/lib/config.d.ts +10 -0
  7. package/lib/config.js +16 -0
  8. package/lib/config.mjs +16 -0
  9. package/lib/config.ts +26 -0
  10. package/lib/databases.d.ts +2 -0
  11. package/lib/databases.js +26 -0
  12. package/lib/databases.mjs +57 -0
  13. package/lib/databases.ts +71 -0
  14. package/lib/datasets.d.ts +34 -0
  15. package/lib/datasets.js +233 -0
  16. package/lib/datasets.mjs +78 -0
  17. package/lib/datasets.ts +118 -0
  18. package/lib/fields.d.ts +10 -0
  19. package/lib/fields.js +68 -0
  20. package/lib/fields.mjs +22 -0
  21. package/lib/fields.ts +29 -0
  22. package/lib/git.d.ts +26 -0
  23. package/lib/git.js +167 -0
  24. package/lib/index.d.ts +13 -0
  25. package/lib/index.js +1 -0
  26. package/lib/index.mjs +7 -0
  27. package/lib/index.ts +64 -0
  28. package/lib/inserters.d.ts +98 -0
  29. package/lib/inserters.js +500 -0
  30. package/lib/inserters.mjs +360 -0
  31. package/lib/inserters.ts +521 -0
  32. package/lib/legislatures.json +38 -0
  33. package/lib/loaders.d.ts +58 -0
  34. package/lib/loaders.js +286 -0
  35. package/lib/loaders.mjs +158 -0
  36. package/lib/loaders.ts +271 -0
  37. package/lib/model/agenda.d.ts +6 -0
  38. package/lib/model/agenda.js +148 -0
  39. package/lib/model/ameli.d.ts +51 -0
  40. package/lib/model/ameli.js +149 -0
  41. package/lib/model/ameli.mjs +84 -0
  42. package/lib/model/ameli.ts +100 -0
  43. package/lib/model/commission.d.ts +18 -0
  44. package/lib/model/commission.js +269 -0
  45. package/lib/model/debats.d.ts +67 -0
  46. package/lib/model/debats.js +95 -0
  47. package/lib/model/debats.mjs +43 -0
  48. package/lib/model/debats.ts +68 -0
  49. package/lib/model/documents.d.ts +12 -0
  50. package/lib/model/documents.js +151 -0
  51. package/lib/model/dosleg.d.ts +7 -0
  52. package/lib/model/dosleg.js +326 -0
  53. package/lib/model/dosleg.mjs +196 -0
  54. package/lib/model/dosleg.ts +240 -0
  55. package/lib/model/index.d.ts +7 -0
  56. package/lib/model/index.js +7 -0
  57. package/lib/model/index.mjs +5 -0
  58. package/lib/model/index.ts +15 -0
  59. package/lib/model/questions.d.ts +45 -0
  60. package/lib/model/questions.js +89 -0
  61. package/lib/model/questions.mjs +71 -0
  62. package/lib/model/questions.ts +93 -0
  63. package/lib/model/scrutins.d.ts +13 -0
  64. package/lib/model/scrutins.js +114 -0
  65. package/lib/model/seance.d.ts +3 -0
  66. package/lib/model/seance.js +267 -0
  67. package/lib/model/sens.d.ts +146 -0
  68. package/lib/model/sens.js +454 -0
  69. package/lib/model/sens.mjs +415 -0
  70. package/lib/model/sens.ts +516 -0
  71. package/lib/model/texte.d.ts +7 -0
  72. package/lib/model/texte.js +256 -0
  73. package/lib/model/texte.mjs +208 -0
  74. package/lib/model/texte.ts +229 -0
  75. package/lib/model/util.d.ts +9 -0
  76. package/lib/model/util.js +38 -0
  77. package/lib/model/util.mjs +19 -0
  78. package/lib/model/util.ts +32 -0
  79. package/lib/parsers/texte.d.ts +7 -0
  80. package/lib/parsers/texte.js +228 -0
  81. package/lib/raw_types/ameli.d.ts +914 -0
  82. package/lib/raw_types/ameli.js +5 -0
  83. package/lib/raw_types/ameli.mjs +163 -0
  84. package/lib/raw_types/debats.d.ts +207 -0
  85. package/lib/raw_types/debats.js +5 -0
  86. package/lib/raw_types/debats.mjs +58 -0
  87. package/lib/raw_types/dosleg.d.ts +1619 -0
  88. package/lib/raw_types/dosleg.js +5 -0
  89. package/lib/raw_types/dosleg.mjs +438 -0
  90. package/lib/raw_types/questions.d.ts +419 -0
  91. package/lib/raw_types/questions.js +5 -0
  92. package/lib/raw_types/questions.mjs +11 -0
  93. package/lib/raw_types/senat.d.ts +11368 -0
  94. package/lib/raw_types/senat.js +5 -0
  95. package/lib/raw_types/sens.d.ts +8248 -0
  96. package/lib/raw_types/sens.js +5 -0
  97. package/lib/raw_types/sens.mjs +508 -0
  98. package/lib/raw_types_kysely/ameli.d.ts +915 -0
  99. package/lib/raw_types_kysely/ameli.js +7 -0
  100. package/lib/raw_types_kysely/ameli.mjs +5 -0
  101. package/lib/raw_types_kysely/ameli.ts +951 -0
  102. package/lib/raw_types_kysely/debats.d.ts +207 -0
  103. package/lib/raw_types_kysely/debats.js +7 -0
  104. package/lib/raw_types_kysely/debats.mjs +5 -0
  105. package/lib/raw_types_kysely/debats.ts +222 -0
  106. package/lib/raw_types_kysely/dosleg.d.ts +3532 -0
  107. package/lib/raw_types_kysely/dosleg.js +7 -0
  108. package/lib/raw_types_kysely/dosleg.mjs +5 -0
  109. package/lib/raw_types_kysely/dosleg.ts +3621 -0
  110. package/lib/raw_types_kysely/questions.d.ts +414 -0
  111. package/lib/raw_types_kysely/questions.js +7 -0
  112. package/lib/raw_types_kysely/questions.mjs +5 -0
  113. package/lib/raw_types_kysely/questions.ts +426 -0
  114. package/lib/raw_types_kysely/sens.d.ts +4394 -0
  115. package/lib/raw_types_kysely/sens.js +7 -0
  116. package/lib/raw_types_kysely/sens.mjs +5 -0
  117. package/lib/raw_types_kysely/sens.ts +4499 -0
  118. package/lib/raw_types_schemats/ameli.d.ts +539 -0
  119. package/lib/raw_types_schemats/ameli.js +2 -0
  120. package/lib/raw_types_schemats/ameli.mjs +2 -0
  121. package/lib/raw_types_schemats/ameli.ts +601 -0
  122. package/lib/raw_types_schemats/debats.d.ts +127 -0
  123. package/lib/raw_types_schemats/debats.js +2 -0
  124. package/lib/raw_types_schemats/debats.mjs +2 -0
  125. package/lib/raw_types_schemats/debats.ts +145 -0
  126. package/lib/raw_types_schemats/dosleg.d.ts +977 -0
  127. package/lib/raw_types_schemats/dosleg.js +2 -0
  128. package/lib/raw_types_schemats/dosleg.mjs +2 -0
  129. package/lib/raw_types_schemats/dosleg.ts +2193 -0
  130. package/lib/raw_types_schemats/questions.d.ts +235 -0
  131. package/lib/raw_types_schemats/questions.js +2 -0
  132. package/lib/raw_types_schemats/questions.mjs +2 -0
  133. package/lib/raw_types_schemats/questions.ts +249 -0
  134. package/lib/raw_types_schemats/sens.d.ts +6915 -0
  135. package/lib/raw_types_schemats/sens.js +2 -0
  136. package/lib/raw_types_schemats/sens.mjs +2 -0
  137. package/lib/raw_types_schemats/sens.ts +2907 -0
  138. package/lib/scripts/convert_data.d.ts +1 -0
  139. package/lib/scripts/convert_data.js +354 -0
  140. package/lib/scripts/convert_data.mjs +181 -0
  141. package/lib/scripts/convert_data.ts +243 -0
  142. package/lib/scripts/data-download.d.ts +1 -0
  143. package/lib/scripts/data-download.js +12 -0
  144. package/lib/scripts/datautil.d.ts +8 -0
  145. package/lib/scripts/datautil.js +34 -0
  146. package/lib/scripts/datautil.mjs +16 -0
  147. package/lib/scripts/datautil.ts +19 -0
  148. package/lib/scripts/images/transparent_150x192.jpg +0 -0
  149. package/lib/scripts/images/transparent_155x225.jpg +0 -0
  150. package/lib/scripts/parse_textes.d.ts +1 -0
  151. package/lib/scripts/parse_textes.js +44 -0
  152. package/lib/scripts/parse_textes.mjs +46 -0
  153. package/lib/scripts/parse_textes.ts +65 -0
  154. package/lib/scripts/retrieve_agenda.d.ts +1 -0
  155. package/lib/scripts/retrieve_agenda.js +132 -0
  156. package/lib/scripts/retrieve_cr_commission.d.ts +1 -0
  157. package/lib/scripts/retrieve_cr_commission.js +364 -0
  158. package/lib/scripts/retrieve_cr_seance.d.ts +6 -0
  159. package/lib/scripts/retrieve_cr_seance.js +347 -0
  160. package/lib/scripts/retrieve_documents.d.ts +3 -0
  161. package/lib/scripts/retrieve_documents.js +219 -0
  162. package/lib/scripts/retrieve_documents.mjs +249 -0
  163. package/lib/scripts/retrieve_documents.ts +298 -0
  164. package/lib/scripts/retrieve_open_data.d.ts +1 -0
  165. package/lib/scripts/retrieve_open_data.js +315 -0
  166. package/lib/scripts/retrieve_open_data.mjs +217 -0
  167. package/lib/scripts/retrieve_open_data.ts +268 -0
  168. package/lib/scripts/retrieve_senateurs_photos.d.ts +1 -0
  169. package/lib/scripts/retrieve_senateurs_photos.js +147 -0
  170. package/lib/scripts/retrieve_senateurs_photos.mjs +147 -0
  171. package/lib/scripts/retrieve_senateurs_photos.ts +177 -0
  172. package/lib/scripts/retrieve_videos.d.ts +1 -0
  173. package/lib/scripts/retrieve_videos.js +461 -0
  174. package/lib/scripts/shared/cli_helpers.d.ts +95 -0
  175. package/lib/scripts/shared/cli_helpers.js +91 -0
  176. package/lib/scripts/shared/cli_helpers.ts +36 -0
  177. package/lib/scripts/shared/util.d.ts +4 -0
  178. package/lib/scripts/shared/util.js +35 -0
  179. package/lib/scripts/shared/util.ts +33 -0
  180. package/lib/scripts/test_iter_load.d.ts +1 -0
  181. package/lib/scripts/test_iter_load.js +12 -0
  182. package/lib/src/config.d.ts +22 -0
  183. package/lib/src/config.js +17 -7
  184. package/lib/src/conversion_textes.js +5 -1
  185. package/lib/src/databases.d.ts +2 -1
  186. package/lib/src/databases_postgres.d.ts +4 -0
  187. package/lib/src/databases_postgres.js +23 -0
  188. package/lib/src/datasets.d.ts +4 -0
  189. package/lib/src/datasets.js +16 -2
  190. package/lib/src/git.d.ts +1 -0
  191. package/lib/src/git.js +45 -11
  192. package/lib/src/index.d.ts +19 -8
  193. package/lib/src/index.js +6 -1
  194. package/lib/src/loaders.js +10 -4
  195. package/lib/src/model/agenda.js +2 -2
  196. package/lib/src/model/ameli.d.ts +64 -52
  197. package/lib/src/model/ameli.js +147 -145
  198. package/lib/src/model/ameli_postgres.d.ts +67 -0
  199. package/lib/src/model/ameli_postgres.js +150 -0
  200. package/lib/src/model/commission.d.ts +3 -2
  201. package/lib/src/model/commission.js +2 -2
  202. package/lib/src/model/debats.d.ts +38 -66
  203. package/lib/src/model/debats.js +110 -93
  204. package/lib/src/model/documents.d.ts +32 -12
  205. package/lib/src/model/documents.js +171 -130
  206. package/lib/src/model/dosleg.d.ts +142 -5
  207. package/lib/src/model/dosleg.js +298 -156
  208. package/lib/src/model/questions.d.ts +54 -45
  209. package/lib/src/model/questions.js +89 -87
  210. package/lib/src/model/scrutins.d.ts +48 -13
  211. package/lib/src/model/scrutins.js +118 -111
  212. package/lib/src/model/seance.js +3 -3
  213. package/lib/src/model/sens.d.ts +109 -179
  214. package/lib/src/model/sens.js +384 -484
  215. package/lib/src/model/util.d.ts +0 -8
  216. package/lib/src/model/util.js +0 -23
  217. package/lib/src/parsers/texte.js +7 -7
  218. package/lib/src/raw_types/ameli.d.ts +1651 -803
  219. package/lib/src/raw_types/ameli.js +1816 -5
  220. package/lib/src/raw_types/debats.d.ts +353 -180
  221. package/lib/src/raw_types/debats.js +517 -5
  222. package/lib/src/raw_types/dosleg.d.ts +2862 -1527
  223. package/lib/src/raw_types/dosleg.js +4354 -5
  224. package/lib/src/raw_types/questions.d.ts +671 -395
  225. package/lib/src/raw_types/questions.js +1303 -5
  226. package/lib/src/raw_types/sens.d.ts +7743 -8148
  227. package/lib/src/raw_types/sens.js +10429 -5
  228. package/lib/src/raw_types_schemats/ameli.d.ts +4 -2
  229. package/lib/src/raw_types_schemats/debats.d.ts +2 -2
  230. package/lib/src/raw_types_schemats/dosleg.d.ts +2 -2
  231. package/lib/src/raw_types_schemats/questions.d.ts +2 -2
  232. package/lib/src/raw_types_schemats/sens.d.ts +10 -4216
  233. package/lib/src/scripts/convert_data.js +7 -6
  234. package/lib/src/scripts/convert_xml_to_html.js +2 -2
  235. package/lib/src/scripts/data-download.js +3 -2
  236. package/lib/src/scripts/retrieve_agenda.js +21 -9
  237. package/lib/src/scripts/retrieve_cr_commission.js +17 -17
  238. package/lib/src/scripts/retrieve_cr_seance.d.ts +14 -1
  239. package/lib/src/scripts/retrieve_cr_seance.js +10 -11
  240. package/lib/src/scripts/retrieve_documents.d.ts +11 -2
  241. package/lib/src/scripts/retrieve_documents.js +25 -14
  242. package/lib/src/scripts/retrieve_open_data.js +514 -153
  243. package/lib/src/scripts/retrieve_senateurs_photos.js +25 -11
  244. package/lib/src/scripts/retrieve_videos.js +12 -11
  245. package/lib/src/scripts/shared/cli_helpers.d.ts +1 -6
  246. package/lib/src/scripts/shared/cli_helpers.js +9 -8
  247. package/lib/src/scripts/shared/incremental_import_sql.d.ts +2 -0
  248. package/lib/src/scripts/shared/incremental_import_sql.js +894 -0
  249. package/lib/src/scripts/shared/prefixed_tables.d.ts +10 -0
  250. package/lib/src/scripts/shared/prefixed_tables.js +36 -0
  251. package/lib/src/scripts/shared/schema_version.d.ts +3 -0
  252. package/lib/src/scripts/shared/schema_version.js +97 -0
  253. package/lib/src/scripts/shared/staging_import.d.ts +3 -0
  254. package/lib/src/scripts/shared/staging_import.js +80 -0
  255. package/lib/src/scripts/shared/staging_metadata_sql.d.ts +1 -0
  256. package/lib/src/scripts/shared/staging_metadata_sql.js +221 -0
  257. package/lib/src/scripts/validate_prefixed_tables.d.ts +1 -0
  258. package/lib/src/scripts/validate_prefixed_tables.js +101 -0
  259. package/lib/src/types/ameli.d.ts +4 -4
  260. package/lib/src/types/debats.d.ts +2 -2
  261. package/lib/src/types/dosleg.d.ts +39 -39
  262. package/lib/src/types/questions.d.ts +2 -2
  263. package/lib/src/types/sens.d.ts +0 -2
  264. package/lib/src/types/texte.d.ts +1 -1
  265. package/lib/src/utils/cr_spliting.d.ts +9 -6
  266. package/lib/src/utils/cr_spliting.js +6 -101
  267. package/lib/src/utils/reunion_odj_building.d.ts +7 -3
  268. package/lib/src/utils/reunion_parsing.d.ts +2 -1
  269. package/lib/src/utils/reunion_parsing.js +2 -2
  270. package/lib/src/videos/match.js +8 -5
  271. package/lib/src/videos/pipeline.d.ts +6 -2
  272. package/lib/src/videos/pipeline.js +21 -8
  273. package/lib/src/videos/search.js +6 -2
  274. package/lib/strings.d.ts +1 -0
  275. package/lib/strings.js +18 -0
  276. package/lib/strings.mjs +18 -0
  277. package/lib/strings.ts +26 -0
  278. package/lib/tests/incrementalImportSql.test.d.ts +1 -0
  279. package/lib/tests/incrementalImportSql.test.js +155 -0
  280. package/lib/tests/prefixedTables.test.d.ts +1 -0
  281. package/lib/tests/prefixedTables.test.js +22 -0
  282. package/lib/tests/schemaVersion.test.d.ts +1 -0
  283. package/lib/tests/schemaVersion.test.js +23 -0
  284. package/lib/tests/validatePrefixedTables.test.d.ts +1 -0
  285. package/lib/tests/validatePrefixedTables.test.js +14 -0
  286. package/lib/types/agenda.d.ts +44 -0
  287. package/lib/types/agenda.js +1 -0
  288. package/lib/types/ameli.d.ts +5 -0
  289. package/lib/types/ameli.js +1 -0
  290. package/lib/types/ameli.mjs +13 -0
  291. package/lib/types/ameli.ts +21 -0
  292. package/lib/types/compte_rendu.d.ts +83 -0
  293. package/lib/types/compte_rendu.js +1 -0
  294. package/lib/types/debats.d.ts +2 -0
  295. package/lib/types/debats.js +1 -0
  296. package/lib/types/debats.mjs +2 -0
  297. package/lib/types/debats.ts +6 -0
  298. package/lib/types/dosleg.d.ts +70 -0
  299. package/lib/types/dosleg.js +1 -0
  300. package/lib/types/dosleg.mjs +151 -0
  301. package/lib/types/dosleg.ts +284 -0
  302. package/lib/types/questions.d.ts +2 -0
  303. package/lib/types/questions.js +1 -0
  304. package/lib/types/questions.mjs +1 -0
  305. package/lib/types/questions.ts +3 -0
  306. package/lib/types/sens.d.ts +10 -0
  307. package/lib/types/sens.js +1 -0
  308. package/lib/types/sens.mjs +1 -0
  309. package/lib/types/sens.ts +12 -0
  310. package/lib/types/sessions.d.ts +5 -0
  311. package/lib/types/sessions.js +84 -0
  312. package/lib/types/sessions.mjs +43 -0
  313. package/lib/types/sessions.ts +42 -0
  314. package/lib/types/texte.d.ts +74 -0
  315. package/lib/types/texte.js +16 -0
  316. package/lib/types/texte.mjs +16 -0
  317. package/lib/types/texte.ts +76 -0
  318. package/lib/typings/windows-1252.d.js +2 -0
  319. package/lib/typings/windows-1252.d.mjs +2 -0
  320. package/lib/typings/windows-1252.d.ts +11 -0
  321. package/lib/utils/cr_spliting.d.ts +28 -0
  322. package/lib/utils/cr_spliting.js +265 -0
  323. package/lib/utils/date.d.ts +10 -0
  324. package/lib/utils/date.js +100 -0
  325. package/lib/utils/nvs-timecode.d.ts +7 -0
  326. package/lib/utils/nvs-timecode.js +79 -0
  327. package/lib/utils/reunion_grouping.d.ts +9 -0
  328. package/lib/utils/reunion_grouping.js +361 -0
  329. package/lib/utils/reunion_odj_building.d.ts +5 -0
  330. package/lib/utils/reunion_odj_building.js +154 -0
  331. package/lib/utils/reunion_parsing.d.ts +23 -0
  332. package/lib/utils/reunion_parsing.js +209 -0
  333. package/lib/utils/scoring.d.ts +14 -0
  334. package/lib/utils/scoring.js +147 -0
  335. package/lib/utils/string_cleaning.d.ts +7 -0
  336. package/lib/utils/string_cleaning.js +57 -0
  337. package/lib/validators/config.d.ts +9 -0
  338. package/lib/validators/config.js +10 -0
  339. package/lib/validators/config.mjs +54 -0
  340. package/lib/validators/config.ts +79 -0
  341. package/lib/validators/senat.d.ts +0 -0
  342. package/lib/validators/senat.js +28 -0
  343. package/lib/validators/senat.mjs +24 -0
  344. package/lib/validators/senat.ts +26 -0
  345. package/package.json +11 -11
@@ -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
+ }