@intentius/chant-lexicon-sql 0.124.46 → 0.124.48

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 (360) hide show
  1. package/dist/change-set.d.ts +1 -1
  2. package/dist/change-set.d.ts.map +1 -1
  3. package/dist/change-set.js +2 -1
  4. package/dist/clickhouse/apply/statements.d.ts +9 -0
  5. package/dist/clickhouse/apply/statements.d.ts.map +1 -1
  6. package/dist/clickhouse/apply/statements.js +24 -0
  7. package/dist/clickhouse/import/live-export.d.ts +7 -3
  8. package/dist/clickhouse/import/live-export.d.ts.map +1 -1
  9. package/dist/clickhouse/import/live-export.js +21 -5
  10. package/dist/clickhouse/live/named-collections.d.ts +27 -0
  11. package/dist/clickhouse/live/named-collections.d.ts.map +1 -0
  12. package/dist/clickhouse/live/named-collections.js +53 -0
  13. package/dist/clickhouse/live/undeclarable.d.ts +15 -3
  14. package/dist/clickhouse/live/undeclarable.d.ts.map +1 -1
  15. package/dist/clickhouse/live/undeclarable.js +18 -4
  16. package/dist/clickhouse/named-collections.d.ts +83 -0
  17. package/dist/clickhouse/named-collections.d.ts.map +1 -0
  18. package/dist/clickhouse/named-collections.js +203 -0
  19. package/dist/clickhouse/parser.d.ts.map +1 -1
  20. package/dist/clickhouse/parser.js +1 -1
  21. package/dist/clickhouse/plan/commands.d.ts +2 -1
  22. package/dist/clickhouse/plan/commands.d.ts.map +1 -1
  23. package/dist/clickhouse/plan/commands.js +17 -2
  24. package/dist/clickhouse/plan/diff.d.ts +15 -0
  25. package/dist/clickhouse/plan/diff.d.ts.map +1 -1
  26. package/dist/clickhouse/plan/diff.js +43 -1
  27. package/dist/clickhouse/plan/disruption.d.ts.map +1 -1
  28. package/dist/clickhouse/plan/disruption.js +5 -0
  29. package/dist/clickhouse/plan/rebuild-handoff.d.ts.map +1 -1
  30. package/dist/clickhouse/plan/rebuild-handoff.js +4 -0
  31. package/dist/clickhouse/plan/report.d.ts.map +1 -1
  32. package/dist/clickhouse/plan/report.js +6 -1
  33. package/dist/clickhouse/plan/rules.d.ts +5 -1
  34. package/dist/clickhouse/plan/rules.d.ts.map +1 -1
  35. package/dist/clickhouse/plan/rules.js +7 -3
  36. package/dist/clickhouse/plan/stream.d.ts +40 -0
  37. package/dist/clickhouse/plan/stream.d.ts.map +1 -0
  38. package/dist/clickhouse/plan/stream.js +107 -0
  39. package/dist/clickhouse/rebuild/observe.d.ts +3 -1
  40. package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
  41. package/dist/clickhouse/rebuild/observe.js +14 -1
  42. package/dist/clickhouse/review-catalog.d.ts +213 -0
  43. package/dist/clickhouse/review-catalog.d.ts.map +1 -0
  44. package/dist/clickhouse/review-catalog.js +330 -0
  45. package/dist/codegen/classifier-page.d.ts.map +1 -1
  46. package/dist/codegen/classifier-page.js +21 -4
  47. package/dist/codegen/clickhouse-module.d.ts.map +1 -1
  48. package/dist/codegen/clickhouse-module.js +3 -0
  49. package/dist/codegen/coverage-page.d.ts.map +1 -1
  50. package/dist/codegen/coverage-page.js +5 -3
  51. package/dist/codegen/docs.js +1 -1
  52. package/dist/codegen/postgres-classifier-page.js +1 -1
  53. package/dist/codegen/registry.d.ts.map +1 -1
  54. package/dist/codegen/registry.js +5 -0
  55. package/dist/composites/catalog.d.ts.map +1 -1
  56. package/dist/composites/catalog.js +100 -0
  57. package/dist/composites/clickhouse.d.ts +2 -0
  58. package/dist/composites/clickhouse.d.ts.map +1 -1
  59. package/dist/composites/clickhouse.js +2 -0
  60. package/dist/composites/otel-tables.d.ts +100 -0
  61. package/dist/composites/otel-tables.d.ts.map +1 -0
  62. package/dist/composites/otel-tables.js +282 -0
  63. package/dist/composites/stream-ingest.d.ts +63 -0
  64. package/dist/composites/stream-ingest.d.ts.map +1 -0
  65. package/dist/composites/stream-ingest.js +39 -0
  66. package/dist/coverage/clickhouse.json +1 -1
  67. package/dist/coverage/postgres.json +7 -7
  68. package/dist/coverage.d.ts +16 -6
  69. package/dist/coverage.d.ts.map +1 -1
  70. package/dist/coverage.js +10 -5
  71. package/dist/drizzle-command.d.ts +17 -0
  72. package/dist/drizzle-command.d.ts.map +1 -0
  73. package/dist/drizzle-command.js +91 -0
  74. package/dist/drizzle.d.ts +70 -0
  75. package/dist/drizzle.d.ts.map +1 -0
  76. package/dist/drizzle.js +684 -0
  77. package/dist/files/clickhouse.d.ts.map +1 -1
  78. package/dist/files/clickhouse.js +8 -1
  79. package/dist/files/common.d.ts +13 -0
  80. package/dist/files/common.d.ts.map +1 -1
  81. package/dist/files/common.js +5 -0
  82. package/dist/files/postgres.js +12 -2
  83. package/dist/generated/clickhouse.d.ts +2 -0
  84. package/dist/generated/clickhouse.d.ts.map +1 -1
  85. package/dist/generated/clickhouse.js +2 -0
  86. package/dist/import-parser.d.ts +21 -1
  87. package/dist/import-parser.d.ts.map +1 -1
  88. package/dist/import-parser.js +79 -5
  89. package/dist/index.d.ts +1 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +1 -1
  92. package/dist/integrity.json +6 -5
  93. package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
  94. package/dist/lint/post-synth/audit-entries.js +1 -0
  95. package/dist/lint/post-synth/index.d.ts.map +1 -1
  96. package/dist/lint/post-synth/index.js +2 -0
  97. package/dist/lint/post-synth/sqlch128.d.ts +18 -0
  98. package/dist/lint/post-synth/sqlch128.d.ts.map +1 -0
  99. package/dist/lint/post-synth/sqlch128.js +222 -0
  100. package/dist/lint/rules/postgres-templates.js +2 -2
  101. package/dist/lsp/postgres-template.d.ts.map +1 -1
  102. package/dist/lsp/postgres-template.js +11 -0
  103. package/dist/manifest.json +1 -1
  104. package/dist/mcp/postgres.js +1 -1
  105. package/dist/meta.json +25 -0
  106. package/dist/migrate/from-postgres/detect.d.ts +4 -0
  107. package/dist/migrate/from-postgres/detect.d.ts.map +1 -0
  108. package/dist/migrate/from-postgres/detect.js +7 -0
  109. package/dist/migrate/from-postgres/emit.d.ts +26 -0
  110. package/dist/migrate/from-postgres/emit.d.ts.map +1 -0
  111. package/dist/migrate/from-postgres/emit.js +86 -0
  112. package/dist/migrate/from-postgres/index.d.ts +51 -0
  113. package/dist/migrate/from-postgres/index.d.ts.map +1 -0
  114. package/dist/migrate/from-postgres/index.js +115 -0
  115. package/dist/migrate/from-postgres/translate.d.ts +73 -0
  116. package/dist/migrate/from-postgres/translate.d.ts.map +1 -0
  117. package/dist/migrate/from-postgres/translate.js +261 -0
  118. package/dist/migrate/from-postgres/types.d.ts +51 -0
  119. package/dist/migrate/from-postgres/types.d.ts.map +1 -0
  120. package/dist/migrate/from-postgres/types.js +211 -0
  121. package/dist/migration-replay.d.ts +73 -0
  122. package/dist/migration-replay.d.ts.map +1 -0
  123. package/dist/migration-replay.js +294 -0
  124. package/dist/okf/index.md +6 -0
  125. package/dist/okf/rules/SQLCH128.md +11 -0
  126. package/dist/okf/types/PostgresForeignDataWrapper.md +9 -0
  127. package/dist/okf/types/PostgresForeignServer.md +9 -0
  128. package/dist/okf/types/PostgresOperator.md +9 -0
  129. package/dist/okf/types/PostgresPublication.md +9 -0
  130. package/dist/okf/types/PostgresStatistics.md +9 -0
  131. package/dist/op/activities/postgres-migration.d.ts.map +1 -1
  132. package/dist/op/activities/postgres-migration.js +4 -0
  133. package/dist/op/activity-contracts.d.ts +20 -0
  134. package/dist/op/activity-contracts.d.ts.map +1 -1
  135. package/dist/op/activity-contracts.js +2 -0
  136. package/dist/plugin.d.ts.map +1 -1
  137. package/dist/plugin.js +36 -2
  138. package/dist/postgres/apply/apply.js +8 -1
  139. package/dist/postgres/apply/statements.d.ts.map +1 -1
  140. package/dist/postgres/apply/statements.js +87 -2
  141. package/dist/postgres/constraint-names.d.ts +30 -0
  142. package/dist/postgres/constraint-names.d.ts.map +1 -0
  143. package/dist/postgres/constraint-names.js +72 -0
  144. package/dist/postgres/entities.d.ts +110 -1
  145. package/dist/postgres/entities.d.ts.map +1 -1
  146. package/dist/postgres/entities.js +146 -0
  147. package/dist/postgres/entity-types.d.ts +5 -0
  148. package/dist/postgres/entity-types.d.ts.map +1 -1
  149. package/dist/postgres/entity-types.js +5 -0
  150. package/dist/postgres/import/generator.d.ts.map +1 -1
  151. package/dist/postgres/import/generator.js +50 -2
  152. package/dist/postgres/import/live-export.d.ts.map +1 -1
  153. package/dist/postgres/import/live-export.js +2 -0
  154. package/dist/postgres/live/catalog-kinds.d.ts +14 -0
  155. package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
  156. package/dist/postgres/live/catalog-kinds.js +206 -0
  157. package/dist/postgres/live/catalog.d.ts +2 -0
  158. package/dist/postgres/live/catalog.d.ts.map +1 -1
  159. package/dist/postgres/live/catalog.js +3 -1
  160. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  161. package/dist/postgres/live/describe-resources.js +10 -2
  162. package/dist/postgres/migrate/backfill.d.ts +2 -0
  163. package/dist/postgres/migrate/backfill.d.ts.map +1 -1
  164. package/dist/postgres/migrate/backfill.js +7 -1
  165. package/dist/postgres/migrate/carry.d.ts +45 -1
  166. package/dist/postgres/migrate/carry.d.ts.map +1 -1
  167. package/dist/postgres/migrate/carry.js +72 -8
  168. package/dist/postgres/migrate/observe.d.ts +28 -4
  169. package/dist/postgres/migrate/observe.d.ts.map +1 -1
  170. package/dist/postgres/migrate/observe.js +41 -12
  171. package/dist/postgres/migrate/op.d.ts +26 -1
  172. package/dist/postgres/migrate/op.d.ts.map +1 -1
  173. package/dist/postgres/migrate/op.js +3 -1
  174. package/dist/postgres/migrate/steps.d.ts +35 -2
  175. package/dist/postgres/migrate/steps.d.ts.map +1 -1
  176. package/dist/postgres/migrate/steps.js +158 -12
  177. package/dist/postgres/parser.d.ts +72 -1
  178. package/dist/postgres/parser.d.ts.map +1 -1
  179. package/dist/postgres/parser.js +194 -2
  180. package/dist/postgres/plan/deep.d.ts.map +1 -1
  181. package/dist/postgres/plan/deep.js +29 -1
  182. package/dist/postgres/plan/diff.d.ts.map +1 -1
  183. package/dist/postgres/plan/diff.js +39 -1
  184. package/dist/postgres/plan/normalize.d.ts +17 -2
  185. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  186. package/dist/postgres/plan/normalize.js +109 -1
  187. package/dist/postgres/plan/rules.d.ts +1 -0
  188. package/dist/postgres/plan/rules.d.ts.map +1 -1
  189. package/dist/postgres/plan/rules.js +3 -2
  190. package/dist/postgres/plan/schema.js +1 -1
  191. package/dist/postgres/secret-options.d.ts +10 -0
  192. package/dist/postgres/secret-options.d.ts.map +1 -0
  193. package/dist/postgres/secret-options.js +9 -0
  194. package/dist/postgres/testing/roundtrip-schema.d.ts +3 -2
  195. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
  196. package/dist/postgres/testing/roundtrip-schema.js +13 -1
  197. package/dist/postgres.d.ts +1 -1
  198. package/dist/postgres.d.ts.map +1 -1
  199. package/dist/postgres.js +1 -1
  200. package/dist/rules/audit-entries.ts +1 -0
  201. package/dist/rules/postgres-templates.ts +2 -2
  202. package/dist/rules/sqlch128.ts +230 -0
  203. package/dist/serializer.d.ts +5 -0
  204. package/dist/serializer.d.ts.map +1 -1
  205. package/dist/serializer.js +13 -2
  206. package/dist/sql-types-command.d.ts +13 -0
  207. package/dist/sql-types-command.d.ts.map +1 -1
  208. package/dist/sql-types-command.js +31 -16
  209. package/dist/sql-types.d.ts +22 -0
  210. package/dist/sql-types.d.ts.map +1 -1
  211. package/dist/sql-types.js +10 -6
  212. package/dist/sqlserver/lexer.d.ts +37 -0
  213. package/dist/sqlserver/lexer.d.ts.map +1 -0
  214. package/dist/sqlserver/lexer.js +254 -0
  215. package/dist/sqlserver/parse.d.ts +185 -0
  216. package/dist/sqlserver/parse.d.ts.map +1 -0
  217. package/dist/sqlserver/parse.js +767 -0
  218. package/dist/sqlserver/translate.d.ts +34 -0
  219. package/dist/sqlserver/translate.d.ts.map +1 -0
  220. package/dist/sqlserver/translate.js +495 -0
  221. package/dist/sqlserver/types.d.ts +44 -0
  222. package/dist/sqlserver/types.d.ts.map +1 -0
  223. package/dist/sqlserver/types.js +133 -0
  224. package/dist/sqlserver/validate.d.ts +31 -0
  225. package/dist/sqlserver/validate.d.ts.map +1 -0
  226. package/dist/sqlserver/validate.js +67 -0
  227. package/dist/validate.d.ts.map +1 -1
  228. package/dist/validate.js +5 -0
  229. package/package.json +2 -2
  230. package/src/change-set.ts +2 -1
  231. package/src/clickhouse/apply/statements.ts +25 -0
  232. package/src/clickhouse/import/live-export.ts +20 -5
  233. package/src/clickhouse/live/named-collections.e2e.test.ts +88 -0
  234. package/src/clickhouse/live/named-collections.ts +61 -0
  235. package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -3
  236. package/src/clickhouse/live/undeclarable.ts +20 -4
  237. package/src/clickhouse/named-collections.test.ts +187 -0
  238. package/src/clickhouse/named-collections.ts +224 -0
  239. package/src/clickhouse/parser.ts +2 -1
  240. package/src/clickhouse/plan/commands.ts +17 -2
  241. package/src/clickhouse/plan/diff.ts +57 -1
  242. package/src/clickhouse/plan/disruption.ts +5 -0
  243. package/src/clickhouse/plan/rebuild-handoff.ts +3 -0
  244. package/src/clickhouse/plan/report.ts +6 -1
  245. package/src/clickhouse/plan/rules.ts +14 -4
  246. package/src/clickhouse/plan/stream-s3queue.e2e.test.ts +148 -0
  247. package/src/clickhouse/plan/stream.test.ts +237 -0
  248. package/src/clickhouse/plan/stream.ts +111 -0
  249. package/src/clickhouse/rebuild/observe.ts +15 -1
  250. package/src/clickhouse/review-catalog.test.ts +181 -0
  251. package/src/clickhouse/review-catalog.ts +489 -0
  252. package/src/codegen/classifier-page.ts +21 -4
  253. package/src/codegen/clickhouse-module.ts +3 -0
  254. package/src/codegen/coverage-page.ts +5 -3
  255. package/src/codegen/docs.ts +1 -1
  256. package/src/codegen/postgres-classifier-page.ts +1 -1
  257. package/src/codegen/registry.ts +5 -0
  258. package/src/composites/catalog.test.ts +6 -1
  259. package/src/composites/catalog.ts +100 -0
  260. package/src/composites/clickhouse.ts +2 -0
  261. package/src/composites/composites.test.ts +1 -1
  262. package/src/composites/otel-tables.test.ts +118 -0
  263. package/src/composites/otel-tables.ts +339 -0
  264. package/src/composites/stream-ingest.test.ts +114 -0
  265. package/src/composites/stream-ingest.ts +82 -0
  266. package/src/core/undeclarable.test.ts +6 -5
  267. package/src/coverage/clickhouse.json +1 -1
  268. package/src/coverage/postgres.json +7 -7
  269. package/src/coverage.test.ts +27 -2
  270. package/src/coverage.ts +18 -7
  271. package/src/drizzle-command.ts +94 -0
  272. package/src/drizzle-kit.e2e.test.ts +89 -0
  273. package/src/drizzle-types.e2e.test.ts +67 -0
  274. package/src/drizzle.test.ts +358 -0
  275. package/src/drizzle.ts +737 -0
  276. package/src/files/clickhouse.ts +8 -1
  277. package/src/files/common.ts +14 -1
  278. package/src/files/postgres.ts +12 -2
  279. package/src/generated/clickhouse.ts +3 -0
  280. package/src/generated/lexicon-sql.json +25 -0
  281. package/src/import-parser.sqlserver.test.ts +92 -0
  282. package/src/import-parser.ts +79 -5
  283. package/src/index.ts +1 -1
  284. package/src/lint/post-synth/audit-entries.ts +1 -0
  285. package/src/lint/post-synth/index.ts +2 -0
  286. package/src/lint/post-synth/sqlch128.test.ts +107 -0
  287. package/src/lint/post-synth/sqlch128.ts +230 -0
  288. package/src/lint/rules/postgres-templates.ts +2 -2
  289. package/src/lsp/postgres-template.ts +11 -0
  290. package/src/mcp/postgres.ts +1 -1
  291. package/src/migrate/from-postgres/cli.test.ts +165 -0
  292. package/src/migrate/from-postgres/detect.ts +7 -0
  293. package/src/migrate/from-postgres/emit.ts +96 -0
  294. package/src/migrate/from-postgres/example.test.ts +205 -0
  295. package/src/migrate/from-postgres/index.ts +122 -0
  296. package/src/migrate/from-postgres/translate.test.ts +242 -0
  297. package/src/migrate/from-postgres/translate.ts +359 -0
  298. package/src/migrate/from-postgres/types.test.ts +116 -0
  299. package/src/migrate/from-postgres/types.ts +236 -0
  300. package/src/migrate/from-postgres/validate.e2e.test.ts +44 -0
  301. package/src/migrate/from-postgres/workspace.e2e.test.ts +114 -0
  302. package/src/migration-replay.e2e.test.ts +155 -0
  303. package/src/migration-replay.test.ts +173 -0
  304. package/src/migration-replay.ts +289 -0
  305. package/src/op/__snapshots__/watch-pipeline.test.ts.snap +2 -2
  306. package/src/op/activities/postgres-migration.ts +4 -0
  307. package/src/op/activity-contracts.ts +2 -0
  308. package/src/plugin.ts +37 -2
  309. package/src/postgres/apply/apply.ts +9 -1
  310. package/src/postgres/apply/statements.ts +74 -1
  311. package/src/postgres/constraint-names.ts +71 -0
  312. package/src/postgres/database-kinds.test.ts +384 -0
  313. package/src/postgres/entities.ts +266 -1
  314. package/src/postgres/entity-types.ts +5 -0
  315. package/src/postgres/import/generator.ts +42 -2
  316. package/src/postgres/import/live-export.ts +1 -0
  317. package/src/postgres/live/catalog-kinds.ts +223 -0
  318. package/src/postgres/live/catalog.ts +6 -1
  319. package/src/postgres/live/database-kinds.e2e.test.ts +164 -0
  320. package/src/postgres/live/describe-resources.ts +11 -2
  321. package/src/postgres/live/undeclarable.e2e.test.ts +13 -6
  322. package/src/postgres/migrate/backfill.ts +13 -1
  323. package/src/postgres/migrate/carry.ts +103 -8
  324. package/src/postgres/migrate/followups.e2e.test.ts +218 -0
  325. package/src/postgres/migrate/followups.test.ts +199 -0
  326. package/src/postgres/migrate/kinds.e2e.test.ts +5 -5
  327. package/src/postgres/migrate/observe.ts +53 -14
  328. package/src/postgres/migrate/op.ts +28 -1
  329. package/src/postgres/migrate/partition.e2e.test.ts +129 -0
  330. package/src/postgres/migrate/partition.test.ts +206 -0
  331. package/src/postgres/migrate/steps.ts +157 -12
  332. package/src/postgres/parser.ts +234 -2
  333. package/src/postgres/plan/deep.ts +28 -1
  334. package/src/postgres/plan/diff.ts +36 -1
  335. package/src/postgres/plan/normalize.ts +122 -1
  336. package/src/postgres/plan/rules.ts +10 -3
  337. package/src/postgres/plan/schema.ts +1 -1
  338. package/src/postgres/secret-options.ts +10 -0
  339. package/src/postgres/testing/roundtrip-schema.ts +13 -1
  340. package/src/postgres.ts +16 -0
  341. package/src/serializer.ts +13 -2
  342. package/src/skills/chant-sql-postgres-migration.md +3 -3
  343. package/src/skills/chant-sql-postgres.md +6 -1
  344. package/src/skills/chant-sql.md +2 -1
  345. package/src/sql-types-command.ts +38 -16
  346. package/src/sql-types.ts +11 -6
  347. package/src/sqlserver/fixture.test.ts +186 -0
  348. package/src/sqlserver/import-cli.test.ts +61 -0
  349. package/src/sqlserver/lexer.ts +260 -0
  350. package/src/sqlserver/parse.ts +865 -0
  351. package/src/sqlserver/translate.test.ts +396 -0
  352. package/src/sqlserver/translate.ts +558 -0
  353. package/src/sqlserver/types.test.ts +102 -0
  354. package/src/sqlserver/types.ts +154 -0
  355. package/src/sqlserver/validate.e2e.test.ts +32 -0
  356. package/src/sqlserver/validate.test.ts +49 -0
  357. package/src/sqlserver/validate.ts +76 -0
  358. package/src/validate.ts +5 -0
  359. package/src/workspace-kinds.test.ts +11 -0
  360. package/workspace-kinds.json +100 -2
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Postgres tables into the ClickHouse tables a CDC pipeline fills (#4399).
3
+ *
4
+ * The input is the Postgres declarations a build reads (or a `.sql` file read
5
+ * into the same declarations), the output a {@link Mirror} per table: its
6
+ * ClickHouse columns, its sort key and the facts the writers need. Writing
7
+ * the mirror as DDL or as a `CdcMirror` call is `./emit.ts`.
8
+ *
9
+ * What is mechanical makes no finding: the type map in `./types.ts`, names,
10
+ * column order, a `NOT NULL` column keeping its type. Everything else is
11
+ * decided by the shared catalog (`../../clickhouse/review-catalog.ts`): the
12
+ * sort key from the primary key, the `ReplacingMergeTree` engine for a
13
+ * mirror, `Nullable` against a default, `LowCardinality` candidates, and the
14
+ * constraints ClickHouse does not enforce. This file adds what only Postgres
15
+ * has: identity, serial and generated columns, defaults, storage options,
16
+ * indexes that are not carried, and the objects of the file that are not
17
+ * tables.
18
+ *
19
+ * A mirror's columns carry no `DEFAULT`: the pipeline writes every column of
20
+ * a row, so the source's default has already run. That is an `info` finding
21
+ * (SQLRV008), not a `warning`, since it changes nothing a reader of the
22
+ * mirror sees.
23
+ *
24
+ * The Postgres schema is not part of the mirror's name. A pipeline chooses
25
+ * the ClickHouse database it writes to, so `shop.customers` becomes
26
+ * `customers` in that database. Two tables with one name in different
27
+ * schemas keep their schema as a database qualifier (`shop.customers`).
28
+ */
29
+ import type { Declarable } from "@intentius/chant/declarable";
30
+ import type { ImportDiagnostic } from "@intentius/chant/import/parser";
31
+ /** The names of the columns the pipeline adds, and what `CdcMirror` calls them by default. */
32
+ export declare const MIRROR_COLUMNS: {
33
+ readonly version: "_version";
34
+ readonly deleted: "_is_deleted";
35
+ };
36
+ export interface MirrorColumn {
37
+ name: string;
38
+ /** The ClickHouse type, `Nullable` and `LowCardinality` decisions included. */
39
+ type: string;
40
+ comment?: string;
41
+ }
42
+ export interface Mirror {
43
+ /** The export the Postgres table is declared as, or its name when the source has none. */
44
+ export: string;
45
+ /** The ClickHouse table name, qualified with a database only when two tables would collide. */
46
+ name: string;
47
+ /** The Postgres schema and table, for the workspace link. */
48
+ source: {
49
+ schema: string;
50
+ table: string;
51
+ };
52
+ columns: MirrorColumn[];
53
+ /** The sort key; empty means `tuple()`. */
54
+ primaryKey: string[];
55
+ /** MergeTree settings the key needs. */
56
+ settings: Record<string, string>;
57
+ /** `-- ` lines for constraints ClickHouse does not enforce. */
58
+ constraintComments: string[];
59
+ /** The table's description, which `CdcMirror` has no place for. */
60
+ comment?: string;
61
+ }
62
+ export interface Translation {
63
+ mirrors: Mirror[];
64
+ diagnostics: ImportDiagnostic[];
65
+ /** The Postgres outputs each mirrored column reads, as a workspace link names them (`shop.customers.email`). */
66
+ outputs: string[];
67
+ }
68
+ export interface TranslateOptions {
69
+ /** The mirror is a `CdcMirror` call: a table description has no place in it. */
70
+ composite: boolean;
71
+ }
72
+ export declare function translatePostgres(entities: ReadonlyMap<string, Declarable>, opts: TranslateOptions): Translation;
73
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../../src/migrate/from-postgres/translate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAgBvE,8FAA8F;AAC9F,eAAO,MAAM,cAAc;;;CAA2D,CAAC;AAEvF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,MAAM;IACrB,0FAA0F;IAC1F,MAAM,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+DAA+D;IAC/D,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,gHAAgH;IAChH,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,SAAS,EAAE,OAAO,CAAC;CACpB;AAwOD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,IAAI,EAAE,gBAAgB,GAAG,WAAW,CA0ChH"}
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Postgres tables into the ClickHouse tables a CDC pipeline fills (#4399).
3
+ *
4
+ * The input is the Postgres declarations a build reads (or a `.sql` file read
5
+ * into the same declarations), the output a {@link Mirror} per table: its
6
+ * ClickHouse columns, its sort key and the facts the writers need. Writing
7
+ * the mirror as DDL or as a `CdcMirror` call is `./emit.ts`.
8
+ *
9
+ * What is mechanical makes no finding: the type map in `./types.ts`, names,
10
+ * column order, a `NOT NULL` column keeping its type. Everything else is
11
+ * decided by the shared catalog (`../../clickhouse/review-catalog.ts`): the
12
+ * sort key from the primary key, the `ReplacingMergeTree` engine for a
13
+ * mirror, `Nullable` against a default, `LowCardinality` candidates, and the
14
+ * constraints ClickHouse does not enforce. This file adds what only Postgres
15
+ * has: identity, serial and generated columns, defaults, storage options,
16
+ * indexes that are not carried, and the objects of the file that are not
17
+ * tables.
18
+ *
19
+ * A mirror's columns carry no `DEFAULT`: the pipeline writes every column of
20
+ * a row, so the source's default has already run. That is an `info` finding
21
+ * (SQLRV008), not a `warning`, since it changes nothing a reader of the
22
+ * mirror sees.
23
+ *
24
+ * The Postgres schema is not part of the mirror's name. A pipeline chooses
25
+ * the ClickHouse database it writes to, so `shop.customers` becomes
26
+ * `customers` in that database. Two tables with one name in different
27
+ * schemas keep their schema as a database qualifier (`shop.customers`).
28
+ */
29
+ import { constraintReview, engineReview, findingText, lowCardinalityReview, nullableReview, sortKeyReview, } from "../../clickhouse/review-catalog.js";
30
+ import { isPostgresObject, POSTGRES_ENTITY_TYPES } from "../../postgres/entities.js";
31
+ import { mapPostgresType } from "./types.js";
32
+ /** The names of the columns the pipeline adds, and what `CdcMirror` calls them by default. */
33
+ export const MIRROR_COLUMNS = { version: "_version", deleted: "_is_deleted" };
34
+ const DEFAULT_SCHEMA = "public";
35
+ const ident = (name) => (/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `\`${name.replace(/\\/g, "\\\\").replace(/`/g, "\\`")}\``);
36
+ const oneLine = (s) => s.replace(/\s+/g, " ").trim();
37
+ class Report {
38
+ constructor() {
39
+ this.diagnostics = [];
40
+ }
41
+ finding(f) {
42
+ this.diagnostics.push({ ruleId: f.ruleId, severity: f.severity, message: findingText(f), entity: f.object });
43
+ }
44
+ /** A finding of this file's own. `error` marks a table the translation refuses; the catalog's findings never are. */
45
+ note(ruleId, severity, object, message, evidence, column, suggestion) {
46
+ const text = findingText({ ruleId, severity: severity === "error" ? "warning" : severity, object, ...(column ? { column } : {}), message, evidence, ...(suggestion ? { suggestion } : {}) });
47
+ this.diagnostics.push({ ruleId, severity, message: text, entity: object });
48
+ }
49
+ }
50
+ const propsOf = (e) => e.props;
51
+ const display = (schema, name) => (schema ? `${schema}.${name}` : name);
52
+ /** The Postgres tables among `entities`, in the order given, with the export each is declared as. */
53
+ function tablesOf(entities) {
54
+ const out = [];
55
+ for (const [key, e] of entities)
56
+ if (isPostgresObject(e) && e.entityType === POSTGRES_ENTITY_TYPES.table)
57
+ out.push({ key, props: propsOf(e) });
58
+ return out;
59
+ }
60
+ /** Whether an index is on this table: its table is named bare, or with the schema, as the DDL wrote it. */
61
+ const tableMatches = (ix, t) => {
62
+ const target = ix.tableName.replace(/"/g, "");
63
+ return target === t.name || target === `${t.schema ?? DEFAULT_SCHEMA}.${t.name}`;
64
+ };
65
+ function translateTable(key, t, indexes, mirrorName, opts, report) {
66
+ const schema = t.schema ?? DEFAULT_SCHEMA;
67
+ const object = display(t.schema, t.name);
68
+ const pk = new Set(t.primaryKey?.columns ?? []);
69
+ for (const reserved of Object.values(MIRROR_COLUMNS)) {
70
+ if (t.columns.some((c) => c.name === reserved)) {
71
+ report.note("SQLRV011", "error", object, `The table is not carried: it already has a column named ${reserved}, which the mirror adds.`, [`column ${reserved} in the source`], reserved, "Rename the source column, or name the pipeline's columns otherwise in the CdcMirror call.");
72
+ return undefined;
73
+ }
74
+ }
75
+ if (opts.composite && !/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(mirrorName)) {
76
+ report.note("SQLRV011", "error", object, `The table is not carried: CdcMirror writes the name ${mirrorName} unquoted, and it needs quoting.`, [`the name ${mirrorName} is not a plain identifier`], undefined, "Emit without --use-composites to keep the quoted name, or rename the table.");
77
+ return undefined;
78
+ }
79
+ if (t.partitionOf !== undefined) {
80
+ report.note("SQLRV011", "info", object, "A partition is not carried; its rows reach the mirror through the parent table.", [`PARTITION OF ${typeof t.partitionOf === "string" ? t.partitionOf : "the parent"}`]);
81
+ return undefined;
82
+ }
83
+ const unread = [];
84
+ if (t.like.length > 0)
85
+ unread.push(...t.like.map((l) => `LIKE ${l}`));
86
+ if (t.inherits.length > 0)
87
+ unread.push(`INHERITS (${t.inherits.map((i) => (typeof i === "string" ? i : "a declared table")).join(", ")})`);
88
+ if (t.ofType !== undefined)
89
+ unread.push(`OF ${typeof t.ofType === "string" ? t.ofType : "a declared type"}`);
90
+ if (unread.length > 0) {
91
+ report.note("SQLRV011", "warning", object, "Columns that come from LIKE, INHERITS or OF are not read.", unread, undefined, "Add the columns to the CdcMirror call by hand, or declare them in the table.");
92
+ }
93
+ const columnTypes = {};
94
+ const columns = [];
95
+ const maxLengths = new Map();
96
+ const droppedStorage = [];
97
+ for (const col of t.columns) {
98
+ const m = mapPostgresType(col.type ?? "");
99
+ if (m.note)
100
+ report.note("SQLRV007", "warning", object, m.note.message, m.note.evidence, col.name);
101
+ if (m.maxLength !== undefined)
102
+ maxLengths.set(col.name, m.maxLength);
103
+ describeGenerated(col, m.serial === true, object, report);
104
+ if (col.default !== undefined && col.generated === undefined && !(m.serial && /nextval\(/i.test(col.default))) {
105
+ report.note("SQLRV008", "info", object, `The default of ${col.name} is not carried.`, [`DEFAULT ${oneLine(col.default)}`, "the pipeline writes every column of a row, so the source has already run the default"], col.name, "Add a ClickHouse DEFAULT if the table is also written by something other than the pipeline.");
106
+ }
107
+ if (col.collate) {
108
+ report.note("SQLRV007", "warning", object, `Collation ${col.collate} on ${col.name} is not carried.`, ["ClickHouse compares String values as bytes; a Postgres collation orders them by locale"], col.name);
109
+ }
110
+ if (col.compression)
111
+ droppedStorage.push(`column ${col.name} COMPRESSION ${col.compression}`);
112
+ if (col.storage)
113
+ droppedStorage.push(`column ${col.name} STORAGE ${col.storage}`);
114
+ // A primary key column, an identity column and a serial column are NOT NULL whatever the DDL says.
115
+ const forcedNotNull = pk.has(col.name) || col.generated?.kind === "identity" || m.serial === true;
116
+ const sourceNullable = m.array === true ? false : !(col.notNull === true || forcedNotNull);
117
+ const n = nullableReview({ object, column: col.name, type: m.type, sourceNullable, ...(col.default !== undefined && sourceNullable ? { defaultExpr: oneLine(col.default) } : {}) });
118
+ if (n.finding)
119
+ report.finding(n.finding);
120
+ columnTypes[col.name] = n.type;
121
+ const lc = lowCardinalityReview({ object, column: col.name, type: n.type, ...(m.maxLength !== undefined ? { maxLength: m.maxLength } : {}) });
122
+ if (lc)
123
+ report.finding(lc);
124
+ columns.push({ name: col.name, type: n.type, ...(col.comment ? { comment: col.comment } : {}) });
125
+ }
126
+ if (columns.length === 0) {
127
+ report.note("SQLRV011", "error", object, "The table is not carried: it declares no column.", ["the CREATE TABLE lists no column"]);
128
+ return undefined;
129
+ }
130
+ // Sort key: the primary key, else the first unique key.
131
+ const uniqueIndex = indexes.find((i) => i.unique === true && i.elements.every((e) => e.column !== undefined));
132
+ const candidates = [];
133
+ if (t.primaryKey && t.primaryKey.columns.length > 0) {
134
+ candidates.push({ kind: "primary-key", ...(t.primaryKey.name ? { name: t.primaryKey.name } : {}), columns: t.primaryKey.columns, source: `PRIMARY KEY${t.primaryKey.name ? ` ${t.primaryKey.name}` : ""}` });
135
+ }
136
+ const firstUnique = t.uniques[0];
137
+ if (firstUnique)
138
+ candidates.push({ kind: "unique-key", ...(firstUnique.name ? { name: firstUnique.name } : {}), columns: firstUnique.columns, source: `UNIQUE${firstUnique.name ? ` ${firstUnique.name}` : ""}` });
139
+ else if (uniqueIndex)
140
+ candidates.push({ kind: "unique-key", name: uniqueIndex.name, columns: uniqueIndex.elements.map((e) => e.column), source: `CREATE UNIQUE INDEX ${uniqueIndex.name}` });
141
+ const sortKey = sortKeyReview({ object, candidates: candidates.filter((c) => c.columns.every((c2) => c2 in columnTypes)), columnTypes });
142
+ report.finding(sortKey.finding);
143
+ if (opts.composite && Object.keys(sortKey.settings).length > 0) {
144
+ report.note("SQLRV001", "warning", object, `The CdcMirror call has no SETTINGS, so ${Object.keys(sortKey.settings).join(", ")} is not set.`, [`the sort key has a Nullable column and needs ${Object.entries(sortKey.settings).map(([k, v]) => `${k} = ${v}`).join(", ")}`], undefined, "Emit without --use-composites to keep the setting, or make the key columns NOT NULL in Postgres.");
145
+ }
146
+ const engine = engineReview({
147
+ object,
148
+ mode: "cdc",
149
+ cdcColumns: { version: MIRROR_COLUMNS.version, deleted: MIRROR_COLUMNS.deleted },
150
+ ...(sortKey.orderBy.length > 0 ? { uniqueKey: sortKey.orderBy } : {}),
151
+ facts: [`the table is declared in Postgres${t.persistence ? ` as ${t.persistence}` : ""}`],
152
+ });
153
+ report.finding(engine.finding);
154
+ // Constraints ClickHouse does not enforce.
155
+ const facts = [];
156
+ for (const fk of t.foreignKeys) {
157
+ facts.push({ kind: "foreign-key", ...(fk.name ? { name: fk.name } : {}), columns: fk.columns, references: { table: fk.refTable, columns: fk.refColumns } });
158
+ }
159
+ for (const u of t.uniques)
160
+ facts.push({ kind: "unique", ...(u.name ? { name: u.name } : {}), columns: u.columns });
161
+ for (const ix of indexes) {
162
+ if (ix.unique === true && ix.elements.every((e) => e.column !== undefined))
163
+ facts.push({ kind: "unique", name: ix.name, columns: ix.elements.map((e) => e.column) });
164
+ }
165
+ for (const c of t.checks)
166
+ facts.push({ kind: "check", ...(c.name ? { name: c.name } : {}), expression: c.expr });
167
+ for (const x of t.exclusions)
168
+ facts.push({ kind: "exclusion", ...(x.name ? { name: x.name } : {}), expression: `${x.using ? `USING ${x.using} ` : ""}(${x.elements})` });
169
+ const constraints = constraintReview({ object, constraints: facts });
170
+ for (const f of constraints.findings)
171
+ report.finding(f);
172
+ // Indexes that are not carried.
173
+ for (const ix of indexes) {
174
+ if (ix.unique === true && ix.elements.every((e) => e.column !== undefined))
175
+ continue;
176
+ const on = ix.elements.map((e) => e.column ?? oneLine(e.expr)).join(", ");
177
+ report.note("SQLRV009", "warning", object, `${ix.name}${ix.unique ? ", a unique index," : ", an index,"} on (${on}) is not carried.`, [`CREATE ${ix.unique ? "UNIQUE " : ""}INDEX ${ix.name}${ix.method ? ` USING ${ix.method}` : ""}${ix.where ? ` WHERE ${oneLine(ix.where)}` : ""}`], undefined, "The sort key is the only index a MergeTree table has by default. A skip index (minmax, set, bloom_filter) on the column can prune granules.");
178
+ }
179
+ // Storage the source chose.
180
+ const dropped = [...droppedStorage];
181
+ if (t.partitionBy)
182
+ dropped.push(`PARTITION BY ${oneLine(t.partitionBy)}`);
183
+ if (t.with)
184
+ dropped.push(`WITH ${oneLine(t.with)}`);
185
+ if (t.tablespace)
186
+ dropped.push(`TABLESPACE ${t.tablespace}`);
187
+ if (t.using)
188
+ dropped.push(`USING ${t.using}`);
189
+ if (t.persistence)
190
+ dropped.push(t.persistence.toUpperCase());
191
+ if (t.rowSecurity === true)
192
+ dropped.push("ROW LEVEL SECURITY");
193
+ if (dropped.length > 0) {
194
+ report.note("SQLRV009", "warning", object, "Storage options of the source table are not carried.", dropped, undefined, "Choose PARTITION BY for the mirror from how it is queried; a source partition is by the write path, a ClickHouse one by the read path.");
195
+ }
196
+ if (t.comment && opts.composite) {
197
+ report.note("SQLRV011", "info", object, "The table's description is not carried: the CdcMirror call has no place for it.", [`COMMENT ON TABLE: ${oneLine(t.comment)}`], undefined, "Emit without --use-composites to keep it as a COMMENT clause.");
198
+ }
199
+ return {
200
+ export: key,
201
+ name: mirrorName,
202
+ source: { schema, table: t.name },
203
+ columns,
204
+ primaryKey: sortKey.orderBy,
205
+ settings: sortKey.settings,
206
+ constraintComments: constraints.comments,
207
+ ...(t.comment && !opts.composite ? { comment: t.comment } : {}),
208
+ };
209
+ }
210
+ /** Identity, serial and generated columns: plain columns here, said once each. */
211
+ function describeGenerated(col, serial, object, report) {
212
+ if (col.generated?.kind === "identity") {
213
+ report.note("SQLRV006", "info", object, `Identity column ${col.name} is a plain column here.`, [`GENERATED ${col.generated.always ? "ALWAYS" : "BY DEFAULT"} AS IDENTITY${col.generated.options ? ` (${oneLine(col.generated.options)})` : ""}`, "ClickHouse has no auto-increment column, and the pipeline loads the value the source made"], col.name);
214
+ }
215
+ else if (col.generated !== undefined) {
216
+ report.note("SQLRV006", "warning", object, `Generated column ${col.name} is a plain column here.`, [`GENERATED ALWAYS AS (${oneLine(col.generated.expr ?? "")}) ${col.generated.kind.toUpperCase()}`, "the pipeline loads the stored value; a virtual column is not sent by every pipeline"], col.name, col.generated.kind === "virtual" ? "Add the column as ALIAS or MATERIALIZED with the expression rewritten in ClickHouse SQL, if the pipeline does not send it." : undefined);
217
+ }
218
+ else if (serial) {
219
+ report.note("SQLRV006", "info", object, `Serial column ${col.name} is a plain integer column here.`, ["serial draws from a sequence, which ClickHouse does not have, and the pipeline loads the value the source made"], col.name);
220
+ }
221
+ }
222
+ /** What an object of the source that is not a table is called in a finding. */
223
+ function describeObject(e) {
224
+ const props = propsOf(e);
225
+ return `${e.entityType.replace(/^Postgres::/, "").toLowerCase()} ${display(props.schema, props.name ?? "?")}`;
226
+ }
227
+ export function translatePostgres(entities, opts) {
228
+ const report = new Report();
229
+ const tables = tablesOf(entities);
230
+ const indexes = [...entities.values()].filter((e) => isPostgresObject(e) && e.entityType === POSTGRES_ENTITY_TYPES.index).map((e) => propsOf(e));
231
+ // Names: a table keeps its bare name unless another schema has a table of that name.
232
+ const counts = new Map();
233
+ for (const { props } of tables)
234
+ counts.set(props.name, (counts.get(props.name) ?? 0) + 1);
235
+ const schemas = new Set(tables.map(({ props }) => props.schema).filter((s) => s !== undefined && s !== DEFAULT_SCHEMA));
236
+ const mirrors = [];
237
+ for (const { key, props } of tables) {
238
+ const collides = (counts.get(props.name) ?? 0) > 1;
239
+ const name = collides ? display(props.schema ?? DEFAULT_SCHEMA, props.name) : props.name;
240
+ if (collides) {
241
+ report.note("SQLRV011", "info", display(props.schema, props.name), `The table keeps its schema as a database, ${name}, because two schemas declare a table of this name.`, ["the Postgres schema is otherwise not part of the mirror's name"]);
242
+ }
243
+ const mine = indexes.filter((ix) => tableMatches(ix, props));
244
+ const m = translateTable(key, props, mine, name, opts, report);
245
+ if (m)
246
+ mirrors.push(m);
247
+ }
248
+ for (const s of [...schemas].sort()) {
249
+ report.note("SQLRV011", "info", s, `Schema ${s} is not part of the mirror's name.`, ["a pipeline writes to one ClickHouse database, which the profile or the pipeline names"], undefined, `Create the tables in the database your pipeline targets (${s} is a common choice).`);
250
+ }
251
+ // Everything else the source declares: named once, with no decision to make.
252
+ for (const e of entities.values()) {
253
+ if (!isPostgresObject(e))
254
+ continue;
255
+ if (e.entityType === POSTGRES_ENTITY_TYPES.table || e.entityType === POSTGRES_ENTITY_TYPES.index || e.entityType === POSTGRES_ENTITY_TYPES.schema)
256
+ continue;
257
+ report.note("SQLRV011", "info", describeObject(e), `Not carried: ${describeObject(e)}.`, ["a mirror is made of tables; this object is not one"]);
258
+ }
259
+ const outputs = mirrors.flatMap((m) => m.columns.map((c) => `${m.source.schema}.${m.source.table}.${c.name}`));
260
+ return { mirrors, diagnostics: report.diagnostics, outputs };
261
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The Postgres to ClickHouse type map (#4399).
3
+ *
4
+ * A type either maps with no judgment (`note` absent) or carries a `note`
5
+ * naming what the map could not keep. Only the first kind is mechanical: the
6
+ * translator writes no diagnostic for it. A type this file does not know (an
7
+ * enum, a domain, an extension type) maps to `String` with a note, because a
8
+ * mirror still has to hold the value.
9
+ *
10
+ * Source types and defaults are from the Postgres data types chapter
11
+ * (https://www.postgresql.org/docs/current/datatype.html): `timestamp` and
12
+ * `time` take a precision of 0 to 6 and default to microseconds, `numeric`
13
+ * with no precision holds any value, `money` is a fixed-point type with a
14
+ * locale-set scale, `float(n)` is `real` for n up to 24 and `double
15
+ * precision` above.
16
+ *
17
+ * Target types are ClickHouse types of the pinned server (`../../spec/pin.ts`);
18
+ * `types.test.ts` checks every family this file writes against the generated
19
+ * catalog.
20
+ */
21
+ export interface MappedType {
22
+ ok: true;
23
+ /** The ClickHouse type, before `Nullable`. */
24
+ type: string;
25
+ /** The declared maximum length of a character type, when it has one. */
26
+ maxLength?: number;
27
+ /** The column is `serial`, `bigserial` or `smallserial`: an integer with a sequence behind it. */
28
+ serial?: boolean;
29
+ /** The column is an array. ClickHouse cannot make an `Array` `Nullable`. */
30
+ array?: boolean;
31
+ /** Present when the map is a judgment: what was lost or chosen. */
32
+ note?: {
33
+ message: string;
34
+ evidence: string[];
35
+ };
36
+ }
37
+ export type TypeMapping = MappedType;
38
+ interface ParsedType {
39
+ /** Lower case words of the type, schema prefix and quotes removed: `timestamp with time zone`. */
40
+ base: string;
41
+ args: number[];
42
+ /** Array dimensions. */
43
+ dims: number;
44
+ raw: string;
45
+ }
46
+ /** Split a column type as written into its words, its parenthesised arguments and its array dimensions. */
47
+ export declare function parseType(text: string): ParsedType;
48
+ /** Map a column type as the declaration wrote it. */
49
+ export declare function mapPostgresType(text: string): TypeMapping;
50
+ export {};
51
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/migrate/from-postgres/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,IAAI,CAAC;IACT,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kGAAkG;IAClG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAChD;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC;AAErC,UAAU,UAAU;IAClB,kGAAkG;IAClG,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,2GAA2G;AAC3G,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CA6BlD;AA0ID,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAoBzD"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The Postgres to ClickHouse type map (#4399).
3
+ *
4
+ * A type either maps with no judgment (`note` absent) or carries a `note`
5
+ * naming what the map could not keep. Only the first kind is mechanical: the
6
+ * translator writes no diagnostic for it. A type this file does not know (an
7
+ * enum, a domain, an extension type) maps to `String` with a note, because a
8
+ * mirror still has to hold the value.
9
+ *
10
+ * Source types and defaults are from the Postgres data types chapter
11
+ * (https://www.postgresql.org/docs/current/datatype.html): `timestamp` and
12
+ * `time` take a precision of 0 to 6 and default to microseconds, `numeric`
13
+ * with no precision holds any value, `money` is a fixed-point type with a
14
+ * locale-set scale, `float(n)` is `real` for n up to 24 and `double
15
+ * precision` above.
16
+ *
17
+ * Target types are ClickHouse types of the pinned server (`../../spec/pin.ts`);
18
+ * `types.test.ts` checks every family this file writes against the generated
19
+ * catalog.
20
+ */
21
+ /** Split a column type as written into its words, its parenthesised arguments and its array dimensions. */
22
+ export function parseType(text) {
23
+ let rest = text.trim();
24
+ let dims = 0;
25
+ for (;;) {
26
+ const m = /\s*\[\s*\d*\s*\]\s*$/.exec(rest);
27
+ if (!m)
28
+ break;
29
+ rest = rest.slice(0, m.index);
30
+ dims++;
31
+ }
32
+ const arrayWord = /\s+array(\s*\[\s*\d*\s*\])?$/i.exec(rest);
33
+ if (arrayWord) {
34
+ rest = rest.slice(0, arrayWord.index);
35
+ dims++;
36
+ }
37
+ const args = [];
38
+ rest = rest.replace(/\(([^)]*)\)/g, (_all, inner) => {
39
+ for (const part of inner.split(",")) {
40
+ const n = Number(part.trim());
41
+ if (Number.isFinite(n))
42
+ args.push(n);
43
+ }
44
+ return " ";
45
+ });
46
+ const base = rest
47
+ .replace(/"/g, "")
48
+ .replace(/^pg_catalog\./i, "")
49
+ .replace(/\s+/g, " ")
50
+ .trim()
51
+ .toLowerCase();
52
+ return { base, args, dims, raw: text.trim() };
53
+ }
54
+ const STRING_NOTE = (what, evidence) => ({ message: `${what} is read as String.`, evidence });
55
+ function mapScalar(t) {
56
+ const { base, args } = t;
57
+ switch (base) {
58
+ case "smallint":
59
+ case "int2":
60
+ return { ok: true, type: "Int16" };
61
+ case "integer":
62
+ case "int":
63
+ case "int4":
64
+ return { ok: true, type: "Int32" };
65
+ case "bigint":
66
+ case "int8":
67
+ return { ok: true, type: "Int64" };
68
+ case "smallserial":
69
+ case "serial2":
70
+ return { ok: true, type: "Int16", serial: true };
71
+ case "serial":
72
+ case "serial4":
73
+ return { ok: true, type: "Int32", serial: true };
74
+ case "bigserial":
75
+ case "serial8":
76
+ return { ok: true, type: "Int64", serial: true };
77
+ case "real":
78
+ case "float4":
79
+ return { ok: true, type: "Float32" };
80
+ case "double precision":
81
+ case "float8":
82
+ return { ok: true, type: "Float64" };
83
+ case "float":
84
+ return { ok: true, type: (args[0] ?? 53) <= 24 ? "Float32" : "Float64" };
85
+ case "numeric":
86
+ case "decimal": {
87
+ if (args.length === 0) {
88
+ return {
89
+ ok: true,
90
+ type: "Decimal(38, 18)",
91
+ note: {
92
+ message: "numeric with no precision is read as Decimal(38, 18).",
93
+ evidence: ["Postgres stores up to 131072 digits before the point and 16383 after; a ClickHouse Decimal has a fixed precision and scale", "a value that does not fit fails the insert"],
94
+ },
95
+ };
96
+ }
97
+ const p = args[0];
98
+ const s = args[1] ?? 0;
99
+ if (p > 76 || s < 0 || s > p) {
100
+ return {
101
+ ok: true,
102
+ type: "Decimal(76, 38)",
103
+ note: { message: `numeric(${String(p)}, ${String(s)}) is read as Decimal(76, 38).`, evidence: ["ClickHouse Decimal holds at most 76 digits"] },
104
+ };
105
+ }
106
+ return { ok: true, type: `Decimal(${String(p)}, ${String(s)})` };
107
+ }
108
+ case "money":
109
+ return {
110
+ ok: true,
111
+ type: "Decimal(19, 2)",
112
+ note: { message: "money is read as Decimal(19, 2).", evidence: ["money takes its scale from the lc_monetary setting of the source server, and two decimals is the common one"] },
113
+ };
114
+ case "boolean":
115
+ case "bool":
116
+ return { ok: true, type: "Bool" };
117
+ case "text":
118
+ case "varchar":
119
+ case "character varying":
120
+ case "name":
121
+ case "citext":
122
+ return { ok: true, type: "String", ...(args[0] !== undefined ? { maxLength: args[0] } : {}) };
123
+ case "char":
124
+ case "character":
125
+ case "bpchar":
126
+ return {
127
+ ok: true,
128
+ type: "String",
129
+ ...(args[0] !== undefined ? { maxLength: args[0] } : {}),
130
+ ...(args[0] !== undefined && args[0] > 1 ? { note: STRING_NOTE("char(n)", ["Postgres pads a char(n) value with spaces to n characters; a String keeps the value as it is loaded"]) } : {}),
131
+ };
132
+ case "bytea":
133
+ return { ok: true, type: "String" };
134
+ case "uuid":
135
+ return { ok: true, type: "UUID" };
136
+ case "date":
137
+ return { ok: true, type: "Date32" };
138
+ case "time":
139
+ case "time without time zone":
140
+ return { ok: true, type: `Time64(${String(args[0] ?? 6)})` };
141
+ case "timetz":
142
+ case "time with time zone":
143
+ return { ok: true, type: "String", note: STRING_NOTE("timetz", ["a time of day with an offset has no ClickHouse type; the offset is kept in the text"]) };
144
+ case "timestamp":
145
+ case "timestamp without time zone":
146
+ return { ok: true, type: `DateTime64(${String(args[0] ?? 6)})` };
147
+ case "timestamptz":
148
+ case "timestamp with time zone":
149
+ return { ok: true, type: `DateTime64(${String(args[0] ?? 6)}, 'UTC')` };
150
+ case "interval":
151
+ return { ok: true, type: "String", note: STRING_NOTE("interval", ["a ClickHouse Interval type cannot be stored in a column", "the value is kept as Postgres prints it"]) };
152
+ case "json":
153
+ case "jsonb":
154
+ return { ok: true, type: "JSON" };
155
+ case "oid":
156
+ return { ok: true, type: "UInt32" };
157
+ case "inet":
158
+ case "cidr":
159
+ return {
160
+ ok: true,
161
+ type: "IPv6",
162
+ note: { message: `${base} is read as IPv6.`, evidence: ["an IPv6 column holds an IPv4 address in its mapped form", "the prefix length of a network is not kept"] },
163
+ };
164
+ case "xml":
165
+ case "macaddr":
166
+ case "macaddr8":
167
+ case "tsvector":
168
+ case "tsquery":
169
+ case "bit":
170
+ case "bit varying":
171
+ case "varbit":
172
+ case "point":
173
+ case "line":
174
+ case "lseg":
175
+ case "box":
176
+ case "path":
177
+ case "polygon":
178
+ case "circle":
179
+ return { ok: true, type: "String", note: STRING_NOTE(base, [`ClickHouse has no ${base} type`, "the value is kept as Postgres prints it"]) };
180
+ default:
181
+ return {
182
+ ok: true,
183
+ type: "String",
184
+ note: STRING_NOTE(t.raw, [`${t.raw} is not a built-in type chant maps (an enum, a domain, a composite or an extension type)`, "the value is kept as Postgres prints it; an enum could be an Enum8 or Enum16 once its labels are settled"]),
185
+ };
186
+ }
187
+ }
188
+ /** Map a column type as the declaration wrote it. */
189
+ export function mapPostgresType(text) {
190
+ const t = parseType(text);
191
+ const inner = mapScalar(t);
192
+ if (t.dims === 0)
193
+ return inner;
194
+ let type = inner.type;
195
+ for (let i = 0; i < t.dims; i++)
196
+ type = `Array(${type})`;
197
+ return {
198
+ ok: true,
199
+ type,
200
+ array: true,
201
+ ...(inner.serial ? { serial: true } : {}),
202
+ note: {
203
+ message: `${t.raw} is read as ${type}.`,
204
+ evidence: [
205
+ ...(inner.note ? [inner.note.message] : []),
206
+ "ClickHouse cannot make an Array Nullable, so a NULL array is stored as an empty one",
207
+ "Postgres does not enforce the declared dimensions or lengths of an array",
208
+ ],
209
+ },
210
+ };
211
+ }