@intentius/chant-lexicon-sql 0.124.46 → 0.124.49

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 (402) 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/statements.d.ts +29 -0
  37. package/dist/clickhouse/plan/statements.d.ts.map +1 -0
  38. package/dist/clickhouse/plan/statements.js +334 -0
  39. package/dist/clickhouse/plan/stream.d.ts +40 -0
  40. package/dist/clickhouse/plan/stream.d.ts.map +1 -0
  41. package/dist/clickhouse/plan/stream.js +107 -0
  42. package/dist/clickhouse/rebuild/observe.d.ts +3 -1
  43. package/dist/clickhouse/rebuild/observe.d.ts.map +1 -1
  44. package/dist/clickhouse/rebuild/observe.js +14 -1
  45. package/dist/clickhouse/review-catalog.d.ts +213 -0
  46. package/dist/clickhouse/review-catalog.d.ts.map +1 -0
  47. package/dist/clickhouse/review-catalog.js +330 -0
  48. package/dist/codegen/classifier-page.d.ts.map +1 -1
  49. package/dist/codegen/classifier-page.js +44 -4
  50. package/dist/codegen/clickhouse-module.d.ts.map +1 -1
  51. package/dist/codegen/clickhouse-module.js +3 -0
  52. package/dist/codegen/coverage-page.d.ts.map +1 -1
  53. package/dist/codegen/coverage-page.js +5 -3
  54. package/dist/codegen/docs.js +1 -1
  55. package/dist/codegen/postgres-classifier-page.js +2 -2
  56. package/dist/codegen/registry.d.ts.map +1 -1
  57. package/dist/codegen/registry.js +10 -0
  58. package/dist/composites/catalog.d.ts.map +1 -1
  59. package/dist/composites/catalog.js +100 -0
  60. package/dist/composites/clickhouse.d.ts +2 -0
  61. package/dist/composites/clickhouse.d.ts.map +1 -1
  62. package/dist/composites/clickhouse.js +2 -0
  63. package/dist/composites/otel-tables.d.ts +100 -0
  64. package/dist/composites/otel-tables.d.ts.map +1 -0
  65. package/dist/composites/otel-tables.js +282 -0
  66. package/dist/composites/stream-ingest.d.ts +63 -0
  67. package/dist/composites/stream-ingest.d.ts.map +1 -0
  68. package/dist/composites/stream-ingest.js +39 -0
  69. package/dist/core/statement-class.d.ts +55 -0
  70. package/dist/core/statement-class.d.ts.map +1 -0
  71. package/dist/core/statement-class.js +43 -0
  72. package/dist/coverage/clickhouse.json +1 -1
  73. package/dist/coverage/postgres.json +12 -12
  74. package/dist/coverage.d.ts +16 -6
  75. package/dist/coverage.d.ts.map +1 -1
  76. package/dist/coverage.js +10 -5
  77. package/dist/drizzle-command.d.ts +17 -0
  78. package/dist/drizzle-command.d.ts.map +1 -0
  79. package/dist/drizzle-command.js +91 -0
  80. package/dist/drizzle.d.ts +70 -0
  81. package/dist/drizzle.d.ts.map +1 -0
  82. package/dist/drizzle.js +689 -0
  83. package/dist/files/clickhouse.d.ts.map +1 -1
  84. package/dist/files/clickhouse.js +8 -1
  85. package/dist/files/common.d.ts +13 -0
  86. package/dist/files/common.d.ts.map +1 -1
  87. package/dist/files/common.js +5 -0
  88. package/dist/files/postgres.d.ts.map +1 -1
  89. package/dist/files/postgres.js +35 -3
  90. package/dist/generated/clickhouse.d.ts +2 -0
  91. package/dist/generated/clickhouse.d.ts.map +1 -1
  92. package/dist/generated/clickhouse.js +2 -0
  93. package/dist/import-parser.d.ts +21 -1
  94. package/dist/import-parser.d.ts.map +1 -1
  95. package/dist/import-parser.js +79 -5
  96. package/dist/index.d.ts +1 -1
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +1 -1
  99. package/dist/integrity.json +7 -6
  100. package/dist/lint/post-synth/audit-entries.d.ts.map +1 -1
  101. package/dist/lint/post-synth/audit-entries.js +1 -0
  102. package/dist/lint/post-synth/index.d.ts.map +1 -1
  103. package/dist/lint/post-synth/index.js +2 -0
  104. package/dist/lint/post-synth/postgres-names.d.ts.map +1 -1
  105. package/dist/lint/post-synth/postgres-names.js +3 -0
  106. package/dist/lint/post-synth/sqlch128.d.ts +18 -0
  107. package/dist/lint/post-synth/sqlch128.d.ts.map +1 -0
  108. package/dist/lint/post-synth/sqlch128.js +222 -0
  109. package/dist/lint/rules/postgres-templates.js +2 -2
  110. package/dist/lsp/postgres-template.d.ts.map +1 -1
  111. package/dist/lsp/postgres-template.js +21 -0
  112. package/dist/manifest.json +1 -1
  113. package/dist/mcp/postgres.js +1 -1
  114. package/dist/meta.json +50 -0
  115. package/dist/migrate/from-postgres/detect.d.ts +4 -0
  116. package/dist/migrate/from-postgres/detect.d.ts.map +1 -0
  117. package/dist/migrate/from-postgres/detect.js +7 -0
  118. package/dist/migrate/from-postgres/emit.d.ts +26 -0
  119. package/dist/migrate/from-postgres/emit.d.ts.map +1 -0
  120. package/dist/migrate/from-postgres/emit.js +86 -0
  121. package/dist/migrate/from-postgres/index.d.ts +51 -0
  122. package/dist/migrate/from-postgres/index.d.ts.map +1 -0
  123. package/dist/migrate/from-postgres/index.js +115 -0
  124. package/dist/migrate/from-postgres/translate.d.ts +73 -0
  125. package/dist/migrate/from-postgres/translate.d.ts.map +1 -0
  126. package/dist/migrate/from-postgres/translate.js +261 -0
  127. package/dist/migrate/from-postgres/types.d.ts +51 -0
  128. package/dist/migrate/from-postgres/types.d.ts.map +1 -0
  129. package/dist/migrate/from-postgres/types.js +211 -0
  130. package/dist/migration-classify.d.ts +23 -0
  131. package/dist/migration-classify.d.ts.map +1 -0
  132. package/dist/migration-classify.js +44 -0
  133. package/dist/migration-replay.d.ts +73 -0
  134. package/dist/migration-replay.d.ts.map +1 -0
  135. package/dist/migration-replay.js +294 -0
  136. package/dist/okf/index.md +11 -0
  137. package/dist/okf/rules/SQLCH128.md +11 -0
  138. package/dist/okf/types/PostgresForeignDataWrapper.md +9 -0
  139. package/dist/okf/types/PostgresForeignServer.md +9 -0
  140. package/dist/okf/types/PostgresForeignTable.md +9 -0
  141. package/dist/okf/types/PostgresOperator.md +9 -0
  142. package/dist/okf/types/PostgresOperatorClass.md +9 -0
  143. package/dist/okf/types/PostgresOperatorFamily.md +9 -0
  144. package/dist/okf/types/PostgresPublication.md +9 -0
  145. package/dist/okf/types/PostgresStatistics.md +9 -0
  146. package/dist/okf/types/PostgresTextSearchConfiguration.md +9 -0
  147. package/dist/okf/types/PostgresTextSearchDictionary.md +9 -0
  148. package/dist/op/activities/postgres-migration.d.ts.map +1 -1
  149. package/dist/op/activities/postgres-migration.js +4 -0
  150. package/dist/op/activity-contracts.d.ts +20 -0
  151. package/dist/op/activity-contracts.d.ts.map +1 -1
  152. package/dist/op/activity-contracts.js +2 -0
  153. package/dist/plugin.d.ts.map +1 -1
  154. package/dist/plugin.js +49 -2
  155. package/dist/postgres/access/acl.js +1 -1
  156. package/dist/postgres/access/import.js +1 -1
  157. package/dist/postgres/apply/apply.js +13 -1
  158. package/dist/postgres/apply/statements.d.ts.map +1 -1
  159. package/dist/postgres/apply/statements.js +243 -4
  160. package/dist/postgres/constraint-names.d.ts +30 -0
  161. package/dist/postgres/constraint-names.d.ts.map +1 -0
  162. package/dist/postgres/constraint-names.js +72 -0
  163. package/dist/postgres/entities.d.ts +229 -2
  164. package/dist/postgres/entities.d.ts.map +1 -1
  165. package/dist/postgres/entities.js +326 -5
  166. package/dist/postgres/entity-types.d.ts +10 -0
  167. package/dist/postgres/entity-types.d.ts.map +1 -1
  168. package/dist/postgres/entity-types.js +10 -0
  169. package/dist/postgres/import/generator.d.ts.map +1 -1
  170. package/dist/postgres/import/generator.js +65 -2
  171. package/dist/postgres/import/live-export.d.ts.map +1 -1
  172. package/dist/postgres/import/live-export.js +2 -0
  173. package/dist/postgres/live/catalog-kinds.d.ts +20 -0
  174. package/dist/postgres/live/catalog-kinds.d.ts.map +1 -1
  175. package/dist/postgres/live/catalog-kinds.js +452 -0
  176. package/dist/postgres/live/catalog.d.ts +2 -0
  177. package/dist/postgres/live/catalog.d.ts.map +1 -1
  178. package/dist/postgres/live/catalog.js +3 -1
  179. package/dist/postgres/live/describe-resources.d.ts.map +1 -1
  180. package/dist/postgres/live/describe-resources.js +12 -2
  181. package/dist/postgres/migrate/backfill.d.ts +2 -0
  182. package/dist/postgres/migrate/backfill.d.ts.map +1 -1
  183. package/dist/postgres/migrate/backfill.js +7 -1
  184. package/dist/postgres/migrate/carry.d.ts +45 -1
  185. package/dist/postgres/migrate/carry.d.ts.map +1 -1
  186. package/dist/postgres/migrate/carry.js +72 -8
  187. package/dist/postgres/migrate/observe.d.ts +28 -4
  188. package/dist/postgres/migrate/observe.d.ts.map +1 -1
  189. package/dist/postgres/migrate/observe.js +45 -13
  190. package/dist/postgres/migrate/op.d.ts +26 -1
  191. package/dist/postgres/migrate/op.d.ts.map +1 -1
  192. package/dist/postgres/migrate/op.js +3 -1
  193. package/dist/postgres/migrate/replication.d.ts +42 -0
  194. package/dist/postgres/migrate/replication.d.ts.map +1 -1
  195. package/dist/postgres/migrate/replication.js +53 -0
  196. package/dist/postgres/migrate/steps.d.ts +35 -2
  197. package/dist/postgres/migrate/steps.d.ts.map +1 -1
  198. package/dist/postgres/migrate/steps.js +158 -12
  199. package/dist/postgres/parser.d.ts +144 -1
  200. package/dist/postgres/parser.d.ts.map +1 -1
  201. package/dist/postgres/parser.js +370 -4
  202. package/dist/postgres/plan/deep.d.ts.map +1 -1
  203. package/dist/postgres/plan/deep.js +43 -4
  204. package/dist/postgres/plan/diff.d.ts.map +1 -1
  205. package/dist/postgres/plan/diff.js +154 -1
  206. package/dist/postgres/plan/normalize.d.ts +28 -2
  207. package/dist/postgres/plan/normalize.d.ts.map +1 -1
  208. package/dist/postgres/plan/normalize.js +260 -1
  209. package/dist/postgres/plan/rules.d.ts +4 -0
  210. package/dist/postgres/plan/rules.d.ts.map +1 -1
  211. package/dist/postgres/plan/rules.js +6 -2
  212. package/dist/postgres/plan/schema.js +1 -1
  213. package/dist/postgres/plan/statements.d.ts +26 -0
  214. package/dist/postgres/plan/statements.d.ts.map +1 -0
  215. package/dist/postgres/plan/statements.js +364 -0
  216. package/dist/postgres/secret-options.d.ts +10 -0
  217. package/dist/postgres/secret-options.d.ts.map +1 -0
  218. package/dist/postgres/secret-options.js +9 -0
  219. package/dist/postgres/testing/roundtrip-schema.d.ts +4 -2
  220. package/dist/postgres/testing/roundtrip-schema.d.ts.map +1 -1
  221. package/dist/postgres/testing/roundtrip-schema.js +24 -1
  222. package/dist/postgres.d.ts +1 -1
  223. package/dist/postgres.d.ts.map +1 -1
  224. package/dist/postgres.js +1 -1
  225. package/dist/rules/audit-entries.ts +1 -0
  226. package/dist/rules/postgres-names.ts +2 -0
  227. package/dist/rules/postgres-templates.ts +2 -2
  228. package/dist/rules/sqlch128.ts +230 -0
  229. package/dist/serializer.d.ts +5 -0
  230. package/dist/serializer.d.ts.map +1 -1
  231. package/dist/serializer.js +13 -2
  232. package/dist/sql-types-command.d.ts +13 -0
  233. package/dist/sql-types-command.d.ts.map +1 -1
  234. package/dist/sql-types-command.js +31 -16
  235. package/dist/sql-types.d.ts +22 -0
  236. package/dist/sql-types.d.ts.map +1 -1
  237. package/dist/sql-types.js +10 -6
  238. package/dist/sqlserver/lexer.d.ts +37 -0
  239. package/dist/sqlserver/lexer.d.ts.map +1 -0
  240. package/dist/sqlserver/lexer.js +254 -0
  241. package/dist/sqlserver/parse.d.ts +185 -0
  242. package/dist/sqlserver/parse.d.ts.map +1 -0
  243. package/dist/sqlserver/parse.js +767 -0
  244. package/dist/sqlserver/translate.d.ts +34 -0
  245. package/dist/sqlserver/translate.d.ts.map +1 -0
  246. package/dist/sqlserver/translate.js +495 -0
  247. package/dist/sqlserver/types.d.ts +44 -0
  248. package/dist/sqlserver/types.d.ts.map +1 -0
  249. package/dist/sqlserver/types.js +133 -0
  250. package/dist/sqlserver/validate.d.ts +31 -0
  251. package/dist/sqlserver/validate.d.ts.map +1 -0
  252. package/dist/sqlserver/validate.js +67 -0
  253. package/dist/validate.d.ts.map +1 -1
  254. package/dist/validate.js +10 -0
  255. package/package.json +2 -2
  256. package/src/change-set.ts +2 -1
  257. package/src/clickhouse/apply/statements.ts +25 -0
  258. package/src/clickhouse/import/live-export.ts +20 -5
  259. package/src/clickhouse/live/named-collections.e2e.test.ts +88 -0
  260. package/src/clickhouse/live/named-collections.ts +61 -0
  261. package/src/clickhouse/live/undeclarable.e2e.test.ts +5 -3
  262. package/src/clickhouse/live/undeclarable.ts +20 -4
  263. package/src/clickhouse/named-collections.test.ts +187 -0
  264. package/src/clickhouse/named-collections.ts +224 -0
  265. package/src/clickhouse/parser.ts +2 -1
  266. package/src/clickhouse/plan/commands.ts +17 -2
  267. package/src/clickhouse/plan/diff.ts +57 -1
  268. package/src/clickhouse/plan/disruption.ts +5 -0
  269. package/src/clickhouse/plan/rebuild-handoff.ts +3 -0
  270. package/src/clickhouse/plan/report.ts +6 -1
  271. package/src/clickhouse/plan/rules.ts +14 -4
  272. package/src/clickhouse/plan/statements.test.ts +61 -0
  273. package/src/clickhouse/plan/statements.ts +306 -0
  274. package/src/clickhouse/plan/stream-s3queue.e2e.test.ts +148 -0
  275. package/src/clickhouse/plan/stream.test.ts +237 -0
  276. package/src/clickhouse/plan/stream.ts +111 -0
  277. package/src/clickhouse/rebuild/observe.ts +15 -1
  278. package/src/clickhouse/review-catalog.test.ts +181 -0
  279. package/src/clickhouse/review-catalog.ts +489 -0
  280. package/src/codegen/classifier-page.ts +44 -4
  281. package/src/codegen/clickhouse-module.ts +3 -0
  282. package/src/codegen/coverage-page.ts +5 -3
  283. package/src/codegen/docs.ts +1 -1
  284. package/src/codegen/postgres-classifier-page.ts +2 -2
  285. package/src/codegen/registry.ts +10 -0
  286. package/src/composites/catalog.test.ts +6 -1
  287. package/src/composites/catalog.ts +100 -0
  288. package/src/composites/clickhouse.ts +2 -0
  289. package/src/composites/composites.test.ts +1 -1
  290. package/src/composites/otel-tables.test.ts +118 -0
  291. package/src/composites/otel-tables.ts +339 -0
  292. package/src/composites/stream-ingest.test.ts +114 -0
  293. package/src/composites/stream-ingest.ts +82 -0
  294. package/src/core/statement-class.ts +88 -0
  295. package/src/core/undeclarable.test.ts +6 -5
  296. package/src/coverage/clickhouse.json +1 -1
  297. package/src/coverage/postgres.json +12 -12
  298. package/src/coverage.test.ts +27 -2
  299. package/src/coverage.ts +18 -7
  300. package/src/drizzle-command.ts +94 -0
  301. package/src/drizzle-kit.e2e.test.ts +89 -0
  302. package/src/drizzle-types.e2e.test.ts +67 -0
  303. package/src/drizzle.test.ts +358 -0
  304. package/src/drizzle.ts +742 -0
  305. package/src/files/clickhouse.ts +8 -1
  306. package/src/files/common.ts +14 -1
  307. package/src/files/postgres.ts +34 -3
  308. package/src/generated/clickhouse.ts +3 -0
  309. package/src/generated/lexicon-sql.json +50 -0
  310. package/src/import-parser.sqlserver.test.ts +92 -0
  311. package/src/import-parser.ts +79 -5
  312. package/src/index.ts +1 -1
  313. package/src/lint/post-synth/audit-entries.ts +1 -0
  314. package/src/lint/post-synth/index.ts +2 -0
  315. package/src/lint/post-synth/postgres-checks.test.ts +8 -1
  316. package/src/lint/post-synth/postgres-names.ts +2 -0
  317. package/src/lint/post-synth/sqlch128.test.ts +107 -0
  318. package/src/lint/post-synth/sqlch128.ts +230 -0
  319. package/src/lint/rules/postgres-templates.ts +2 -2
  320. package/src/lsp/postgres-template.ts +20 -0
  321. package/src/mcp/postgres.ts +1 -1
  322. package/src/migrate/from-postgres/cli.test.ts +165 -0
  323. package/src/migrate/from-postgres/detect.ts +7 -0
  324. package/src/migrate/from-postgres/emit.ts +96 -0
  325. package/src/migrate/from-postgres/example.test.ts +205 -0
  326. package/src/migrate/from-postgres/index.ts +122 -0
  327. package/src/migrate/from-postgres/translate.test.ts +242 -0
  328. package/src/migrate/from-postgres/translate.ts +359 -0
  329. package/src/migrate/from-postgres/types.test.ts +116 -0
  330. package/src/migrate/from-postgres/types.ts +236 -0
  331. package/src/migrate/from-postgres/validate.e2e.test.ts +44 -0
  332. package/src/migrate/from-postgres/workspace.e2e.test.ts +114 -0
  333. package/src/migration-classify.test.ts +150 -0
  334. package/src/migration-classify.ts +45 -0
  335. package/src/migration-replay.e2e.test.ts +155 -0
  336. package/src/migration-replay.test.ts +173 -0
  337. package/src/migration-replay.ts +289 -0
  338. package/src/op/__snapshots__/watch-pipeline.test.ts.snap +2 -2
  339. package/src/op/activities/postgres-migration.ts +4 -0
  340. package/src/op/activity-contracts.ts +2 -0
  341. package/src/op/watch-pipeline.test.ts +8 -1
  342. package/src/plugin.ts +51 -2
  343. package/src/postgres/access/acl.ts +1 -1
  344. package/src/postgres/access/import.ts +1 -1
  345. package/src/postgres/apply/apply.ts +14 -1
  346. package/src/postgres/apply/statements.ts +225 -4
  347. package/src/postgres/constraint-names.ts +71 -0
  348. package/src/postgres/database-kinds.test.ts +865 -0
  349. package/src/postgres/entities.ts +541 -5
  350. package/src/postgres/entity-types.ts +10 -0
  351. package/src/postgres/import/generator.ts +54 -2
  352. package/src/postgres/import/live-export.ts +1 -0
  353. package/src/postgres/live/catalog-kinds.ts +494 -0
  354. package/src/postgres/live/catalog.ts +6 -1
  355. package/src/postgres/live/database-kinds.e2e.test.ts +164 -0
  356. package/src/postgres/live/describe-resources.ts +13 -2
  357. package/src/postgres/live/undeclarable.e2e.test.ts +13 -6
  358. package/src/postgres/migrate/backfill.ts +13 -1
  359. package/src/postgres/migrate/carry.ts +103 -8
  360. package/src/postgres/migrate/followups.e2e.test.ts +218 -0
  361. package/src/postgres/migrate/followups.test.ts +199 -0
  362. package/src/postgres/migrate/kinds.e2e.test.ts +5 -5
  363. package/src/postgres/migrate/observe.ts +56 -15
  364. package/src/postgres/migrate/op.ts +28 -1
  365. package/src/postgres/migrate/partition.e2e.test.ts +129 -0
  366. package/src/postgres/migrate/partition.test.ts +206 -0
  367. package/src/postgres/migrate/replication.ts +66 -0
  368. package/src/postgres/migrate/steps.ts +157 -12
  369. package/src/postgres/migrate/subscriber.e2e.test.ts +141 -0
  370. package/src/postgres/migrate/subscriber.test.ts +59 -0
  371. package/src/postgres/parser.ts +455 -4
  372. package/src/postgres/plan/deep.ts +42 -4
  373. package/src/postgres/plan/diff.ts +137 -1
  374. package/src/postgres/plan/normalize.ts +277 -1
  375. package/src/postgres/plan/rules.ts +32 -4
  376. package/src/postgres/plan/schema.ts +1 -1
  377. package/src/postgres/plan/statements.test.ts +78 -0
  378. package/src/postgres/plan/statements.ts +299 -0
  379. package/src/postgres/secret-options.ts +10 -0
  380. package/src/postgres/testing/roundtrip-schema.ts +24 -1
  381. package/src/postgres.ts +33 -0
  382. package/src/serializer.ts +13 -2
  383. package/src/skills/chant-sql-postgres-migration.md +4 -3
  384. package/src/skills/chant-sql-postgres.md +11 -1
  385. package/src/skills/chant-sql.md +2 -1
  386. package/src/sql-files.test.ts +33 -0
  387. package/src/sql-types-command.ts +38 -16
  388. package/src/sql-types.ts +11 -6
  389. package/src/sqlserver/fixture.test.ts +186 -0
  390. package/src/sqlserver/import-cli.test.ts +61 -0
  391. package/src/sqlserver/lexer.ts +260 -0
  392. package/src/sqlserver/parse.ts +865 -0
  393. package/src/sqlserver/translate.test.ts +396 -0
  394. package/src/sqlserver/translate.ts +558 -0
  395. package/src/sqlserver/types.test.ts +102 -0
  396. package/src/sqlserver/types.ts +154 -0
  397. package/src/sqlserver/validate.e2e.test.ts +32 -0
  398. package/src/sqlserver/validate.test.ts +49 -0
  399. package/src/sqlserver/validate.ts +76 -0
  400. package/src/validate.ts +10 -0
  401. package/src/workspace-kinds.test.ts +11 -0
  402. package/workspace-kinds.json +100 -2
@@ -18,6 +18,16 @@ export const POSTGRES_ENTITY_TYPES = {
18
18
  collation: "Postgres::Collation",
19
19
  cast: "Postgres::Cast",
20
20
  eventTrigger: "Postgres::EventTrigger",
21
+ operator: "Postgres::Operator",
22
+ operatorClass: "Postgres::OperatorClass",
23
+ operatorFamily: "Postgres::OperatorFamily",
24
+ foreignTable: "Postgres::ForeignTable",
25
+ textSearchConfiguration: "Postgres::TextSearchConfiguration",
26
+ textSearchDictionary: "Postgres::TextSearchDictionary",
27
+ statistics: "Postgres::Statistics",
28
+ foreignDataWrapper: "Postgres::ForeignDataWrapper",
29
+ foreignServer: "Postgres::ForeignServer",
30
+ publication: "Postgres::Publication",
21
31
  extension: "Postgres::Extension",
22
32
  function: "Postgres::Function",
23
33
  procedure: "Postgres::Procedure",
@@ -60,6 +60,16 @@ const TAG: Record<string, string> = {
60
60
  [POSTGRES_ENTITY_TYPES.collation]: "collation",
61
61
  [POSTGRES_ENTITY_TYPES.cast]: "cast",
62
62
  [POSTGRES_ENTITY_TYPES.eventTrigger]: "eventTrigger",
63
+ [POSTGRES_ENTITY_TYPES.operator]: "operator",
64
+ [POSTGRES_ENTITY_TYPES.operatorClass]: "operatorClass",
65
+ [POSTGRES_ENTITY_TYPES.operatorFamily]: "operatorFamily",
66
+ [POSTGRES_ENTITY_TYPES.foreignTable]: "foreignTable",
67
+ [POSTGRES_ENTITY_TYPES.textSearchConfiguration]: "textSearchConfiguration",
68
+ [POSTGRES_ENTITY_TYPES.textSearchDictionary]: "textSearchDictionary",
69
+ [POSTGRES_ENTITY_TYPES.statistics]: "statistics",
70
+ [POSTGRES_ENTITY_TYPES.foreignDataWrapper]: "foreignDataWrapper",
71
+ [POSTGRES_ENTITY_TYPES.foreignServer]: "foreignServer",
72
+ [POSTGRES_ENTITY_TYPES.publication]: "publication",
63
73
  [POSTGRES_ENTITY_TYPES.extension]: "extension",
64
74
  [POSTGRES_ENTITY_TYPES.function]: "func",
65
75
  [POSTGRES_ENTITY_TYPES.procedure]: "procedure",
@@ -81,6 +91,12 @@ const UNREFERENCED: ReadonlySet<string> = new Set([
81
91
  POSTGRES_ENTITY_TYPES.extension,
82
92
  POSTGRES_ENTITY_TYPES.cast,
83
93
  POSTGRES_ENTITY_TYPES.eventTrigger,
94
+ POSTGRES_ENTITY_TYPES.operator,
95
+ // An index names a class, but a class and its own family share a name; a configuration shares one with a dictionary, which a mapping names.
96
+ POSTGRES_ENTITY_TYPES.operatorClass,
97
+ POSTGRES_ENTITY_TYPES.textSearchConfiguration,
98
+ POSTGRES_ENTITY_TYPES.statistics,
99
+ POSTGRES_ENTITY_TYPES.publication,
84
100
  ]);
85
101
 
86
102
  /** The tag each entity type is declared with (`table`, `func`, ...). */
@@ -131,6 +147,17 @@ function bareSpots(tokens: Token[]): Set<number> {
131
147
  name(node.function);
132
148
  }
133
149
  if (node.statement === "eventTrigger") name(node.function);
150
+ if (node.statement === "foreignServer") name(node.wrapper);
151
+ if (node.statement === "foreignTable") {
152
+ for (const c of node.columns) if (c.type) first(c.type.from, c.type.to);
153
+ name(node.server);
154
+ }
155
+ if (node.statement === "operatorClass") first(node.type.from, node.type.to);
156
+ if (node.statement === "foreignDataWrapper") {
157
+ name(node.handler ?? undefined);
158
+ name(node.validator ?? undefined);
159
+ }
160
+ if (node.statement === "statistics") name(node.table);
134
161
  if (node.statement === "index" || node.statement === "policy") name(node.table);
135
162
  if (node.statement === "trigger") {
136
163
  name(node.table);
@@ -141,6 +168,25 @@ function bareSpots(tokens: Token[]): Set<number> {
141
168
  return out;
142
169
  }
143
170
 
171
+ /** The tokens that name a schema in a publication's `TABLES IN SCHEMA a, b`, past the first, which follows the word SCHEMA. */
172
+ function schemaSpots(tokens: Token[]): Set<number> {
173
+ const out = new Set<number>();
174
+ let nodes: StatementNode[];
175
+ try {
176
+ nodes = parseStatements(tokens);
177
+ } catch {
178
+ return out;
179
+ }
180
+ for (const node of nodes) {
181
+ if (node.statement !== "publication") continue;
182
+ for (const i of node.items) {
183
+ if (i.kind !== "schema") continue;
184
+ for (let k = i.schema.span.from; k < i.schema.span.to; k++) if (!isTrivia(tokens[k]!)) { out.add(k); break; }
185
+ }
186
+ }
187
+ return out;
188
+ }
189
+
144
190
  function templateBody(item: Item, byQualified: Map<string, Item>, schemaExport: Map<string, string>): TemplateBody {
145
191
  const tokens = tokenizeText(item.ddl, 0);
146
192
  const sig: number[] = [];
@@ -148,6 +194,7 @@ function templateBody(item: Item, byQualified: Map<string, Item>, schemaExport:
148
194
  if (!isTrivia(t)) sig.push(i);
149
195
  });
150
196
  const spots = bareSpots(tokens);
197
+ const schemaAt = item.type === POSTGRES_ENTITY_TYPES.publication ? schemaSpots(tokens) : new Set<number>();
151
198
  const replace = new Map<number, { to: number; ref: string }>();
152
199
  const isSelf = (target: Item) => target === item || (target.exportName === item.exportName && target.type === item.type);
153
200
  for (let k = 0; k < sig.length; k++) {
@@ -169,10 +216,15 @@ function templateBody(item: Item, byQualified: Map<string, Item>, schemaExport:
169
216
  }
170
217
  const dot = tokens[sig[k + 1] ?? -1];
171
218
  const b = tokens[sig[k + 2] ?? -1];
219
+ // An operator's own `schema.symbol`: the schema is the schema's export, though the symbol is no name.
220
+ if (item.type === POSTGRES_ENTITY_TYPES.operator && isName(a) && dot?.kind === "punct" && dot.text === "." && b?.kind === "op" && identValue(a) === item.schema && schemaExport.has(item.schema)) {
221
+ replace.set(sig[k]!, { to: sig[k]! + 1, ref: schemaExport.get(item.schema)! });
222
+ continue;
223
+ }
172
224
  if (!isName(a) || dot?.kind !== "punct" || dot.text !== "." || !isName(b)) {
173
225
  // An extension's `WITH SCHEMA app`, a grant's `ON SCHEMA app`, default privileges' `IN SCHEMA app`.
174
- const bySchemaWord = item.type === POSTGRES_ENTITY_TYPES.extension || item.type === POSTGRES_ENTITY_TYPES.grant || item.type === POSTGRES_ENTITY_TYPES.defaultPrivileges;
175
- if (bySchemaWord && isName(a) && prev?.kind === "ident" && prev.text.toUpperCase() === "SCHEMA") {
226
+ const bySchemaWord = item.type === POSTGRES_ENTITY_TYPES.extension || item.type === POSTGRES_ENTITY_TYPES.grant || item.type === POSTGRES_ENTITY_TYPES.defaultPrivileges || schemaAt.size > 0;
227
+ if (bySchemaWord && isName(a) && ((prev?.kind === "ident" && prev.text.toUpperCase() === "SCHEMA") || schemaAt.has(sig[k]!))) {
176
228
  const s = schemaExport.get(identValue(a));
177
229
  if (s) replace.set(sig[k]!, { to: sig[k]! + 1, ref: s });
178
230
  continue;
@@ -84,6 +84,7 @@ export async function exportResources(options: ExportOptions): Promise<ExportedT
84
84
  warnings.push(`${o.schema ? `${o.schema}.` : ""}${o.name} is kept by ${o.foreign}; left out, since declaring it would have chant change what that tool owns`);
85
85
  continue;
86
86
  }
87
+ for (const w of o.warnings ?? []) warnings.push(`${o.schema ? `${o.schema}.` : ""}${o.name}: ${w}`);
87
88
  objects.push({ type: o.type, ...(o.schema ? { schema: o.schema } : {}), name: o.name, ddl: o.statement });
88
89
  }
89
90
  return objectsToIR([...objects, ...access], warnings) as ExportedTemplate;
@@ -23,6 +23,7 @@ import { notExtensionOwned, scopeSql, type LivePgObject, type SchemaScope } from
23
23
  import { POSTGRES_ENTITY_TYPES } from "../entity-types";
24
24
  import { quoteIdent } from "../keywords";
25
25
  import { stripMarker } from "../../core/ownership";
26
+ import { isSecretOption } from "../secret-options";
26
27
 
27
28
  type Row = Record<string, unknown>;
28
29
 
@@ -310,3 +311,496 @@ export async function readRoutineKinds(client: PostgresClient, scope: SchemaScop
310
311
  }
311
312
  return out;
312
313
  }
314
+
315
+ /** An operator named by another's COMMUTATOR or NEGATOR, as `OPERATOR(schema.symbol)`. */
316
+ const operatorRef = (column: string): string =>
317
+ `(SELECT 'OPERATOR(' || pg_catalog.quote_ident(rn.nspname) || '.' || r.oprname || ')' FROM pg_catalog.pg_operator r JOIN pg_catalog.pg_namespace rn ON rn.oid = r.oprnamespace WHERE r.oid = ${column})`;
318
+
319
+ /**
320
+ * Operators: the ones a schema defines, not the server's own (oids below the
321
+ * first normal object id) or an extension's. Identified by name and operand
322
+ * types, `NONE` for a missing side (`DROP OPERATOR`'s spelling).
323
+ */
324
+ export async function readOperators(client: PostgresClient, scope: SchemaScope): Promise<LivePgObject[]> {
325
+ const s = scopeSql("n.nspname", scope);
326
+ const rows = await client.query<Row>(
327
+ `SELECT o.oid::text AS oid, n.nspname AS schema, o.oprname AS name,
328
+ CASE WHEN o.oprleft <> 0 THEN pg_catalog.format_type(o.oprleft, NULL) END AS leftarg,
329
+ CASE WHEN o.oprright <> 0 THEN pg_catalog.format_type(o.oprright, NULL) END AS rightarg,
330
+ ${proc("o.oprcode::pg_catalog.regproc")} AS func, ${operatorRef("o.oprcom")} AS commutator, ${operatorRef("o.oprnegate")} AS negator,
331
+ ${proc("o.oprrest::pg_catalog.regproc")} AS restrict, ${proc("o.oprjoin::pg_catalog.regproc")} AS join_func,
332
+ o.oprcanhash AS hashes, o.oprcanmerge AS merges,
333
+ pg_catalog.obj_description(o.oid, 'pg_operator') AS comment, ${dependentsOf("pg_operator", "o.oid")} AS dependents
334
+ FROM pg_catalog.pg_operator o JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace
335
+ WHERE ${s.sql} AND o.oid >= 16384 AND ${notExtensionOwned("pg_operator", "o.oid")}
336
+ ORDER BY n.nspname ${C}, o.oprname ${C}, pg_catalog.format_type(o.oprleft, NULL) ${C} NULLS FIRST, pg_catalog.format_type(o.oprright, NULL) ${C} NULLS FIRST`,
337
+ s.params,
338
+ );
339
+ return rows.map((r) => {
340
+ const left = str(r.leftarg);
341
+ const right = str(r.rightarg);
342
+ const signature = `(${left ?? "NONE"},${right ?? "NONE"})`;
343
+ const name = `${quoteIdent(String(r.schema))}.${r.name}`;
344
+ const opts = [`FUNCTION = ${r.func}`];
345
+ if (left) opts.push(`LEFTARG = ${left}`);
346
+ if (right) opts.push(`RIGHTARG = ${right}`);
347
+ if (str(r.commutator)) opts.push(`COMMUTATOR = ${r.commutator}`);
348
+ if (str(r.negator)) opts.push(`NEGATOR = ${r.negator}`);
349
+ if (str(r.restrict)) opts.push(`RESTRICT = ${r.restrict}`);
350
+ if (str(r.join_func)) opts.push(`JOIN = ${r.join_func}`);
351
+ if (r.hashes === true) opts.push("HASHES");
352
+ if (r.merges === true) opts.push("MERGES");
353
+ return {
354
+ type: POSTGRES_ENTITY_TYPES.operator,
355
+ schema: String(r.schema),
356
+ name: String(r.name),
357
+ oid: String(r.oid),
358
+ signature,
359
+ ...withComment(r),
360
+ ...dependents(r),
361
+ statement: [`CREATE OPERATOR ${name} (${opts.join(", ")})`, ...commentOn(`OPERATOR ${name}${signature}`, r.comment)].join(";\n"),
362
+ };
363
+ });
364
+ }
365
+
366
+ /** An `OPTIONS (...)` clause from a catalog `text[]` of `name=value`, and the names left out because they hold a credential. */
367
+ function optionsOf(raw: unknown): { clause: string; omitted: string[] } {
368
+ const kept: string[] = [];
369
+ const omitted: string[] = [];
370
+ for (const entry of (raw as string[] | null) ?? []) {
371
+ const at = entry.indexOf("=");
372
+ const name = at < 0 ? entry : entry.slice(0, at);
373
+ if (isSecretOption(name)) omitted.push(name);
374
+ else kept.push(`${quoteIdent(name)} ${literal(at < 0 ? "" : entry.slice(at + 1))}`);
375
+ }
376
+ return { clause: kept.length > 0 ? ` OPTIONS (${kept.join(", ")})` : "", omitted };
377
+ }
378
+
379
+ const omittedWarning = (omitted: readonly string[]): { warnings?: string[] } =>
380
+ omitted.length > 0 ? { warnings: [`the option${omitted.length === 1 ? "" : "s"} ${omitted.join(", ")} hold${omitted.length === 1 ? "s" : ""} a credential and ${omitted.length === 1 ? "is" : "are"} left out; the environment supplies it`] } : {};
381
+
382
+ /**
383
+ * The kinds of object #3911 adds that belong to the database or hang off a
384
+ * table: extended statistics (printed by the server), foreign data wrappers
385
+ * and servers, and publications, each assembled from its catalog rows. A
386
+ * wrapper, server or publication is the database's, so the schema scope does
387
+ * not narrow it. A credential in an option is left out and said so.
388
+ */
389
+ export async function readDatabaseKinds(client: PostgresClient, scope: SchemaScope): Promise<LivePgObject[]> {
390
+ const out: LivePgObject[] = [...(await readOperators(client, scope))];
391
+
392
+ const s = scopeSql("n.nspname", scope);
393
+ const stats = await client.query<Row>(
394
+ `SELECT e.oid::text AS oid, n.nspname AS schema, e.stxname AS name, pg_catalog.pg_get_statisticsobjdef(e.oid) AS def,
395
+ pg_catalog.obj_description(e.oid, 'pg_statistic_ext') AS comment
396
+ FROM pg_catalog.pg_statistic_ext e JOIN pg_catalog.pg_namespace n ON n.oid = e.stxnamespace
397
+ WHERE ${s.sql} AND ${notExtensionOwned("pg_statistic_ext", "e.oid")}
398
+ ORDER BY n.nspname ${C}, e.stxname ${C}`,
399
+ s.params,
400
+ );
401
+ for (const r of stats) {
402
+ const q = qname(String(r.schema), String(r.name));
403
+ out.push({
404
+ type: POSTGRES_ENTITY_TYPES.statistics,
405
+ schema: String(r.schema),
406
+ name: String(r.name),
407
+ oid: String(r.oid),
408
+ ...withComment(r),
409
+ statement: [String(r.def), ...commentOn(`STATISTICS ${q}`, r.comment)].join(";\n"),
410
+ });
411
+ }
412
+
413
+ const wrappers = await client.query<Row>(
414
+ `SELECT w.oid::text AS oid, w.fdwname AS name, ${proc("w.fdwhandler::pg_catalog.regproc")} AS handler, ${proc("w.fdwvalidator::pg_catalog.regproc")} AS validator,
415
+ w.fdwoptions AS options, pg_catalog.obj_description(w.oid, 'pg_foreign_data_wrapper') AS comment, ${dependentsOf("pg_foreign_data_wrapper", "w.oid")} AS dependents
416
+ FROM pg_catalog.pg_foreign_data_wrapper w
417
+ WHERE ${notExtensionOwned("pg_foreign_data_wrapper", "w.oid")}
418
+ ORDER BY w.fdwname ${C}`,
419
+ );
420
+ for (const r of wrappers) {
421
+ const name = quoteIdent(String(r.name));
422
+ const { clause, omitted } = optionsOf(r.options);
423
+ out.push({
424
+ type: POSTGRES_ENTITY_TYPES.foreignDataWrapper,
425
+ name: String(r.name),
426
+ oid: String(r.oid),
427
+ ...withComment(r),
428
+ ...dependents(r),
429
+ ...omittedWarning(omitted),
430
+ statement: [
431
+ `CREATE FOREIGN DATA WRAPPER ${name}${str(r.handler) ? ` HANDLER ${r.handler}` : ""}${str(r.validator) ? ` VALIDATOR ${r.validator}` : ""}${clause}`,
432
+ ...commentOn(`FOREIGN DATA WRAPPER ${name}`, r.comment),
433
+ ].join(";\n"),
434
+ });
435
+ }
436
+
437
+ const servers = await client.query<Row>(
438
+ `SELECT v.oid::text AS oid, v.srvname AS name, v.srvtype AS type, v.srvversion AS version, w.fdwname AS wrapper, v.srvoptions AS options,
439
+ pg_catalog.obj_description(v.oid, 'pg_foreign_server') AS comment, ${dependentsOf("pg_foreign_server", "v.oid")} AS dependents
440
+ FROM pg_catalog.pg_foreign_server v JOIN pg_catalog.pg_foreign_data_wrapper w ON w.oid = v.srvfdw
441
+ WHERE ${notExtensionOwned("pg_foreign_server", "v.oid")}
442
+ ORDER BY v.srvname ${C}`,
443
+ );
444
+ for (const r of servers) {
445
+ const name = quoteIdent(String(r.name));
446
+ const { clause, omitted } = optionsOf(r.options);
447
+ out.push({
448
+ type: POSTGRES_ENTITY_TYPES.foreignServer,
449
+ name: String(r.name),
450
+ oid: String(r.oid),
451
+ ...withComment(r),
452
+ ...dependents(r),
453
+ ...omittedWarning(omitted),
454
+ statement: [
455
+ `CREATE SERVER ${name}${str(r.type) ? ` TYPE ${literal(String(r.type))}` : ""}${str(r.version) ? ` VERSION ${literal(String(r.version))}` : ""} FOREIGN DATA WRAPPER ${quoteIdent(String(r.wrapper))}${clause}`,
456
+ ...commentOn(`SERVER ${name}`, r.comment),
457
+ ].join(";\n"),
458
+ });
459
+ }
460
+
461
+ out.push(...(await readPublications(client)));
462
+ out.push(...(await readOperatorClasses(client, scope)), ...(await readTextSearch(client, scope)), ...(await readForeignTables(client, scope)));
463
+ return out;
464
+ }
465
+
466
+ /** Publications. Column lists, row filters and schema members are 15's; they are read where the catalog has them. */
467
+ async function readPublications(client: PostgresClient): Promise<LivePgObject[]> {
468
+ const has = async (catalog: string, column?: string): Promise<boolean> => {
469
+ const rows = await client.query<Row>(
470
+ column
471
+ ? `SELECT 1 AS x FROM pg_catalog.pg_attribute WHERE attrelid = to_regclass('pg_catalog.${catalog}') AND attname = '${column}' AND NOT attisdropped`
472
+ : `SELECT 1 AS x WHERE to_regclass('pg_catalog.${catalog}') IS NOT NULL`,
473
+ );
474
+ return rows.length > 0;
475
+ };
476
+ const columnLists = await has("pg_publication_rel", "prattrs");
477
+ const schemaMembers = await has("pg_publication_namespace");
478
+
479
+ const pubs = await client.query<Row>(
480
+ `SELECT p.oid::text AS oid, p.pubname AS name, p.puballtables AS alltables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate,
481
+ pg_catalog.to_jsonb(p) AS def, pg_catalog.obj_description(p.oid, 'pg_publication') AS comment
482
+ FROM pg_catalog.pg_publication p
483
+ WHERE ${notExtensionOwned("pg_publication", "p.oid")}
484
+ ORDER BY p.pubname ${C}`,
485
+ );
486
+ const out: LivePgObject[] = [];
487
+ for (const r of pubs) {
488
+ const name = quoteIdent(String(r.name));
489
+ const d = (typeof r.def === "string" ? JSON.parse(r.def) : r.def) as Record<string, unknown>;
490
+ const tables = r.alltables
491
+ ? []
492
+ : await client.query<Row>(
493
+ `SELECT n.nspname AS schema, c.relname AS name,
494
+ ${columnLists ? "ARRAY(SELECT pg_catalog.quote_ident(a.attname) FROM pg_catalog.pg_attribute a WHERE a.attrelid = r.prrelid AND a.attnum = ANY(r.prattrs::pg_catalog.int2[]) ORDER BY a.attnum)" : "NULL::text[]"} AS columns,
495
+ ${columnLists ? "pg_catalog.pg_get_expr(r.prqual, r.prrelid)" : "NULL::text"} AS filter
496
+ FROM pg_catalog.pg_publication_rel r JOIN pg_catalog.pg_class c ON c.oid = r.prrelid JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
497
+ WHERE r.prpubid = $1::oid ORDER BY n.nspname ${C}, c.relname ${C}`,
498
+ [String(r.oid)],
499
+ );
500
+ const schemas =
501
+ schemaMembers && !r.alltables
502
+ ? await client.query<Row>(
503
+ `SELECT n.nspname AS name FROM pg_catalog.pg_publication_namespace pn JOIN pg_catalog.pg_namespace n ON n.oid = pn.pnnspid WHERE pn.pnpubid = $1::oid ORDER BY n.nspname ${C}`,
504
+ [String(r.oid)],
505
+ )
506
+ : [];
507
+ const members = [
508
+ ...tables.map((t) => {
509
+ const columns = (t.columns as string[] | null) ?? [];
510
+ return `${qname(String(t.schema), String(t.name))}${columns.length > 0 ? ` (${columns.join(", ")})` : ""}${str(t.filter) ? ` WHERE (${t.filter})` : ""}`;
511
+ }),
512
+ ];
513
+ const clauses: string[] = [];
514
+ if (r.alltables) clauses.push("FOR ALL TABLES");
515
+ else if (members.length > 0 || schemas.length > 0) {
516
+ const parts: string[] = [];
517
+ if (members.length > 0) parts.push(`TABLE ${members.join(", ")}`);
518
+ if (schemas.length > 0) parts.push(`TABLES IN SCHEMA ${schemas.map((x) => quoteIdent(String(x.name))).join(", ")}`);
519
+ clauses.push(`FOR ${parts.join(", ")}`);
520
+ }
521
+ const publish = [r.pubinsert && "insert", r.pubupdate && "update", r.pubdelete && "delete", r.pubtruncate && "truncate"].filter(Boolean);
522
+ const options: string[] = [];
523
+ if (publish.length !== 4) options.push(`publish = ${literal(publish.join(", "))}`);
524
+ if (d.pubviaroot === true) options.push("publish_via_partition_root = true");
525
+ if (d.pubgencols !== undefined && d.pubgencols !== "n") options.push("publish_generated_columns = stored");
526
+ if (options.length > 0) clauses.push(`WITH (${options.join(", ")})`);
527
+ out.push({
528
+ type: POSTGRES_ENTITY_TYPES.publication,
529
+ name: String(r.name),
530
+ oid: String(r.oid),
531
+ ...withComment(r),
532
+ statement: [`CREATE PUBLICATION ${name}${clauses.length > 0 ? ` ${clauses.join(" ")}` : ""}`, ...commentOn(`PUBLICATION ${name}`, r.comment)].join(";\n"),
533
+ });
534
+ }
535
+ return out;
536
+ }
537
+
538
+ // ── Operator classes and families, text search, foreign tables (#4406) ──
539
+
540
+ /** An object named by `schema`.`name` columns, as a clause: a `pg_catalog` one bare. */
541
+ const refName = (schema: string, name: string): string =>
542
+ `CASE WHEN ${schema} = 'pg_catalog' THEN pg_catalog.quote_ident(${name}) ELSE pg_catalog.quote_ident(${schema}) || '.' || pg_catalog.quote_ident(${name}) END`;
543
+
544
+ /** An operand type, `NONE` for the missing side of a prefix operator. */
545
+ const operand = (column: string): string => `CASE WHEN ${column} = 0 THEN 'NONE' ELSE pg_catalog.format_type(${column}, NULL) END`;
546
+
547
+ /**
548
+ * Operator classes and families. A class carries its members: the operators
549
+ * and support functions that depend on it (`pg_depend`, a member made by
550
+ * CREATE OPERATOR CLASS depends on the class; one added by ALTER OPERATOR
551
+ * FAMILY ... ADD depends on the family and is "loose"). A family named as its
552
+ * class, in the same schema, is the one CREATE OPERATOR CLASS made for it and
553
+ * is the class's; every other family is read, with a warning when it holds
554
+ * loose members, which a declaration cannot carry.
555
+ */
556
+ async function readOperatorClasses(client: PostgresClient, scope: SchemaScope): Promise<LivePgObject[]> {
557
+ const out: LivePgObject[] = [];
558
+ const s = scopeSql("n.nspname", scope);
559
+ const member = (catalog: string, alias: string) =>
560
+ `JOIN pg_catalog.pg_depend dep ON dep.classid = '${catalog}'::pg_catalog.regclass AND dep.objid = ${alias}.oid AND dep.refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass AND dep.refobjid = c.oid`;
561
+ const classes = await client.query<Row>(
562
+ `SELECT c.oid::text AS oid, n.nspname AS schema, c.opcname AS name, am.amname AS method, c.opcdefault AS isdefault,
563
+ pg_catalog.format_type(c.opcintype, NULL) AS type,
564
+ CASE WHEN c.opckeytype <> 0 THEN pg_catalog.format_type(c.opckeytype, NULL) END AS storage,
565
+ CASE WHEN f.opfname = c.opcname AND f.opfnamespace = c.opcnamespace THEN NULL ELSE ${refName("fn.nspname", "f.opfname")} END AS family,
566
+ ARRAY(SELECT 'OPERATOR ' || o.amopstrategy || ' ' || pg_catalog.quote_ident(opn.nspname) || '.' || op.oprname ||
567
+ ' (' || ${operand("o.amoplefttype")} || ', ' || ${operand("o.amoprighttype")} || ')' ||
568
+ CASE WHEN o.amoppurpose = 'o' THEN ' FOR ORDER BY ' || (SELECT pg_catalog.quote_ident(sn.nspname) || '.' || pg_catalog.quote_ident(sf.opfname)
569
+ FROM pg_catalog.pg_opfamily sf JOIN pg_catalog.pg_namespace sn ON sn.oid = sf.opfnamespace WHERE sf.oid = o.amopsortfamily) ELSE '' END
570
+ FROM pg_catalog.pg_amop o ${member("pg_catalog.pg_amop", "o")}
571
+ JOIN pg_catalog.pg_operator op ON op.oid = o.amopopr JOIN pg_catalog.pg_namespace opn ON opn.oid = op.oprnamespace
572
+ ORDER BY o.amopstrategy, o.amoplefttype, o.amoprighttype) AS operators,
573
+ ARRAY(SELECT 'FUNCTION ' || p.amprocnum || ' (' || ${operand("p.amproclefttype")} || ', ' || ${operand("p.amprocrighttype")} || ') ' || p.amproc::pg_catalog.regprocedure::pg_catalog.text
574
+ FROM pg_catalog.pg_amproc p ${member("pg_catalog.pg_amproc", "p")}
575
+ ORDER BY p.amprocnum, p.amproclefttype, p.amprocrighttype) AS functions,
576
+ pg_catalog.obj_description(c.oid, 'pg_opclass') AS comment, ${dependentsOf("pg_opclass", "c.oid")} AS dependents
577
+ FROM pg_catalog.pg_opclass c JOIN pg_catalog.pg_namespace n ON n.oid = c.opcnamespace JOIN pg_catalog.pg_am am ON am.oid = c.opcmethod
578
+ JOIN pg_catalog.pg_opfamily f ON f.oid = c.opcfamily JOIN pg_catalog.pg_namespace fn ON fn.oid = f.opfnamespace
579
+ WHERE ${s.sql} AND c.oid >= 16384 AND ${notExtensionOwned("pg_opclass", "c.oid")}
580
+ ORDER BY n.nspname ${C}, c.opcname ${C}, am.amname ${C}`,
581
+ s.params,
582
+ );
583
+ for (const r of classes) {
584
+ const q = qname(String(r.schema), String(r.name));
585
+ const method = String(r.method);
586
+ const items = [...((r.operators as string[] | null) ?? []), ...((r.functions as string[] | null) ?? []), ...(str(r.storage) ? [`STORAGE ${r.storage}`] : [])];
587
+ out.push({
588
+ type: POSTGRES_ENTITY_TYPES.operatorClass,
589
+ schema: String(r.schema),
590
+ name: String(r.name),
591
+ oid: String(r.oid),
592
+ signature: ` USING ${method}`,
593
+ ...withComment(r),
594
+ ...dependents(r),
595
+ ...(items.length === 0 ? { unsupported: "no operator, support function or storage type of it could be read; a CREATE OPERATOR CLASS needs at least one" } : {}),
596
+ statement: [
597
+ `CREATE OPERATOR CLASS ${q}${r.isdefault === true ? " DEFAULT" : ""} FOR TYPE ${r.type} USING ${method}${str(r.family) ? ` FAMILY ${r.family}` : ""} AS\n ${items.join(",\n ")}`,
598
+ ...commentOn(`OPERATOR CLASS ${q} USING ${method}`, r.comment),
599
+ ].join(";\n"),
600
+ });
601
+ }
602
+
603
+ const families = await client.query<Row>(
604
+ `SELECT f.oid::text AS oid, n.nspname AS schema, f.opfname AS name, am.amname AS method,
605
+ (SELECT count(*) FROM (SELECT o.oid FROM pg_catalog.pg_amop o WHERE o.amopfamily = f.oid UNION ALL SELECT p.oid FROM pg_catalog.pg_amproc p WHERE p.amprocfamily = f.oid) m
606
+ WHERE NOT EXISTS (SELECT 1 FROM pg_catalog.pg_depend dep WHERE dep.classid IN ('pg_catalog.pg_amop'::pg_catalog.regclass, 'pg_catalog.pg_amproc'::pg_catalog.regclass)
607
+ AND dep.objid = m.oid AND dep.refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass)) AS loose,
608
+ pg_catalog.obj_description(f.oid, 'pg_opfamily') AS comment, ${dependentsOf("pg_opfamily", "f.oid")} AS dependents
609
+ FROM pg_catalog.pg_opfamily f JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace JOIN pg_catalog.pg_am am ON am.oid = f.opfmethod
610
+ WHERE ${s.sql} AND f.oid >= 16384 AND ${notExtensionOwned("pg_opfamily", "f.oid")}
611
+ AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_opclass c WHERE c.opcfamily = f.oid AND c.opcname = f.opfname AND c.opcnamespace = f.opfnamespace)
612
+ ORDER BY n.nspname ${C}, f.opfname ${C}, am.amname ${C}`,
613
+ s.params,
614
+ );
615
+ for (const r of families) {
616
+ const q = qname(String(r.schema), String(r.name));
617
+ const method = String(r.method);
618
+ const loose = Number(r.loose ?? 0);
619
+ out.push({
620
+ type: POSTGRES_ENTITY_TYPES.operatorFamily,
621
+ schema: String(r.schema),
622
+ name: String(r.name),
623
+ oid: String(r.oid),
624
+ signature: ` USING ${method}`,
625
+ ...withComment(r),
626
+ ...dependents(r),
627
+ ...(loose > 0 ? { warnings: [`${loose} loose member${loose === 1 ? "" : "s"} added with ALTER OPERATOR FAMILY ... ADD ${loose === 1 ? "is" : "are"} left out; a declaration holds the family and its classes' members`] } : {}),
628
+ statement: [`CREATE OPERATOR FAMILY ${q} USING ${method}`, ...commentOn(`OPERATOR FAMILY ${q} USING ${method}`, r.comment)].join(";\n"),
629
+ });
630
+ }
631
+ return out;
632
+ }
633
+
634
+ /**
635
+ * A dictionary's `dictinitoption`, which the server prints as
636
+ * `name = 'value', ...` (a quote and a backslash doubled in the value), as a
637
+ * list of name and value; undefined when the text is not that form.
638
+ */
639
+ export function parseDictionaryOptions(init: string | undefined): Array<[string, string]> | undefined {
640
+ if (init === undefined || init.trim() === "") return [];
641
+ const out: Array<[string, string]> = [];
642
+ const item = /\s*("(?:[^"]|"")+"|[^\s=,]+)\s*=\s*'((?:[^']|'')*)'\s*(?:,|$)/y;
643
+ let at = 0;
644
+ while (at < init.length) {
645
+ item.lastIndex = at;
646
+ const m = item.exec(init);
647
+ if (!m) return undefined;
648
+ const name = m[1]!.startsWith('"') ? m[1]!.slice(1, -1).replace(/""/g, '"') : m[1]!;
649
+ out.push([name, m[2]!.replace(/''/g, "'").replace(/\\\\/g, "\\")]);
650
+ at = item.lastIndex;
651
+ }
652
+ return out;
653
+ }
654
+
655
+ /** Text search dictionaries and configurations; a configuration's mappings are the ALTER ... ADD MAPPING statements after its CREATE. */
656
+ async function readTextSearch(client: PostgresClient, scope: SchemaScope): Promise<LivePgObject[]> {
657
+ const out: LivePgObject[] = [];
658
+ const s = scopeSql("n.nspname", scope);
659
+ const dictionaries = await client.query<Row>(
660
+ `SELECT d.oid::text AS oid, n.nspname AS schema, d.dictname AS name, d.dictinitoption AS init,
661
+ (SELECT ${refName("tn.nspname", "t.tmplname")} FROM pg_catalog.pg_ts_template t JOIN pg_catalog.pg_namespace tn ON tn.oid = t.tmplnamespace WHERE t.oid = d.dicttemplate) AS template,
662
+ pg_catalog.obj_description(d.oid, 'pg_ts_dict') AS comment, ${dependentsOf("pg_ts_dict", "d.oid")} AS dependents
663
+ FROM pg_catalog.pg_ts_dict d JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace
664
+ WHERE ${s.sql} AND d.oid >= 16384 AND ${notExtensionOwned("pg_ts_dict", "d.oid")}
665
+ ORDER BY n.nspname ${C}, d.dictname ${C}`,
666
+ s.params,
667
+ );
668
+ for (const r of dictionaries) {
669
+ const q = qname(String(r.schema), String(r.name));
670
+ const options = parseDictionaryOptions(str(r.init));
671
+ out.push({
672
+ type: POSTGRES_ENTITY_TYPES.textSearchDictionary,
673
+ schema: String(r.schema),
674
+ name: String(r.name),
675
+ oid: String(r.oid),
676
+ ...withComment(r),
677
+ ...dependents(r),
678
+ ...(options === undefined ? { unsupported: `its options (${str(r.init)}) are not in the form the server writes them` } : {}),
679
+ statement: [
680
+ `CREATE TEXT SEARCH DICTIONARY ${q} (${["TEMPLATE = " + r.template, ...(options ?? []).map(([k, v]) => `${quoteIdent(k)} = ${literal(v)}`)].join(", ")})`,
681
+ ...commentOn(`TEXT SEARCH DICTIONARY ${q}`, r.comment),
682
+ ].join(";\n"),
683
+ });
684
+ }
685
+
686
+ const configurations = await client.query<Row>(
687
+ `SELECT c.oid::text AS oid, n.nspname AS schema, c.cfgname AS name,
688
+ (SELECT ${refName("pn.nspname", "p.prsname")} FROM pg_catalog.pg_ts_parser p JOIN pg_catalog.pg_namespace pn ON pn.oid = p.prsnamespace WHERE p.oid = c.cfgparser) AS parser,
689
+ ARRAY(SELECT tt.alias || E'\\t' || pg_catalog.string_agg((SELECT ${refName("dn.nspname", "d.dictname")} FROM pg_catalog.pg_ts_dict d JOIN pg_catalog.pg_namespace dn ON dn.oid = d.dictnamespace WHERE d.oid = m.mapdict), ', ' ORDER BY m.mapseqno)
690
+ FROM pg_catalog.pg_ts_config_map m, pg_catalog.ts_token_type(c.cfgparser) tt WHERE m.mapcfg = c.oid AND tt.tokid = m.maptokentype
691
+ GROUP BY tt.tokid, tt.alias ORDER BY tt.tokid) AS mappings,
692
+ pg_catalog.obj_description(c.oid, 'pg_ts_config') AS comment, ${dependentsOf("pg_ts_config", "c.oid")} AS dependents
693
+ FROM pg_catalog.pg_ts_config c JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace
694
+ WHERE ${s.sql} AND c.oid >= 16384 AND ${notExtensionOwned("pg_ts_config", "c.oid")}
695
+ ORDER BY n.nspname ${C}, c.cfgname ${C}`,
696
+ s.params,
697
+ );
698
+ for (const r of configurations) {
699
+ const q = qname(String(r.schema), String(r.name));
700
+ // Token types that share their dictionaries share an ADD MAPPING, in the parser's token order.
701
+ const groups = new Map<string, string[]>();
702
+ for (const line of (r.mappings as string[] | null) ?? []) {
703
+ const [token, dictionaries] = line.split("\t") as [string, string];
704
+ groups.set(dictionaries, [...(groups.get(dictionaries) ?? []), quoteIdent(token)]);
705
+ }
706
+ out.push({
707
+ type: POSTGRES_ENTITY_TYPES.textSearchConfiguration,
708
+ schema: String(r.schema),
709
+ name: String(r.name),
710
+ oid: String(r.oid),
711
+ ...withComment(r),
712
+ ...dependents(r),
713
+ statement: [
714
+ `CREATE TEXT SEARCH CONFIGURATION ${q} (PARSER = ${r.parser})`,
715
+ ...[...groups].map(([dicts, tokens]) => `ALTER TEXT SEARCH CONFIGURATION ${q} ADD MAPPING FOR ${tokens.join(", ")} WITH ${dicts}`),
716
+ ...commentOn(`TEXT SEARCH CONFIGURATION ${q}`, r.comment),
717
+ ].join(";\n"),
718
+ });
719
+ }
720
+ return out;
721
+ }
722
+
723
+ /**
724
+ * Foreign tables: their columns (type, collation, default, NOT NULL and
725
+ * OPTIONS), CHECK constraints, server and OPTIONS. One that is a partition,
726
+ * inherits or has a generated column is read as unsupported, since a
727
+ * declaration does not hold those. An option that holds a credential is left
728
+ * out and said so.
729
+ */
730
+ async function readForeignTables(client: PostgresClient, scope: SchemaScope): Promise<LivePgObject[]> {
731
+ const s = scopeSql("n.nspname", scope);
732
+ const tables = await client.query<Row>(
733
+ `SELECT c.oid::text AS oid, n.nspname AS schema, c.relname AS name, c.relispartition AS ispartition,
734
+ EXISTS (SELECT 1 FROM pg_catalog.pg_inherits i WHERE i.inhrelid = c.oid) AS inherits,
735
+ fs.srvname AS server, ft.ftoptions AS options,
736
+ pg_catalog.obj_description(c.oid, 'pg_class') AS comment, ${dependentsOf("pg_class", "c.oid")} AS dependents
737
+ FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
738
+ JOIN pg_catalog.pg_foreign_table ft ON ft.ftrelid = c.oid JOIN pg_catalog.pg_foreign_server fs ON fs.oid = ft.ftserver
739
+ WHERE c.relkind = 'f' AND ${s.sql} AND ${notExtensionOwned("pg_class", "c.oid")}
740
+ ORDER BY n.nspname ${C}, c.relname ${C}`,
741
+ s.params,
742
+ );
743
+ if (tables.length === 0) return [];
744
+ const oids = tables.map((t) => String(t.oid));
745
+ const columns = await client.query<Row>(
746
+ `SELECT a.attrelid::text AS rel, a.attname AS name, pg_catalog.format_type(a.atttypid, a.atttypmod) AS type, a.attnotnull AS notnull,
747
+ a.attgenerated::text AS generated, pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) AS dflt,
748
+ CASE WHEN a.attcollation <> 0 AND a.attcollation <> t.typcollation THEN ${collationName("a.attcollation")} END AS collation,
749
+ a.attfdwoptions AS options, pg_catalog.col_description(a.attrelid, a.attnum) AS comment
750
+ FROM pg_catalog.pg_attribute AS a JOIN pg_catalog.pg_type t ON t.oid = a.atttypid
751
+ LEFT JOIN pg_catalog.pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
752
+ WHERE a.attrelid = ANY($1::oid[]) AND a.attnum > 0 AND NOT a.attisdropped
753
+ ORDER BY a.attrelid, a.attnum`,
754
+ [oids],
755
+ );
756
+ const checks = await client.query<Row>(
757
+ `SELECT k.conrelid::text AS rel, k.conname AS name, pg_catalog.pg_get_constraintdef(k.oid, true) AS def, pg_catalog.obj_description(k.oid, 'pg_constraint') AS comment
758
+ FROM pg_catalog.pg_constraint k
759
+ WHERE k.conrelid = ANY($1::oid[]) AND k.contype = 'c' AND k.conislocal
760
+ ORDER BY k.conrelid, k.conname ${C}`,
761
+ [oids],
762
+ );
763
+ return tables.map((t): LivePgObject => {
764
+ const rel = String(t.oid);
765
+ const q = qname(String(t.schema), String(t.name));
766
+ const omitted: string[] = [];
767
+ const lines: string[] = [];
768
+ const commentLines: string[] = [];
769
+ let generated = false;
770
+ for (const c of columns.filter((x) => String(x.rel) === rel)) {
771
+ const parts = [quoteIdent(String(c.name)), String(c.type)];
772
+ const opts = optionsOf(c.options);
773
+ omitted.push(...opts.omitted);
774
+ if (opts.clause) parts.push(opts.clause.trim());
775
+ if (str(c.collation)) parts.push(`COLLATE ${c.collation}`);
776
+ if (str(c.generated)) generated = true;
777
+ else if (str(c.dflt)) parts.push(`DEFAULT ${c.dflt}`);
778
+ if (c.notnull === true) parts.push("NOT NULL");
779
+ lines.push(parts.join(" "));
780
+ commentLines.push(...commentOn(`COLUMN ${q}.${quoteIdent(String(c.name))}`, c.comment));
781
+ }
782
+ for (const k of checks.filter((x) => String(x.rel) === rel)) {
783
+ lines.push(`CONSTRAINT ${quoteIdent(String(k.name))} ${k.def}`);
784
+ commentLines.push(...commentOn(`CONSTRAINT ${quoteIdent(String(k.name))} ON ${q}`, k.comment));
785
+ }
786
+ const { clause, omitted: tableOmitted } = optionsOf(t.options);
787
+ omitted.push(...tableOmitted);
788
+ const unsupported =
789
+ t.ispartition === true ? "it is a partition of another table (CREATE FOREIGN TABLE ... PARTITION OF is not declared)" : t.inherits === true ? "it inherits from another table (INHERITS is not declared on a foreign table)" : generated ? "it has a generated column (not declared on a foreign table)" : undefined;
790
+ return {
791
+ type: POSTGRES_ENTITY_TYPES.foreignTable,
792
+ schema: String(t.schema),
793
+ name: String(t.name),
794
+ oid: rel,
795
+ ...withComment(t),
796
+ ...dependents(t),
797
+ ...omittedWarning(omitted),
798
+ ...(unsupported ? { unsupported } : {}),
799
+ statement: [
800
+ `CREATE FOREIGN TABLE ${q} (${lines.length > 0 ? `\n ${lines.join(",\n ")}\n` : ""}) SERVER ${quoteIdent(String(t.server))}${clause}`,
801
+ ...commentOn(`FOREIGN TABLE ${q}`, t.comment),
802
+ ...commentLines,
803
+ ].join(";\n"),
804
+ };
805
+ });
806
+ }
@@ -53,7 +53,7 @@ import { MIGRATION_TRAILER_KEY } from "../migrate/names";
53
53
  import { isProviderOwned, providerData } from "../providers";
54
54
  import { FOREIGN_TABLES } from "../../core/foreign-tables";
55
55
  import type { PostgresProvider } from "../providers/types";
56
- import { readCollations, readRoutineKinds, readTypeKinds } from "./catalog-kinds";
56
+ import { readCollations, readDatabaseKinds, readRoutineKinds, readTypeKinds } from "./catalog-kinds";
57
57
 
58
58
  /** The server's own schemas, never part of a declared schema. */
59
59
  export const SYSTEM_SCHEMAS = ["pg_catalog", "information_schema", "pg_toast"];
@@ -83,6 +83,8 @@ export interface LivePgObject {
83
83
  dependents?: string[];
84
84
  /** Why no declaration can hold this object; it is read so import can say it left it out. */
85
85
  unsupported?: string;
86
+ /** What was left out of an object that is otherwise read (an option that holds a credential), for import to say. */
87
+ warnings?: string[];
86
88
  }
87
89
 
88
90
  /** What {@link readLiveSchema} reads. */
@@ -629,6 +631,9 @@ export async function readLiveSchema(client: PostgresClient, scope: SchemaScope
629
631
  // Aggregates, casts and event triggers, after the functions they call (#3911).
630
632
  out.push(...(await readRoutineKinds(client, scope)));
631
633
 
634
+ // Extended statistics, foreign data wrappers and servers, and publications (#3911).
635
+ out.push(...(await readDatabaseKinds(client, scope)));
636
+
632
637
  // Triggers: the ones written by hand, not a foreign key's or a partition's clone of its parent's.
633
638
  {
634
639
  const rows = await client.query<Row>(