@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
@@ -25,7 +25,7 @@ import { col, migrationNames, MIGRATION_TRAILER_KEY, type MigrationNames } from
25
25
  import { MigrationRefusal, observeMigration, type MigrationObservation } from "./observe";
26
26
  import { postgresReceiptStore, receiptAddress } from "./receipts";
27
27
  import type { ReplicationLagBound } from "./replication";
28
- import { addCarriedConstraint, carriedNotReady, carriedSwitchStatements, requiresNotNull, SERIAL_TYPES, storedType, type CarriedObject } from "./carry";
28
+ import { addCarriedConstraint, carriedNotReady, carriedSwitchStatements, partitionIndexName, partitionIndexStatement, partitionTree, requiresNotNull, SERIAL_TYPES, storedType, type CarriedObject, type CarriedState } from "./carry";
29
29
 
30
30
  /** What every step is given. */
31
31
  export interface MigrationRun {
@@ -39,6 +39,10 @@ export interface MigrationRun {
39
39
  column: string;
40
40
  /** The type change's expression over the old row's columns. */
41
41
  using?: string;
42
+ /** A rename with a type change: the expression that gives the old column's value from the new column's. */
43
+ reverse?: string;
44
+ /** A rename of a column with a default, serial or identity: the declared default lands on the new column at the contract. */
45
+ defaultAtContract?: boolean;
42
46
  /** This project's ownership marker, stamped on every working object. */
43
47
  marker?: OwnershipMarker;
44
48
  /** The major the build targets. */
@@ -70,6 +74,8 @@ export const observe = (run: MigrationRun): Promise<MigrationObservation> =>
70
74
  ...(run.objects ? { objects: run.objects } : {}),
71
75
  ...(run.marker ? { marker: run.marker } : {}),
72
76
  ...(run.using !== undefined ? { using: run.using } : {}),
77
+ ...(run.reverse !== undefined ? { reverse: run.reverse } : {}),
78
+ ...(run.defaultAtContract ? { defaultAtContract: true } : {}),
73
79
  ...(run.major !== undefined ? { major: run.major } : {}),
74
80
  });
75
81
 
@@ -174,7 +180,7 @@ export interface ExpandResult {
174
180
  }
175
181
 
176
182
  /** The new column's type: the declared one, with its collation when it declares one. */
177
- const newColumnType = (o: MigrationObservation): string => `${o.names.change === "rename" ? o.source!.type : storedType(o.column.type)}${o.column.collate ? ` COLLATE ${o.column.collate}` : ""}`;
183
+ const newColumnType = (o: MigrationObservation): string => `${o.names.change === "rename" && o.reverse === undefined ? o.source!.type : storedType(o.column.type)}${o.column.collate ? ` COLLATE ${o.column.collate}` : ""}`;
178
184
 
179
185
  /**
180
186
  * Expand: add the new column, nullable and with no default, which changes
@@ -211,10 +217,27 @@ export async function expand(run: MigrationRun): Promise<ExpandResult> {
211
217
  * An insert takes the new column's value when it is given and the old one's
212
218
  * otherwise; an update takes whichever of the two it changed.
213
219
  */
214
- export function dualWriteBody(o: Pick<MigrationObservation, "names" | "expression">): string {
220
+ export function dualWriteBody(o: Pick<MigrationObservation, "names" | "expression"> & { reverse?: string }): string {
215
221
  const n = o.names;
216
222
  const nw = `NEW.${col(n.newColumn)}`;
217
223
  const src = `NEW.${col(n.source)}`;
224
+ if (n.change === "rename" && o.reverse !== undefined) {
225
+ // A rename with a type change: a writer of the old name has `expression` applied, a writer of the new name `reverse`, each over the row as it is.
226
+ const fwd = `(SELECT ${o.expression} FROM (SELECT NEW.*) AS ${col(n.table)})`;
227
+ const back = `(SELECT ${o.reverse} FROM (SELECT NEW.*) AS ${col(n.table)})`;
228
+ return [
229
+ `BEGIN`,
230
+ ` IF TG_OP = 'INSERT' THEN`,
231
+ ` IF ${nw} IS NULL THEN ${nw} := ${fwd}; ELSE ${src} := ${back}; END IF;`,
232
+ ` ELSIF ${nw} IS DISTINCT FROM OLD.${col(n.newColumn)} THEN`,
233
+ ` ${src} := ${back};`,
234
+ ` ELSE`,
235
+ ` ${nw} := ${fwd};`,
236
+ ` END IF;`,
237
+ ` RETURN NEW;`,
238
+ `END`,
239
+ ].join("\n");
240
+ }
218
241
  if (n.change === "type") {
219
242
  return [`BEGIN`, ` ${nw} := (SELECT ${o.expression} FROM (SELECT NEW.*) AS ${col(n.table)});`, ` RETURN NEW;`, `END`].join("\n");
220
243
  }
@@ -233,7 +256,7 @@ export function dualWriteBody(o: Pick<MigrationObservation, "names" | "expressio
233
256
  }
234
257
 
235
258
  /** The statements that start the dual write: the function, the trigger, their comments. */
236
- export function dualWriteStatements(o: Pick<MigrationObservation, "names" | "expression">, marker: OwnershipMarker | undefined, defaultSchema: string): string[] {
259
+ export function dualWriteStatements(o: Pick<MigrationObservation, "names" | "expression"> & { reverse?: string }, marker: OwnershipMarker | undefined, defaultSchema: string): string[] {
237
260
  const n = o.names;
238
261
  return [
239
262
  `CREATE OR REPLACE FUNCTION ${n.qualifiedFn}() RETURNS trigger LANGUAGE plpgsql SET search_path = ${quoteIdent(defaultSchema)} AS $chant$\n${dualWriteBody(o)}\n$chant$`,
@@ -304,6 +327,11 @@ export async function carryOver(run: MigrationRun): Promise<CarryResult> {
304
327
  result.carried.push(`${c.kind} ${c.tableName}.${c.name} -> ${c.target}`);
305
328
  const state = o.carriedStates.get(c.working);
306
329
  const comment = workingComment(n, run.marker, "carry", `${c.kind} ${c.name} on the new column`, { of: c.name });
330
+ if (c.kind === "index" && c.partitioned) {
331
+ if (await carryPartitionedIndex(run, n, c, comment, state)) result.built++;
332
+ else result.ready++;
333
+ continue;
334
+ }
307
335
  if (c.kind === "index" || c.kind === "key") {
308
336
  const ident = qualifiedIndex(c, c.working);
309
337
  if (state?.ready) {
@@ -340,6 +368,85 @@ export async function carryOver(run: MigrationRun): Promise<CarryResult> {
340
368
  return result;
341
369
  }
342
370
 
371
+ /**
372
+ * Carry over one index of a partitioned table (#3333). A partitioned index
373
+ * cannot be built `CONCURRENTLY`, so it is made the way the Postgres docs
374
+ * give for adding one without blocking writes: the partitioned index
375
+ * `ON ONLY` the table (nothing is built, and it is INVALID), each leaf
376
+ * partition's index built `CONCURRENTLY` on its own, then
377
+ * `ALTER INDEX ... ATTACH PARTITION` for each, deepest first. The partitioned
378
+ * index becomes valid when its last partition is attached. A partition
379
+ * attached to the table meanwhile gets its index from Postgres, and one that
380
+ * already has an index attached to this one is left alone, so a run after a
381
+ * crash goes on from where the last stopped. Returns whether anything was
382
+ * made.
383
+ */
384
+ export async function carryPartitionedIndex(run: MigrationRun, n: MigrationNames, c: CarriedObject, comment: string, state: CarriedState | undefined): Promise<boolean> {
385
+ if (state?.ready && state.marked) return false;
386
+ const root = qualifiedIndex(c, c.working);
387
+ const one = async <T>(sql: string, params: unknown[]): Promise<T | undefined> => (await run.client.query<T>(sql, params))[0];
388
+ const oidOf = async (ident: string): Promise<string | undefined> => (await one<{ oid: string | null }>("SELECT pg_catalog.to_regclass($1)::oid::text AS oid", [ident]))?.oid ?? undefined;
389
+ /** The index on `partitionOid` that is attached to `parentIndexOid`, whoever made it. */
390
+ const attachedIndex = async (parentIndexOid: string, partitionOid: string): Promise<{ oid: string; ident: string } | undefined> => {
391
+ const row = await one<{ oid: string; schema: string; name: string }>(
392
+ `SELECT x.oid::text AS oid, s.nspname AS schema, x.relname AS name FROM pg_catalog.pg_inherits h JOIN pg_catalog.pg_index i ON i.indexrelid = h.inhrelid
393
+ JOIN pg_catalog.pg_class x ON x.oid = i.indexrelid JOIN pg_catalog.pg_namespace s ON s.oid = x.relnamespace WHERE h.inhparent = $1::oid AND i.indrelid = $2::oid`,
394
+ [parentIndexOid, partitionOid],
395
+ );
396
+ return row ? { oid: String(row.oid), ident: `${quoteIdent(row.schema)}.${quoteIdent(row.name)}` } : undefined;
397
+ };
398
+ if (!state?.present) {
399
+ await inTransaction(run, async (exec) => {
400
+ await exec(c.definition);
401
+ await exec(`COMMENT ON INDEX ${root} IS ${pgString(comment)}`);
402
+ });
403
+ } else if (!state.marked) {
404
+ await inTransaction(run, (exec) => exec(`COMMENT ON INDEX ${root} IS ${pgString(comment)}`));
405
+ }
406
+ const tree = (await partitionTree(run.client, c.tableOid)).filter((p) => p.oid !== c.tableOid);
407
+ const byOid = new Map(tree.map((p) => [p.oid, p]));
408
+ const ours = (p: { schema: string; name: string }) => `${quoteIdent(p.schema)}.${quoteIdent(partitionIndexName(p, c.working))}`;
409
+ const rootIndex = { oid: (await oidOf(root))!, ident: root };
410
+ /** The index a partition's index attaches to: the partitioned index above it, as it is now. */
411
+ const above = async (p: { parent?: string }): Promise<{ oid: string; ident: string }> => {
412
+ if (p.parent === c.tableOid || p.parent === undefined) return rootIndex;
413
+ const parent = byOid.get(p.parent)!;
414
+ const up = await above(parent);
415
+ return (await attachedIndex(up.oid, parent.oid)) ?? { oid: (await oidOf(ours(parent)))!, ident: ours(parent) };
416
+ };
417
+ // Top down: each partition without an index attached to its parent's gets a standalone one.
418
+ const pending: Array<{ p: (typeof tree)[number]; parent: { oid: string; ident: string } }> = [];
419
+ for (const p of tree) {
420
+ const parent = await above(p);
421
+ if (await attachedIndex(parent.oid, p.oid)) continue;
422
+ const ident = ours(p);
423
+ const cur = await one<{ valid: boolean; comment: string | null }>(
424
+ "SELECT i.indisvalid AS valid, pg_catalog.obj_description(i.indexrelid, 'pg_class') AS comment FROM pg_catalog.pg_index i WHERE i.indexrelid = pg_catalog.to_regclass($1)",
425
+ [ident],
426
+ );
427
+ if (cur && !cur.valid) await outsideTransaction(run, `DROP INDEX CONCURRENTLY IF EXISTS ${ident}`);
428
+ if (!cur || !cur.valid) {
429
+ const statement = partitionIndexStatement(c, p, partitionIndexName(p, c.working));
430
+ const dropInvalid = async () => {
431
+ const row = await one<{ invalid: boolean }>("SELECT NOT i.indisvalid AS invalid FROM pg_catalog.pg_index i WHERE i.indexrelid = pg_catalog.to_regclass($1)", [ident]);
432
+ if (row?.invalid) await run.client.query(`DROP INDEX CONCURRENTLY IF EXISTS ${ident}`);
433
+ };
434
+ if (p.leaf) await outsideTransaction(run, statement, { scan: true, cleanup: dropInvalid });
435
+ else await inTransaction(run, (exec) => exec(statement));
436
+ }
437
+ if (!cur || !cur.valid || cur.comment == null) {
438
+ const partitionComment = workingComment(n, run.marker, "carry", `${c.kind} ${c.name} on the new column, for partition ${p.name}`, { of: c.name });
439
+ await inTransaction(run, (exec) => exec(`COMMENT ON INDEX ${ident} IS ${pgString(partitionComment)}`));
440
+ }
441
+ pending.push({ p, parent });
442
+ }
443
+ // Bottom up: a partition's index is valid once its own partitions are attached.
444
+ for (const { p, parent } of [...pending].sort((a, b) => b.p.level - a.p.level)) {
445
+ await inTransaction(run, (exec) => exec(`ALTER INDEX ${parent.ident} ATTACH PARTITION ${ours(p)}`));
446
+ }
447
+ return true;
448
+ }
449
+
343
450
  // ── switch ─────────────────────────────────────────────────────────────
344
451
 
345
452
  export interface SwitchResult {
@@ -373,13 +480,17 @@ export interface SequenceState {
373
480
  * `serial` column's sequence is handed over with `OWNED BY`, takes the
374
481
  * declared integer type, and becomes the new column's default.
375
482
  */
376
- function sequenceStatements(o: MigrationObservation, state: SequenceState | undefined): { before: string[]; after: string[] } {
483
+ function sequenceStatements(o: MigrationObservation, state: SequenceState | undefined, at: "switch" | "contract" = "switch"): { before: string[]; after: string[] } {
377
484
  const seq = o.sequence;
378
485
  const n = o.names;
379
486
  const t = n.qualifiedTable;
380
- if (!seq || n.change !== "type") return { before: [], after: [] };
487
+ if (!seq || n.change !== (at === "switch" ? "type" : "rename")) return { before: [], after: [] };
488
+ if (at === "contract" && seq.kind === "serial") {
489
+ // The sequence goes to the new column before the old one is dropped, which would drop it; the default is set after.
490
+ return { before: [`ALTER SEQUENCE ${seq.ident} OWNED BY ${t}.${col(n.column)}`], after: [] };
491
+ }
381
492
  if (seq.kind === "identity") {
382
- if (!state) throw new Error(`${n.key}: the switch moves the identity of ${n.source} and needs its sequence's state, read under the switch's lock`);
493
+ if (!state) throw new Error(`${n.key}: the ${at} moves the identity of ${n.source} and needs its sequence's state, read under the switch's lock`);
383
494
  const g = o.column.generated;
384
495
  const options = [`SEQUENCE NAME ${seq.ident}`, ...(g?.options ? [g.options.trim().replace(/^\(|\)$/g, "")] : [])].join(" ");
385
496
  return {
@@ -540,6 +651,31 @@ export const identityOf = (marker: OwnershipMarker | undefined): { stack?: strin
540
651
  * catalog change under ACCESS EXCLUSIVE). The migration's receipts go with
541
652
  * them. Before the date, say until when and drop nothing.
542
653
  */
654
+ /**
655
+ * The contract's statements. Always: the trigger and its function, then the
656
+ * old column. A rename of a column with a default, serial or identity
657
+ * (`defaultAtContract`) also gives the declared default to the new column
658
+ * here, in the same transaction, so no writer sees both columns defaulted:
659
+ * a serial's sequence is handed over before the old column is dropped (which
660
+ * would drop it), an identity is dropped from the old column and added to
661
+ * the new one under the same sequence name, set to where the old one stood.
662
+ */
663
+ export function contractStatements(o: MigrationObservation, sequenceState?: SequenceState): string[] {
664
+ const n = o.names;
665
+ const t = n.qualifiedTable;
666
+ const old = o.old!;
667
+ const sequence = sequenceStatements(o, sequenceState, "contract");
668
+ const dflt = o.column.default ?? (o.sequence?.kind === "serial" ? old.default : undefined);
669
+ const out: string[] = [];
670
+ if (o.trigger) out.push(`DROP TRIGGER ${col(n.trigger)} ON ${t}`);
671
+ if (o.fn) out.push(`DROP FUNCTION ${n.qualifiedFn}()`);
672
+ out.push(...sequence.before);
673
+ out.push(`ALTER TABLE ${t} DROP COLUMN ${col(old.name)}`);
674
+ if (dflt !== undefined && n.change === "rename" && o.sequence?.kind !== "identity") out.push(`ALTER TABLE ${t} ALTER COLUMN ${col(n.column)} SET DEFAULT ${dflt}`);
675
+ out.push(...sequence.after);
676
+ return out;
677
+ }
678
+
543
679
  export async function contract(run: MigrationRun): Promise<ContractResult> {
544
680
  const plan = await retainPlan(run);
545
681
  if (!plan.oldColumn) return { state: plan.state, dropped: false };
@@ -550,9 +686,14 @@ export async function contract(run: MigrationRun): Promise<ContractResult> {
550
686
  const o = await observe(run);
551
687
  const n = o.names;
552
688
  await inTransaction(run, async (exec) => {
553
- if (o.trigger) await exec(`DROP TRIGGER ${col(n.trigger)} ON ${n.qualifiedTable}`);
554
- if (o.fn) await exec(`DROP FUNCTION ${n.qualifiedFn}()`);
555
- await exec(`ALTER TABLE ${n.qualifiedTable} DROP COLUMN ${col(o.old!.name)}`);
689
+ let state: SequenceState | undefined;
690
+ if (o.sequence?.kind === "identity" && n.change === "rename") {
691
+ // Writers wait from here, so the sequence's position read next is where the moved identity carries on.
692
+ await exec(`LOCK TABLE ${n.qualifiedTable} IN ACCESS EXCLUSIVE MODE`);
693
+ const [row] = await exec(`SELECT last_value::text AS last, is_called AS called FROM ${o.sequence.ident}`);
694
+ state = { lastValue: String(row?.last), isCalled: row?.called === true };
695
+ }
696
+ for (const sql of contractStatements(o, state)) await exec(sql);
556
697
  });
557
698
  await postgresReceiptStore(run.client, n.schema, identityOf(run.marker)).forget(batchPrefix(identityOf(run.marker), n.key));
558
699
  return { state: "done", dropped: true, oldColumn: plan.oldColumn };
@@ -619,7 +760,11 @@ export async function compensate(run: Pick<MigrationRun, "client" | "target" | "
619
760
  ).filter((c) => ours(c.comment, "carry"));
620
761
  const carriedIndexes = (
621
762
  await run.client.query<{ name: string; comment: string | null }>(
622
- "SELECT i.indexrelid::pg_catalog.regclass::text AS name, pg_catalog.obj_description(i.indexrelid, 'pg_class') AS comment FROM pg_catalog.pg_index i WHERE i.indrelid = $1::oid ORDER BY 1",
763
+ // The table's own indexes, and those on its partitions that no partitioned index has taken yet (an attached one goes with the partitioned index).
764
+ `SELECT i.indexrelid::pg_catalog.regclass::text AS name, pg_catalog.obj_description(i.indexrelid, 'pg_class') AS comment
765
+ FROM pg_catalog.pg_index i
766
+ WHERE (i.indrelid = $1::oid OR (i.indrelid IN (SELECT t.relid FROM pg_catalog.pg_partition_tree($1::oid::pg_catalog.regclass) t) AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_inherits h WHERE h.inhrelid = i.indexrelid)))
767
+ ORDER BY i.indrelid <> $1::oid, 1`,
623
768
  [rel.oid],
624
769
  )
625
770
  ).filter((c) => ours(c.comment, "carry"));
@@ -643,7 +788,7 @@ export async function compensate(run: Pick<MigrationRun, "client" | "target" | "
643
788
  dropped.push(`constraint ${c.name} on ${c.rel}`);
644
789
  }
645
790
  for (const c of carriedIndexes) {
646
- await exec(`DROP INDEX ${c.name}`);
791
+ await exec(`DROP INDEX IF EXISTS ${c.name}`);
647
792
  dropped.push(`index ${c.name}`);
648
793
  }
649
794
  for (const c of newColumns) {
@@ -271,6 +271,69 @@ export interface EventTriggerNode {
271
271
  function: NameNode;
272
272
  }
273
273
 
274
+ /** `CREATE OPERATOR [schema.]symbol (FUNCTION = f, LEFTARG = type, RIGHTARG = type, ...)`. */
275
+ export interface OperatorNode {
276
+ statement: "operator";
277
+ /** The schema, when written, then the operator's symbol. */
278
+ name: NameNode;
279
+ options: DefElem[];
280
+ }
281
+
282
+ /** `CREATE STATISTICS [IF NOT EXISTS] name [(kind, ...)] ON target, ... FROM table`. */
283
+ export interface StatisticsNode {
284
+ statement: "statistics";
285
+ ifNotExists: boolean;
286
+ name: NameNode;
287
+ /** `ndistinct`, `dependencies`, `mcv`, folded; empty when the server picks them all. */
288
+ kinds: string[];
289
+ /** Each column or parenthesized expression. */
290
+ targets: Span[];
291
+ table: NameNode;
292
+ }
293
+
294
+ /** One `name 'value'` of an `OPTIONS (...)` list. */
295
+ export interface GenericOption {
296
+ name: string;
297
+ nameSpan: Span;
298
+ value: Span;
299
+ }
300
+
301
+ /** `CREATE FOREIGN DATA WRAPPER name [HANDLER f | NO HANDLER] [VALIDATOR f | NO VALIDATOR] [OPTIONS (...)]`. */
302
+ export interface ForeignDataWrapperNode {
303
+ statement: "foreignDataWrapper";
304
+ name: NameNode;
305
+ /** The handler function; `null` for `NO HANDLER`. */
306
+ handler?: NameNode | null;
307
+ /** The validator function; `null` for `NO VALIDATOR`. */
308
+ validator?: NameNode | null;
309
+ options: GenericOption[];
310
+ }
311
+
312
+ /** `CREATE SERVER [IF NOT EXISTS] name [TYPE 't'] [VERSION 'v'] FOREIGN DATA WRAPPER w [OPTIONS (...)]`. */
313
+ export interface ForeignServerNode {
314
+ statement: "foreignServer";
315
+ ifNotExists: boolean;
316
+ name: NameNode;
317
+ type?: Span;
318
+ version?: Span;
319
+ wrapper: NameNode;
320
+ options: GenericOption[];
321
+ }
322
+
323
+ /** One member of a publication: a table (with its column list and row filter) or every table of a schema. */
324
+ export type PublicationItem =
325
+ | { kind: "table"; table: NameNode; columns?: Array<{ name: string; span: Span }>; where?: Span }
326
+ | { kind: "schema"; schema: NameNode };
327
+
328
+ /** `CREATE PUBLICATION name [FOR ALL TABLES | FOR TABLE ... | FOR TABLES IN SCHEMA ...] [WITH (...)]`. */
329
+ export interface PublicationNode {
330
+ statement: "publication";
331
+ name: NameNode;
332
+ allTables: boolean;
333
+ items: PublicationItem[];
334
+ options: DefElem[];
335
+ }
336
+
274
337
  export interface DomainNode {
275
338
  statement: "domain";
276
339
  name: NameNode;
@@ -418,6 +481,11 @@ export type StatementNode =
418
481
  | CollationNode
419
482
  | CastNode
420
483
  | EventTriggerNode
484
+ | OperatorNode
485
+ | StatisticsNode
486
+ | ForeignDataWrapperNode
487
+ | ForeignServerNode
488
+ | PublicationNode
421
489
  | DomainNode
422
490
  | ExtensionNode
423
491
  | RoutineNode
@@ -503,6 +571,15 @@ export const AGGREGATE_OPTIONS = [
503
571
  /** `CREATE COLLATION`'s options (sql-createcollation). */
504
572
  export const COLLATION_OPTIONS = ["locale", "lc_collate", "lc_ctype", "provider", "deterministic", "rules", "version"];
505
573
 
574
+ /** `CREATE OPERATOR`'s options (sql-createoperator); `procedure` is the old spelling of `function`. */
575
+ export const OPERATOR_OPTIONS = ["function", "procedure", "leftarg", "rightarg", "commutator", "negator", "restrict", "join", "hashes", "merges"];
576
+
577
+ /** `CREATE STATISTICS`'s kinds (sql-createstatistics). */
578
+ export const STATISTICS_KINDS = ["ndistinct", "dependencies", "mcv"];
579
+
580
+ /** `CREATE PUBLICATION ... WITH (...)`'s options (sql-createpublication); `publish_generated_columns` is 18's. */
581
+ export const PUBLICATION_OPTIONS = ["publish", "publish_via_partition_root", "publish_generated_columns"];
582
+
506
583
  /** What may follow `IS` inside a `b_expr`. */
507
584
  const B_EXPR_IS = [["DISTINCT", "FROM"], ["NOT", "DISTINCT", "FROM"], ["DOCUMENT"], ["NOT", "DOCUMENT"]];
508
585
 
@@ -1012,6 +1089,11 @@ class PgParser extends SqlCursor {
1012
1089
  if (this.accept("COLLATION")) return this.collation();
1013
1090
  if (this.accept("CAST")) return this.cast();
1014
1091
  if (this.acceptSeq("EVENT", "TRIGGER")) return this.eventTrigger();
1092
+ if (this.accept("OPERATOR")) return this.operator();
1093
+ if (this.accept("STATISTICS")) return this.statistics();
1094
+ if (this.acceptSeq("FOREIGN", "DATA", "WRAPPER")) return this.foreignDataWrapper();
1095
+ if (this.accept("SERVER")) return this.foreignServer();
1096
+ if (this.accept("PUBLICATION")) return this.publication();
1015
1097
  if (kw(this.peek(), "UNIQUE", "INDEX")) return this.index();
1016
1098
  }
1017
1099
  const persistence = this.persistence();
@@ -1021,7 +1103,7 @@ class PgParser extends SqlCursor {
1021
1103
  const recursive = this.accept("RECURSIVE");
1022
1104
  if (!orReplace && !recursive && persistence === undefined && this.acceptSeq("MATERIALIZED", "VIEW")) return this.view(true, false, false, false);
1023
1105
  if (this.accept("VIEW")) return this.view(false, orReplace, persistence === "temporary", recursive);
1024
- return this.fail("expected SCHEMA, TABLE, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, TRIGGER, EVENT TRIGGER, COLLATION, CAST, POLICY or ROLE");
1106
+ return this.fail("expected SCHEMA, TABLE, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, TRIGGER, EVENT TRIGGER, COLLATION, CAST, OPERATOR, STATISTICS, FOREIGN DATA WRAPPER, SERVER, PUBLICATION, POLICY or ROLE");
1025
1107
  }
1026
1108
 
1027
1109
  private schema(): SchemaNode {
@@ -1239,6 +1321,145 @@ class PgParser extends SqlCursor {
1239
1321
  return node;
1240
1322
  }
1241
1323
 
1324
+ /** An unqualified, database-wide object's name (a wrapper, a server, a publication). */
1325
+ private databaseName(what: string): NameNode {
1326
+ const { text, span } = this.nameTok(what);
1327
+ if (this.isPunct(".")) this.fail(`expected ${what} without a schema (it belongs to the database)`);
1328
+ return { pieces: [text], span };
1329
+ }
1330
+
1331
+ /** `( name 'value' [, ...] )`: a foreign data wrapper's or server's options. */
1332
+ private genericOptions(): GenericOption[] {
1333
+ this.expectPunct("(");
1334
+ const out: GenericOption[] = [];
1335
+ const seen = new Set<string>();
1336
+ for (;;) {
1337
+ const t = this.next();
1338
+ if (t.kind !== "ident" && t.kind !== "qident") this.fail("expected an option name", t);
1339
+ const nameSpan = this.span(this.idx(-1), []);
1340
+ const name = identValue(t);
1341
+ if (seen.has(name)) this.fail(`${name} is given twice`, t);
1342
+ seen.add(name);
1343
+ const v = this.next();
1344
+ if (v.kind !== "string" && v.kind !== "ref") this.fail("expected the option's value, a string", v);
1345
+ out.push({ name, nameSpan, value: this.span(this.idx(-1), v.kind === "ref" ? [v.part] : []) });
1346
+ if (this.acceptPunct(",")) continue;
1347
+ this.expectPunct(")");
1348
+ return out;
1349
+ }
1350
+ }
1351
+
1352
+ /** `[schema.]symbol`: an operator's name, its symbol made of operator characters. */
1353
+ private operatorName(): NameNode {
1354
+ const from = this.idx();
1355
+ const refs: number[] = [];
1356
+ const pieces: string[] = [];
1357
+ const symbol = (): void => {
1358
+ const t = this.next();
1359
+ if (t.kind !== "op") this.fail("expected the operator's symbol (operator characters, such as ===)", t);
1360
+ pieces.push(t.text);
1361
+ };
1362
+ if (this.peek()?.kind === "op") symbol();
1363
+ else {
1364
+ const t = this.next();
1365
+ this.colId(t, "a schema name");
1366
+ if (t.kind === "ref") refs.push(t.part);
1367
+ pieces.push(t.kind === "ref" ? "" : identValue(t));
1368
+ this.expectPunct(".");
1369
+ symbol();
1370
+ }
1371
+ return { pieces, span: this.span(from, refs) };
1372
+ }
1373
+
1374
+ private operator(): OperatorNode {
1375
+ if (kw(this.peek(), "CLASS", "FAMILY") && !this.isPunct(".", 1)) this.fail("expected the operator's name (CREATE OPERATOR CLASS and CREATE OPERATOR FAMILY are not declared)");
1376
+ const name = this.operatorName();
1377
+ const options = this.definition("an operator option");
1378
+ this.checkDefinition(options, OPERATOR_OPTIONS, [], "an operator");
1379
+ if (!options.some((o) => o.name === "function" || o.name === "procedure")) this.fail("expected FUNCTION (an operator needs the function it calls)", this.lastSeen());
1380
+ return { statement: "operator", name, options };
1381
+ }
1382
+
1383
+ private statistics(): StatisticsNode {
1384
+ const ifNotExists = this.ifNotExists();
1385
+ const name = this.qualifiedName("a statistics name");
1386
+ const kinds: string[] = [];
1387
+ if (this.isPunct("(")) {
1388
+ for (const k of this.nameList("a statistics kind")) {
1389
+ if (!STATISTICS_KINDS.includes(k.name)) this.fail(`expected a statistics kind (${STATISTICS_KINDS.join(", ")})`, this.tokens[k.span.from]);
1390
+ kinds.push(k.name);
1391
+ }
1392
+ }
1393
+ this.expect("ON");
1394
+ const targets: Span[] = [];
1395
+ do targets.push(this.expr(["FROM"]));
1396
+ while (this.acceptPunct(","));
1397
+ this.expect("FROM");
1398
+ const table = this.qualifiedName("a table name");
1399
+ return { statement: "statistics", ifNotExists, name, kinds, targets, table };
1400
+ }
1401
+
1402
+ private foreignDataWrapper(): ForeignDataWrapperNode {
1403
+ const node: ForeignDataWrapperNode = { statement: "foreignDataWrapper", name: this.databaseName("a foreign data wrapper name"), options: [] };
1404
+ for (;;) {
1405
+ if (this.accept("HANDLER")) node.handler = this.qualifiedName("a handler function");
1406
+ else if (this.acceptSeq("NO", "HANDLER")) node.handler = null;
1407
+ else if (this.accept("VALIDATOR")) node.validator = this.qualifiedName("a validator function");
1408
+ else if (this.acceptSeq("NO", "VALIDATOR")) node.validator = null;
1409
+ else if (this.accept("OPTIONS")) node.options = this.genericOptions();
1410
+ else return node;
1411
+ }
1412
+ }
1413
+
1414
+ private foreignServer(): ForeignServerNode {
1415
+ const ifNotExists = this.ifNotExists();
1416
+ const name = this.databaseName("a server name");
1417
+ const node: Partial<ForeignServerNode> & { statement: "foreignServer" } = { statement: "foreignServer", ifNotExists, name, options: [] };
1418
+ const string = (what: string): Span => {
1419
+ const t = this.next();
1420
+ if (t.kind !== "string" && t.kind !== "ref") this.fail(`expected ${what} as a string`, t);
1421
+ return this.span(this.idx(-1), t.kind === "ref" ? [t.part] : []);
1422
+ };
1423
+ if (this.accept("TYPE")) node.type = string("the server type");
1424
+ if (this.accept("VERSION")) node.version = string("the server version");
1425
+ if (!this.acceptSeq("FOREIGN", "DATA", "WRAPPER")) this.fail("expected FOREIGN DATA WRAPPER (a server names the wrapper it is for)");
1426
+ node.wrapper = this.qualifiedName("a foreign data wrapper name");
1427
+ if (this.accept("OPTIONS")) node.options = this.genericOptions();
1428
+ return node as ForeignServerNode;
1429
+ }
1430
+
1431
+ private publication(): PublicationNode {
1432
+ const node: PublicationNode = { statement: "publication", name: this.databaseName("a publication name"), allTables: false, items: [], options: [] };
1433
+ if (this.accept("FOR")) {
1434
+ if (this.acceptSeq("ALL", "TABLES")) node.allTables = true;
1435
+ else {
1436
+ let mode: "table" | "schema" | undefined;
1437
+ do {
1438
+ if (this.accept("TABLE")) mode = "table";
1439
+ else if (this.acceptSeq("TABLES", "IN", "SCHEMA")) mode = "schema";
1440
+ else if (mode === undefined) this.fail("expected ALL TABLES, TABLE or TABLES IN SCHEMA");
1441
+ if (mode === "schema") {
1442
+ node.items.push({ kind: "schema", schema: this.qualifiedName("a schema name") });
1443
+ continue;
1444
+ }
1445
+ this.accept("ONLY");
1446
+ const table = this.qualifiedName("a table name");
1447
+ if (this.isOp("*")) this.p++;
1448
+ const item: PublicationItem = { kind: "table", table };
1449
+ if (this.isPunct("(")) item.columns = this.nameList("a column name");
1450
+ if (this.accept("WHERE")) item.where = this.checkedParens();
1451
+ node.items.push(item);
1452
+ } while (this.acceptPunct(","));
1453
+ }
1454
+ }
1455
+ if (kw(this.peek(), "WITH") && this.isPunct("(", 1)) {
1456
+ this.p++;
1457
+ node.options = this.definition("a publication option");
1458
+ this.checkDefinition(node.options, PUBLICATION_OPTIONS, [], "a publication");
1459
+ }
1460
+ return node;
1461
+ }
1462
+
1242
1463
  private table(persistence: TableNode["persistence"]): TableNode {
1243
1464
  const ifNotExists = this.ifNotExists();
1244
1465
  const node: TableNode = { statement: "table", persistence, ifNotExists, name: this.qualifiedName("a table name"), columns: [], constraints: [], like: [] };
@@ -1850,6 +2071,17 @@ class PgParser extends SqlCursor {
1850
2071
  const node: CommentNode = { statement: "comment", objectType, target, cast: { source, target: castTarget } };
1851
2072
  if (!this.accept("NULL")) node.text = this.commentText();
1852
2073
  return node;
2074
+ } else if (this.acceptSeq("FOREIGN", "DATA", "WRAPPER")) {
2075
+ objectType = "FOREIGN DATA WRAPPER";
2076
+ target = this.qualifiedName("a foreign data wrapper name");
2077
+ } else if (this.accept("OPERATOR")) {
2078
+ objectType = "OPERATOR";
2079
+ target = this.operatorName();
2080
+ if (!this.isPunct("(")) this.fail("expected '(' and the operator's operand types");
2081
+ args = this.parenthesized();
2082
+ } else if (this.accept("SERVER", "PUBLICATION", "STATISTICS")) {
2083
+ objectType = this.lastSeen()!.text.toUpperCase();
2084
+ target = this.qualifiedName(`a ${objectType.toLowerCase()} name`);
1853
2085
  } else if (this.accept("AGGREGATE", "COLLATION")) {
1854
2086
  objectType = this.lastSeen()!.text.toUpperCase();
1855
2087
  target = this.qualifiedName(`an ${objectType.toLowerCase()} name`);
@@ -1861,7 +2093,7 @@ class PgParser extends SqlCursor {
1861
2093
  if (this.acceptSeq("MATERIALIZED", "VIEW")) objectType = "MATERIALIZED VIEW";
1862
2094
  else if (this.accept("TABLE", "COLUMN", "INDEX", "VIEW", "SEQUENCE", "TYPE", "DOMAIN", "SCHEMA", "EXTENSION", "FUNCTION", "PROCEDURE")) {
1863
2095
  objectType = this.tokens[this.idx(-1)]!.text.toUpperCase();
1864
- } else return this.fail("expected TABLE, COLUMN, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, SCHEMA, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, COLLATION, CAST, TRIGGER, EVENT TRIGGER, POLICY, ROLE or CONSTRAINT");
2096
+ } else return this.fail("expected TABLE, COLUMN, INDEX, VIEW, MATERIALIZED VIEW, SEQUENCE, TYPE, DOMAIN, SCHEMA, EXTENSION, FUNCTION, PROCEDURE, AGGREGATE, COLLATION, CAST, OPERATOR, TRIGGER, EVENT TRIGGER, STATISTICS, FOREIGN DATA WRAPPER, SERVER, PUBLICATION, POLICY, ROLE or CONSTRAINT");
1865
2097
  target = this.qualifiedName("an object name");
1866
2098
  if ((objectType === "FUNCTION" || objectType === "PROCEDURE") && this.isPunct("(")) args = this.parenthesized();
1867
2099
  }
@@ -43,6 +43,11 @@ const TAG: Record<string, (s: TemplateStringsArray) => { props: object }> = {
43
43
  [POSTGRES_ENTITY_TYPES.collation]: tags.collation,
44
44
  [POSTGRES_ENTITY_TYPES.cast]: tags.cast,
45
45
  [POSTGRES_ENTITY_TYPES.eventTrigger]: tags.eventTrigger,
46
+ [POSTGRES_ENTITY_TYPES.operator]: tags.operator,
47
+ [POSTGRES_ENTITY_TYPES.statistics]: tags.statistics,
48
+ [POSTGRES_ENTITY_TYPES.foreignDataWrapper]: tags.foreignDataWrapper,
49
+ [POSTGRES_ENTITY_TYPES.foreignServer]: tags.foreignServer,
50
+ [POSTGRES_ENTITY_TYPES.publication]: tags.publication,
46
51
  [POSTGRES_ENTITY_TYPES.extension]: tags.extension,
47
52
  [POSTGRES_ENTITY_TYPES.function]: tags.func,
48
53
  [POSTGRES_ENTITY_TYPES.procedure]: tags.procedure,
@@ -137,6 +142,18 @@ const PROP_OF_FIELD: Record<string, string[]> = {
137
142
  context: ["context"],
138
143
  event: ["event"],
139
144
  tags: ["tags"],
145
+ targets: ["targets"],
146
+ kinds: ["kinds"],
147
+ handler: ["handler", "handlerName", "noHandler"],
148
+ validator: ["validator", "validatorName", "noValidator"],
149
+ serverType: ["serverType"],
150
+ wrapper: ["wrapper", "wrapperName"],
151
+ allTables: ["allTables"],
152
+ tables: ["tables"],
153
+ schemas: ["schemas"],
154
+ publish: [],
155
+ viaRoot: [],
156
+ generatedColumns: [],
140
157
  };
141
158
 
142
159
  /** Props keys that hold constraints, by constraint kind. */
@@ -159,6 +176,7 @@ export function inDeclaredVocabulary(declared: Props, live: Props, d: CanonicalP
159
176
  if (d.schema === l.schema) adopt("schema");
160
177
  // An aggregate's argument types and a cast's two types are its identity: the same, they are the declaration's.
161
178
  if (d.kind === "aggregate" && d.signature === l.signature) for (const key of ["args", "orderBy"]) adopt(key);
179
+ if (d.kind === "operator" && d.signature === l.signature) for (const key of ["leftArg", "rightArg"]) adopt(key);
162
180
  if (d.kind === "cast" && d.name === l.name) for (const key of ["sourceType", "targetType"]) adopt(key);
163
181
 
164
182
  const fieldKeys = new Set([...Object.keys(d.fields), ...Object.keys(l.fields)]);
@@ -193,7 +211,16 @@ export function inDeclaredVocabulary(declared: Props, live: Props, d: CanonicalP
193
211
  ].map((c) => c.value);
194
212
  }
195
213
  // An aggregate's or a collation's `options` is the declaration's only when every field read from it is the same (a role's `options` shares the key).
196
- const optionFields = d.kind === "aggregate" ? ["definition"] : d.kind === "collation" ? ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"] : undefined;
214
+ const optionFields =
215
+ d.kind === "aggregate" || d.kind === "operator"
216
+ ? ["definition"]
217
+ : d.kind === "collation"
218
+ ? ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"]
219
+ : d.kind === "foreignDataWrapper" || d.kind === "foreignServer"
220
+ ? ["options"]
221
+ : d.kind === "publication"
222
+ ? ["publish", "viaRoot", "generatedColumns"]
223
+ : undefined;
197
224
  if (optionFields) {
198
225
  if (optionFields.every((f) => JSON.stringify(d.fields[f]) === JSON.stringify(l.fields[f]))) adopt("options");
199
226
  else out.options = live.options;