@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
@@ -215,7 +215,7 @@ function diffUnparsed(key: string, before: PgDiffObject, after: PgDiffObject, ou
215
215
  }
216
216
 
217
217
  /** The kinds (#3911) whose definition has no ALTER: a change is a drop and a create, or (an aggregate's) CREATE OR REPLACE. */
218
- const REPLACED_KINDS: ReadonlySet<string> = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger"]);
218
+ const REPLACED_KINDS: ReadonlySet<string> = new Set(["composite", "range", "aggregate", "collation", "cast", "eventTrigger", "operator", "operatorClass", "operatorFamily", "statistics"]);
219
219
 
220
220
  /** What each of {@link REPLACED_KINDS} compares besides its name and comment. */
221
221
  const DEFINITION: Readonly<Record<string, readonly string[]>> = {
@@ -225,8 +225,126 @@ const DEFINITION: Readonly<Record<string, readonly string[]>> = {
225
225
  collation: ["provider", "lcCollate", "lcCtype", "locale", "nondeterministic", "rules"],
226
226
  cast: ["castMethod", "function", "context"],
227
227
  eventTrigger: ["event", "tags", "function"],
228
+ operator: ["definition"],
229
+ // A class's members, type, family and default flag have no ALTER; a family has no definition besides its name and method (its identity).
230
+ operatorClass: ["default", "dataType", "family", "items"],
231
+ operatorFamily: [],
232
+ statistics: ["table", "kinds", "targets"],
228
233
  };
229
234
 
235
+ /** The kinds (#3911) the server alters in place, with the fields that cannot be altered (the object is dropped and created) and those that can. */
236
+ const ALTERED_KINDS: Readonly<Record<string, { recreate: readonly string[]; inPlace: readonly string[]; rule?: PgClassifierRuleId }>> = {
237
+ foreignDataWrapper: { recreate: [], inPlace: ["handler", "validator", "options"] },
238
+ foreignServer: { recreate: ["serverType", "wrapper"], inPlace: ["version", "options"] },
239
+ publication: { recreate: ["allTables"], inPlace: ["tables", "schemas", "publish", "viaRoot", "generatedColumns"] },
240
+ // ALTER TEXT SEARCH DICTIONARY sets options and cannot change the template; ALTER TEXT SEARCH CONFIGURATION maps token types and cannot change the parser (SQLPG305).
241
+ textSearchDictionary: { recreate: ["template"], inPlace: ["options"], rule: "SQLPG305" },
242
+ textSearchConfiguration: { recreate: ["parser"], inPlace: ["mappings"], rule: "SQLPG305" },
243
+ };
244
+
245
+ /**
246
+ * A foreign data wrapper's, foreign server's or publication's change: the
247
+ * comment and each field ALTER reaches in place (SQLPG303); a field it does
248
+ * not reach (a server's type and wrapper, a publication's FOR ALL TABLES) by
249
+ * dropping and creating the object (SQLPG300), refused while something
250
+ * depends on it (SQLPG301).
251
+ */
252
+ function diffAltered(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[]): void {
253
+ if (!same(before.fields.comment, after.fields.comment)) out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
254
+ const { recreate, inPlace, rule = "SQLPG303" } = ALTERED_KINDS[after.kind]!;
255
+ const replaced = recreate.filter((k) => !same(before.fields[k], after.fields[k]));
256
+ // An object that lives in a schema is another object under another name: a rename between builds is a drop and a create.
257
+ if (after.schema !== undefined && (before.schema !== after.schema || before.name !== after.name)) replaced.unshift("name");
258
+ if (replaced.length > 0) {
259
+ const value = (o: PgDiffObject, k: string) => (k === "name" ? qualified(o) : (show(o.fields[k]) ?? "-"));
260
+ const was = replaced.map((k) => `${k} ${value(before, k)}`).join("; ");
261
+ const now = replaced.map((k) => `${k} ${value(after, k)}`).join("; ");
262
+ if (before.dependents && before.dependents.length > 0) {
263
+ out.push(change(key, replaced.join(", "), "SQLPG301", was, now, { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` }));
264
+ } else out.push(change(key, replaced.join(", "), "SQLPG300", was, now));
265
+ return;
266
+ }
267
+ for (const k of inPlace) if (!same(before.fields[k], after.fields[k])) out.push(change(key, k, rule, show(before.fields[k]), show(after.fields[k])));
268
+ }
269
+
270
+ /**
271
+ * Which of an operator's attributes `ALTER OPERATOR ... SET` can change from
272
+ * what the server holds (the Postgres 18 page, with the supported majors):
273
+ * RESTRICT and JOIN on every major, set or removed; from 17 COMMUTATOR and
274
+ * NEGATOR when the operator has none, and HASHES and MERGES when off, never
275
+ * back. Anything else about the definition is dropped and created.
276
+ */
277
+ function operatorAlterable(key: string, before: Record<string, unknown>, after: Record<string, unknown>, major: number): boolean {
278
+ if (key === "restrict" || key === "join") return true;
279
+ if (major < 17) return false;
280
+ if (key === "commutator" || key === "negator") return before[key] === undefined && after[key] !== undefined;
281
+ if (key === "hashes" || key === "merges") return before[key] === undefined && after[key] === "true";
282
+ return false;
283
+ }
284
+
285
+ /** An operator's change: in place (SQLPG304) when every attribute that changed is one ALTER OPERATOR reaches, else dropped and created like the other kinds without an ALTER. */
286
+ function diffOperator(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[], major: number): void {
287
+ const bo = (before.fields.definition ?? {}) as Record<string, unknown>;
288
+ const ao = (after.fields.definition ?? {}) as Record<string, unknown>;
289
+ const renamed = before.schema !== after.schema || before.name !== after.name || before.signature !== after.signature;
290
+ // In the order the page lists them, so the statement reads the same whatever the order the options were written in.
291
+ const ORDER = ["restrict", "join", "commutator", "negator", "hashes", "merges"];
292
+ const rank = (k: string) => (ORDER.includes(k) ? ORDER.indexOf(k) : ORDER.length);
293
+ const changed = [...new Set([...Object.keys(bo), ...Object.keys(ao)])].filter((k) => !same(bo[k], ao[k])).sort((a, b) => rank(a) - rank(b) || (a < b ? -1 : 1));
294
+ if (renamed || !changed.every((k) => operatorAlterable(k, bo, ao, major))) {
295
+ diffReplaced(key, before, after, out);
296
+ return;
297
+ }
298
+ if (!same(before.fields.comment, after.fields.comment)) out.push(change(key, "comment", "SQLPG216", show(before.fields.comment), show(after.fields.comment)));
299
+ const note = major < 17 ? undefined : "from 17 ALTER OPERATOR also sets a commutator's or negator's own commutator or negator";
300
+ for (const k of changed) out.push(change(key, `definition.${k}`, "SQLPG304", show(bo[k]), show(ao[k]), note && (k === "commutator" || k === "negator") ? { note } : {}));
301
+ }
302
+
303
+ /**
304
+ * A foreign table's columns, as ALTER FOREIGN TABLE changes them (SQLPG306):
305
+ * all catalog changes, none reading the remote side. A column is its name
306
+ * (a rename is a drop and an add); its comment is the generic comment rule.
307
+ */
308
+ function diffForeignColumns(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[]): void {
309
+ const byName = new Map(before.columns.map((c) => [c.name, c]));
310
+ const afterNames = new Set(after.columns.map((c) => c.name));
311
+ for (const b of before.columns) if (!afterNames.has(b.name)) out.push(change(key, `columns.${b.name}`, "SQLPG306", `${b.name} ${b.type ?? ""}`.trim(), undefined));
312
+ for (const a of after.columns) {
313
+ const b = byName.get(a.name);
314
+ if (!b) {
315
+ out.push(change(key, `columns.${a.name}`, "SQLPG306", undefined, `${a.name} ${a.type ?? ""}`.trim()));
316
+ continue;
317
+ }
318
+ const at = `columns.${a.name}`;
319
+ if (b.type !== a.type) out.push(change(key, `${at}.type`, "SQLPG306", b.type, a.type));
320
+ if (b.collate !== a.collate) out.push(change(key, `${at}.collate`, "SQLPG306", b.collate, a.collate));
321
+ if (b.default !== a.default) out.push(change(key, `${at}.default`, "SQLPG306", b.default, a.default));
322
+ if (b.notNull !== a.notNull) out.push(change(key, `${at}.notNull`, "SQLPG306", b.notNull ? "NOT NULL" : "NULL", a.notNull ? "NOT NULL" : "NULL"));
323
+ if (!same(b.options, a.options)) out.push(change(key, `${at}.options`, "SQLPG306", show(b.options), show(a.options)));
324
+ if (b.comment !== a.comment) out.push(change(key, `${at}.comment`, "SQLPG216", b.comment, a.comment));
325
+ }
326
+ }
327
+
328
+ /** A foreign table's CHECK constraints: a dropped one first, so a constraint replaced under its name is dropped before it is added. */
329
+ function diffForeignConstraints(key: string, before: PgDiffObject, after: PgDiffObject, out: PgChange[]): void {
330
+ const label = (c: CanonicalConstraint) => (c.name ? `${c.name}: ${c.body}` : c.body);
331
+ const remaining = [...before.constraints];
332
+ const added: CanonicalConstraint[] = [];
333
+ const comments: PgChange[] = [];
334
+ for (const a of after.constraints) {
335
+ const i = remaining.findIndex((b) => sameConstraint(a, b));
336
+ if (i < 0) {
337
+ added.push(a);
338
+ continue;
339
+ }
340
+ const b = remaining.splice(i, 1)[0]!;
341
+ if (b.comment !== a.comment) comments.push(change(key, `constraints.${a.name ?? b.name}.comment`, "SQLPG216", b.comment, a.comment));
342
+ }
343
+ for (const b of remaining) out.push(change(key, `constraints.${b.name ?? b.kind.toLowerCase()}`, "SQLPG306", label(b), undefined));
344
+ for (const a of added) out.push(change(key, `constraints.${a.name ?? a.kind.toLowerCase()}`, "SQLPG306", undefined, label(a)));
345
+ out.push(...comments);
346
+ }
347
+
230
348
  /**
231
349
  * A type's, aggregate's, collation's, cast's or event trigger's change: its
232
350
  * comment in place; its definition by CREATE OR REPLACE AGGREGATE where that
@@ -261,6 +379,15 @@ function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out:
261
379
  const [b, a] = f(name);
262
380
  if (!same(b, a)) out.push(change(key, name, rule, show(b), show(a), extra));
263
381
  };
382
+ if (after.kind === "operator") {
383
+ diffOperator(key, before, after, out, major);
384
+ return;
385
+ }
386
+ // A foreign table moved to another server is another table: ALTER FOREIGN TABLE has no SERVER action.
387
+ if (after.kind === "foreignTable" && before.schema === after.schema && before.name === after.name && !same(before.fields.server, after.fields.server)) {
388
+ out.push(change(key, "server", before.dependents && before.dependents.length > 0 ? "SQLPG301" : "SQLPG300", show(before.fields.server), show(after.fields.server), before.dependents && before.dependents.length > 0 ? { note: `${before.dependents.join(", ")} depend${before.dependents.length === 1 ? "s" : ""} on it` } : {}));
389
+ return;
390
+ }
264
391
  if (REPLACED_KINDS.has(after.kind)) {
265
392
  // A cast is named by its types, and an aggregate's argument types are its identity: either changed, between two builds, is another object.
266
393
  if (after.kind === "cast" && before.name !== after.name) {
@@ -270,6 +397,10 @@ function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out:
270
397
  diffReplaced(key, before, after, out);
271
398
  return;
272
399
  }
400
+ if (after.kind in ALTERED_KINDS) {
401
+ diffAltered(key, before, after, out);
402
+ return;
403
+ }
273
404
  if (before.schema !== after.schema || before.name !== after.name) {
274
405
  out.push(change(key, "name", after.kind === "index" ? "SQLPG229" : "SQLPG228", qualified(before), qualified(after)));
275
406
  }
@@ -386,6 +517,11 @@ function diffObject(key: string, before: PgDiffObject, after: PgDiffObject, out:
386
517
  field("attributes", "SQLPG294");
387
518
  field("connectionLimit", "SQLPG294");
388
519
  break;
520
+ case "foreignTable":
521
+ diffForeignColumns(key, before, after, out);
522
+ diffForeignConstraints(key, before, after, out);
523
+ field("options", "SQLPG306");
524
+ break;
389
525
  case "table":
390
526
  if (!same(before.fields.rowSecurity, after.fields.rowSecurity) || !same(before.fields.forceRowSecurity, after.fields.forceRowSecurity)) {
391
527
  const state = (o: PgDiffObject) => (o.fields.rowSecurity ? (o.fields.forceRowSecurity ? "enabled, forced" : "enabled") : o.fields.forceRowSecurity ? "disabled, forced" : "disabled");
@@ -51,6 +51,12 @@ import { stripMarker } from "../../core/ownership";
51
51
  import { sequenceDefaults } from "../live/catalog";
52
52
  import type {
53
53
  AggregateArgDef,
54
+ OperatorProps,
55
+ OperatorClassProps,
56
+ OperatorFamilyProps,
57
+ ForeignTableProps,
58
+ TextSearchConfigurationProps,
59
+ TextSearchDictionaryProps,
54
60
  AggregateProps,
55
61
  CastProps,
56
62
  CheckDef,
@@ -59,6 +65,10 @@ import type {
59
65
  CompositeTypeProps,
60
66
  DomainProps,
61
67
  EventTriggerProps,
68
+ StatisticsProps,
69
+ ForeignDataWrapperProps,
70
+ ForeignServerProps,
71
+ PublicationProps,
62
72
  RangeTypeProps,
63
73
  EnumProps,
64
74
  ExclusionDef,
@@ -318,6 +328,8 @@ export interface CanonicalColumn {
318
328
  collate?: string;
319
329
  compression?: string;
320
330
  storage?: string;
331
+ /** A foreign table column's `OPTIONS`, sorted by name. */
332
+ options?: Record<string, string>;
321
333
  comment?: string;
322
334
  }
323
335
 
@@ -348,6 +360,16 @@ export type CanonicalKind =
348
360
  | "collation"
349
361
  | "cast"
350
362
  | "eventTrigger"
363
+ | "operator"
364
+ | "operatorClass"
365
+ | "operatorFamily"
366
+ | "foreignTable"
367
+ | "textSearchConfiguration"
368
+ | "textSearchDictionary"
369
+ | "statistics"
370
+ | "foreignDataWrapper"
371
+ | "foreignServer"
372
+ | "publication"
351
373
  | "function"
352
374
  | "procedure"
353
375
  | "trigger"
@@ -393,6 +415,16 @@ const kindOf: Record<string, CanonicalKind> = {
393
415
  "Postgres::Collation": "collation",
394
416
  "Postgres::Cast": "cast",
395
417
  "Postgres::EventTrigger": "eventTrigger",
418
+ "Postgres::Operator": "operator",
419
+ "Postgres::OperatorClass": "operatorClass",
420
+ "Postgres::OperatorFamily": "operatorFamily",
421
+ "Postgres::ForeignTable": "foreignTable",
422
+ "Postgres::TextSearchConfiguration": "textSearchConfiguration",
423
+ "Postgres::TextSearchDictionary": "textSearchDictionary",
424
+ "Postgres::Statistics": "statistics",
425
+ "Postgres::ForeignDataWrapper": "foreignDataWrapper",
426
+ "Postgres::ForeignServer": "foreignServer",
427
+ "Postgres::Publication": "publication",
396
428
  "Postgres::Extension": "extension",
397
429
  "Postgres::Function": "function",
398
430
  "Postgres::Procedure": "procedure",
@@ -617,7 +649,7 @@ function triggerFields(p: TriggerProps, defaultSchema: string, fields: Record<st
617
649
  // ── Types, aggregates, collations, casts, event triggers (#3911) ──────
618
650
 
619
651
  /** The kinds that live in no schema. */
620
- const UNPLACED: ReadonlySet<CanonicalKind> = new Set(["schema", "extension", "role", "cast", "eventTrigger"]);
652
+ const UNPLACED: ReadonlySet<CanonicalKind> = new Set(["schema", "extension", "role", "cast", "eventTrigger", "foreignDataWrapper", "foreignServer", "publication"]);
621
653
 
622
654
  /**
623
655
  * A function named where an object calls it (an aggregate's SFUNC, a cast's
@@ -700,6 +732,179 @@ function collationFields(p: CollationProps, fields: Record<string, unknown>): vo
700
732
  fields.rules = o.rules;
701
733
  }
702
734
 
735
+ /** An operator's identity among its overloads: its operand types, `(integer,integer)`, `NONE` for a missing side. */
736
+ export function operatorSignature(p: Pick<OperatorProps, "leftArg" | "rightArg">, defaultSchema: string): string {
737
+ const side = (t: string | undefined) => (t === undefined ? "NONE" : canonicalArgType(t, defaultSchema));
738
+ return `(${side(p.leftArg)},${side(p.rightArg)})`;
739
+ }
740
+
741
+ /**
742
+ * An operator named where another object names it (a COMMUTATOR or NEGATOR):
743
+ * `OPERATOR(app.===)`, `app.===` or a bare symbol, which the server looks for
744
+ * in the new operator's own schema, as `schema.symbol`.
745
+ */
746
+ export function canonicalOperatorRef(text: string, ownSchema: string): string {
747
+ const inner = text.trim().replace(/^OPERATOR\s*\(\s*(.*?)\s*\)$/is, "$1");
748
+ const m = /^("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*\.\s*(\S.*)$/s.exec(inner);
749
+ return `${quoteIdent(m ? identValue(m[1]!) : ownSchema)}.${(m ? m[2]! : inner).trim()}`;
750
+ }
751
+
752
+ /** An operator's definition as compared: each function and operator named canonically, a flag only when set. */
753
+ function operatorOptions(p: OperatorProps, ownSchema: string, defaultSchema: string): Record<string, string> {
754
+ const out: Record<string, string> = {};
755
+ for (const [k, v] of Object.entries(p.options)) {
756
+ if (k === "function" || k === "restrict" || k === "join") out[k] = canonicalFunctionName(v, defaultSchema);
757
+ else if (k === "commutator" || k === "negator") out[k] = canonicalOperatorRef(v, ownSchema);
758
+ else if (k === "hashes" || k === "merges") {
759
+ if (isTrue(v)) out[k] = "true";
760
+ } else out[k] = defValue(v);
761
+ }
762
+ return Object.fromEntries(Object.entries(out).sort(([a], [b]) => (a < b ? -1 : 1)));
763
+ }
764
+
765
+ /**
766
+ * A name where an object is named (a text search parser, template or
767
+ * dictionary, an operator family): the schema left off when it is
768
+ * `pg_catalog` or the default schema, since a bare name is looked for in both.
769
+ * The catalog prints every schema, so both sides are brought to this form.
770
+ */
771
+ export function canonicalRef(text: string, defaultSchema: string): string {
772
+ const pieces = canonicalName(text).split(/\.(?=(?:[^"]*"[^"]*")*[^"]*$)/);
773
+ if (pieces.length >= 2) {
774
+ const schema = identValue(pieces[0]!);
775
+ return schema === "pg_catalog" || schema === defaultSchema ? pieces.slice(1).join(".") : pieces.join(".");
776
+ }
777
+ return pieces[0]!;
778
+ }
779
+
780
+ /** An operator named in an operator class: `OPERATOR(app.<<)`, `app.<<` or a bare symbol, by `canonicalRef`'s rule for the schema. */
781
+ export function canonicalOperatorName(text: string, defaultSchema: string): string {
782
+ const inner = text.trim().replace(/^OPERATOR\s*\(\s*(.*?)\s*\)$/is, "$1").trim();
783
+ const m = /^("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*\.\s*(\S.*)$/s.exec(inner);
784
+ if (!m) return inner;
785
+ const schema = identValue(m[1]!);
786
+ return schema === "pg_catalog" || schema === defaultSchema ? m[2]!.trim() : `${quoteIdent(schema)}.${m[2]!.trim()}`;
787
+ }
788
+
789
+ /** A comma list split at its top level: `numeric(12,2), text` is two items. */
790
+ function splitTopLevel(text: string): string[] {
791
+ const out: string[] = [];
792
+ let depth = 0;
793
+ let current = "";
794
+ for (const ch of text) {
795
+ if (ch === "(" || ch === "[") depth++;
796
+ else if (ch === ")" || ch === "]") depth--;
797
+ if (ch === "," && depth === 0) {
798
+ out.push(current.trim());
799
+ current = "";
800
+ } else current += ch;
801
+ }
802
+ if (current.trim() !== "") out.push(current.trim());
803
+ return out;
804
+ }
805
+
806
+ /**
807
+ * The operand types a support function of an operator class is registered
808
+ * for when `FUNCTION n (left, right)` does not say (sql-createopclass): a
809
+ * B-tree comparison function's (1) and a hash function's (1, 2) are the
810
+ * function's own input types, a B-tree `in_range` function's (3) its first
811
+ * and third, and every other support function's the class's type. The catalog
812
+ * always stores both, so a declaration is brought to them.
813
+ */
814
+ function supportTypes(method: string, number: number, classType: string, args: readonly string[]): [string, string] {
815
+ if (method === "btree" && number === 1) return [args[0] ?? classType, args[1] ?? args[0] ?? classType];
816
+ if (method === "btree" && number === 3) return [args[0] ?? classType, args[2] ?? classType];
817
+ if (method === "hash" && (number === 1 || number === 2)) return [args[0] ?? classType, args[0] ?? classType];
818
+ return [classType, classType];
819
+ }
820
+
821
+ /** An operator class as compared: its type, family, default flag and members, each member canonical and the list sorted. */
822
+ function operatorClassFields(p: OperatorClassProps, schema: string, defaultSchema: string, fields: Record<string, unknown>): void {
823
+ const type = canonicalType(p.dataType, defaultSchema)!;
824
+ fields.default = p.default === true;
825
+ fields.dataType = type;
826
+ // A class with no FAMILY is in a family of its own name; so is one that names that family.
827
+ const own = canonicalRef(`${quoteIdent(schema)}.${quoteIdent(p.name)}`, defaultSchema);
828
+ const named = p.family === undefined ? own : /^\s*("(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)\s*$/.test(p.family) && identValue(p.family.trim()) === p.name ? own : canonicalRef(p.family, defaultSchema);
829
+ fields.family = named === own ? undefined : named;
830
+ const side = (t: string) => (t.trim().toUpperCase() === "NONE" ? "NONE" : canonicalArgType(t, defaultSchema));
831
+ const members = p.items.map((i): { rank: number; number: number; text: string } => {
832
+ if (i.kind === "storage") return { rank: 2, number: 0, text: `storage ${canonicalType(i.type, defaultSchema)}` };
833
+ const n = Number(i.number);
834
+ if (i.kind === "operator") {
835
+ const [l, r] = i.types !== undefined ? splitTopLevel(i.types) : [type, type];
836
+ const sorted = i.orderBy !== undefined ? ` for order by ${canonicalRef(i.orderBy, defaultSchema)}` : "";
837
+ return { rank: 0, number: n, text: `operator ${n} ${canonicalOperatorName(i.operator, defaultSchema)} (${side(l ?? type)},${side(r ?? l ?? type)})${sorted}` };
838
+ }
839
+ const args = splitTopLevel(i.args).map((a) => canonicalArgType(a, defaultSchema));
840
+ const given = i.types !== undefined ? splitTopLevel(i.types).map(side) : undefined;
841
+ const [l, r] = given ? [given[0]!, given[1] ?? given[0]!] : supportTypes(p.method, n, type, args);
842
+ return { rank: 1, number: n, text: `function ${n} (${l},${r}) ${canonicalFunctionName(i.function, defaultSchema)}(${args.join(",")})` };
843
+ });
844
+ members.sort((a, b) => a.rank - b.rank || a.number - b.number || (a.text < b.text ? -1 : 1));
845
+ fields.items = members.map((m) => m.text);
846
+ }
847
+
848
+ /** A dictionary option's value as compared: a string constant's value, a quoted name's, a bare word folded, a number as written. */
849
+ function dictionaryValue(v: string): string {
850
+ const t = v.trim();
851
+ if (/^([Ee]?'|\$)/.test(t)) return stringValue(t) ?? t;
852
+ if (/^"/.test(t)) return identValue(t);
853
+ return t.toLowerCase();
854
+ }
855
+
856
+ /** A text search configuration's mappings as compared: each token type to its dictionaries in order, sorted by token type. */
857
+ function mappingFields(p: TextSearchConfigurationProps, defaultSchema: string): Record<string, string[]> | undefined {
858
+ const out: Record<string, string[]> = {};
859
+ for (const m of p.mappings) for (const t of m.tokens) out[t.toLowerCase()] = m.dictionaries.map((d) => canonicalRef(d, defaultSchema));
860
+ const entries = Object.entries(out).sort(([a], [b]) => (a < b ? -1 : 1));
861
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
862
+ }
863
+
864
+ /** An `OPTIONS (...)` list as compared: by name, none as nothing. */
865
+ function sortedOptions(options: Record<string, string> | undefined): Record<string, string> | undefined {
866
+ const entries = Object.entries(options ?? {}).sort(([a], [b]) => (a < b ? -1 : 1));
867
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
868
+ }
869
+
870
+ const STATISTICS_ALL = ["dependencies", "mcv", "ndistinct"];
871
+
872
+ /**
873
+ * The kinds of statistics gathered, sorted. Left out, the server builds all
874
+ * three, except for a lone expression, which gets none of them (only the
875
+ * expression's own statistics); `pg_get_statisticsobjdef` prints the list only
876
+ * when it is not that default.
877
+ */
878
+ export function statisticsKinds(kinds: readonly string[] | undefined, targets: readonly string[]): string[] | undefined {
879
+ if (kinds && kinds.length > 0) {
880
+ const sorted = [...new Set(kinds.map((k) => k.toLowerCase()))].sort();
881
+ return sorted.join() === STATISTICS_ALL.join() ? undefined : sorted;
882
+ }
883
+ const lone = targets.length === 1 && !/^"?[A-Za-z_][A-Za-z0-9_$]*"?$/.test(targets[0]!.trim());
884
+ return lone ? ["none"] : undefined;
885
+ }
886
+
887
+ /** Every action a publication publishes when `publish` is not given. */
888
+ const PUBLISH_ALL = ["delete", "insert", "truncate", "update"];
889
+
890
+ /** A publication as compared: its tables with their column lists and row filters, its schemas, and each option at its default left out. */
891
+ function publicationFields(p: PublicationProps, defaultSchema: string, fields: Record<string, unknown>): void {
892
+ fields.allTables = p.allTables === true;
893
+ const tables = p.tables.map((t) => ({
894
+ table: canonicalName(t.tableName, defaultSchema),
895
+ ...(t.columns && t.columns.length > 0 ? { columns: [...t.columns].sort() } : {}),
896
+ ...(t.where ? { where: canonicalExpr(t.where) } : {}),
897
+ }));
898
+ fields.tables = tables.length > 0 ? tables.sort((a, b) => (a.table < b.table ? -1 : 1)) : undefined;
899
+ fields.schemas = p.schemas.length > 0 ? p.schemas.map((s) => quoteIdent(identValue(s))).sort() : undefined;
900
+ const o = p.options;
901
+ const publish = o.publish === undefined ? PUBLISH_ALL : [...new Set(defValue(o.publish).split(",").map((x) => x.trim().toLowerCase()).filter(Boolean))].sort();
902
+ fields.publish = publish.join() === PUBLISH_ALL.join() ? undefined : publish;
903
+ fields.viaRoot = isTrue(o.publish_via_partition_root);
904
+ const generated = o.publish_generated_columns === undefined ? "none" : defValue(o.publish_generated_columns);
905
+ fields.generatedColumns = generated === "none" ? undefined : generated;
906
+ }
907
+
703
908
  /** A cast's name, `(source AS target)`, its types canonical. */
704
909
  export const castName = (p: Pick<CastProps, "sourceType" | "targetType">, defaultSchema: string): string =>
705
910
  `(${canonicalType(p.sourceType, defaultSchema)} AS ${canonicalType(p.targetType, defaultSchema)})`;
@@ -774,6 +979,77 @@ export function canonicalPgObject(entityType: string, props: Record<string, unkn
774
979
  fields.function = canonicalFunctionName(p.functionName, defaultSchema);
775
980
  break;
776
981
  }
982
+ case "operator": {
983
+ const p = props as unknown as OperatorProps;
984
+ base.signature = operatorSignature(p, defaultSchema);
985
+ fields.definition = operatorOptions(p, base.schema!, defaultSchema);
986
+ break;
987
+ }
988
+ case "operatorClass":
989
+ operatorClassFields(props as unknown as OperatorClassProps, base.schema!, defaultSchema, fields);
990
+ base.signature = ` USING ${(props as unknown as OperatorClassProps).method}`;
991
+ break;
992
+ case "operatorFamily":
993
+ base.signature = ` USING ${(props as unknown as OperatorFamilyProps).method}`;
994
+ break;
995
+ case "textSearchDictionary": {
996
+ const p = props as unknown as TextSearchDictionaryProps;
997
+ fields.template = canonicalRef(p.template, defaultSchema);
998
+ fields.options = sortedOptions(Object.fromEntries(Object.entries(p.options).map(([k, v]) => [k.toLowerCase(), dictionaryValue(v)])));
999
+ break;
1000
+ }
1001
+ case "textSearchConfiguration": {
1002
+ const p = props as unknown as TextSearchConfigurationProps;
1003
+ fields.parser = canonicalRef(p.parser, defaultSchema);
1004
+ fields.mappings = mappingFields(p, defaultSchema);
1005
+ break;
1006
+ }
1007
+ case "foreignTable": {
1008
+ const p = props as unknown as ForeignTableProps;
1009
+ p.columns.forEach((c: ColumnDef, position) => {
1010
+ const options = sortedOptions(c.options);
1011
+ columns.push({
1012
+ name: c.name,
1013
+ position,
1014
+ type: canonicalType(c.type, defaultSchema),
1015
+ notNull: c.notNull === true,
1016
+ default: canonicalExpr(c.default),
1017
+ collate: c.collate ? canonicalName(c.collate) : undefined,
1018
+ comment: comment(c.comment),
1019
+ ...(options ? { options } : {}),
1020
+ });
1021
+ });
1022
+ for (const c of p.checks) constraints.push({ kind: "CHECK", name: c.name, body: ["check", canonicalExpr(c.expr), c.noInherit ? "no inherit" : ""].filter(Boolean).join(" "), ...(c.notValid ? { notValid: true } : {}), comment: comment(c.comment) });
1023
+ fields.server = quoteIdent(p.serverName);
1024
+ fields.options = sortedOptions(p.options);
1025
+ break;
1026
+ }
1027
+ case "statistics": {
1028
+ const p = props as unknown as StatisticsProps;
1029
+ fields.table = canonicalName(p.tableName, defaultSchema);
1030
+ const targets = p.targets.map((t) => canonicalExpr(t)!).sort();
1031
+ fields.targets = targets;
1032
+ fields.kinds = statisticsKinds(p.kinds, p.targets);
1033
+ break;
1034
+ }
1035
+ case "foreignDataWrapper": {
1036
+ const p = props as unknown as ForeignDataWrapperProps;
1037
+ fields.handler = p.handlerName && !p.noHandler ? canonicalFunctionName(p.handlerName, defaultSchema) : undefined;
1038
+ fields.validator = p.validatorName && !p.noValidator ? canonicalFunctionName(p.validatorName, defaultSchema) : undefined;
1039
+ fields.options = sortedOptions(p.options);
1040
+ break;
1041
+ }
1042
+ case "foreignServer": {
1043
+ const p = props as unknown as ForeignServerProps;
1044
+ fields.serverType = p.serverType;
1045
+ fields.version = p.version;
1046
+ fields.wrapper = quoteIdent(identValue(p.wrapperName));
1047
+ fields.options = sortedOptions(p.options);
1048
+ break;
1049
+ }
1050
+ case "publication":
1051
+ publicationFields(props as unknown as PublicationProps, defaultSchema, fields);
1052
+ break;
777
1053
  case "function":
778
1054
  case "procedure": {
779
1055
  const p = props as unknown as RoutineProps;
@@ -360,15 +360,15 @@ export const PG_CLASSIFIER_RULES = {
360
360
  SQLPG300: rule(
361
361
  "SQLPG300",
362
362
  "metadata",
363
- "Drop and create a type, aggregate, collation, cast or event trigger",
364
- "A composite or range type's definition, a collation's, a cast's and an event trigger's have no ALTER, and an aggregate's argument types, state type and final function cannot be replaced: the object is dropped and created in one transaction. Nothing on the server depends on it (a column, a view, a function or a cast would make the DROP fail); the locks are on its catalog rows only.",
363
+ "Drop and create a type, aggregate, collation, cast, event trigger, operator, operator class or family, foreign table's server, text search object, statistics object, foreign server or publication",
364
+ "A composite or range type's definition, a collation's, a cast's, an event trigger's, an operator's function and operands, and any attribute of it that SQLPG304 does not alter, an operator class's members, type, family and default flag, an operator family's index method, and extended statistics' have no ALTER (ALTER OPERATOR CLASS and ALTER OPERATOR FAMILY change a name, an owner or a schema only), an aggregate's argument types, state type and final function cannot be replaced, and neither can a foreign server's type or wrapper, a foreign table's server, a text search configuration's parser, a text search dictionary's template or a publication's FOR ALL TABLES: the object is dropped and created in one transaction. Nothing on the server depends on it (a column, a view, an index, a function or a cast would make the DROP fail); the locks are on its catalog rows only. Dropped statistics are empty until the next ANALYZE, and a publication that is dropped stops its subscribers until it is created again.",
365
365
  `${DOCS}/sql-createtype.html`,
366
366
  ),
367
367
  SQLPG301: rule(
368
368
  "SQLPG301",
369
369
  "expand",
370
- "Change a type, aggregate, collation or cast that other objects depend on",
371
- "No ALTER makes the change, and DROP refuses while a column, a view, a function or another object depends on the object. Create the new definition under another name, move what depends on it, then drop the old.",
370
+ "Change a type, aggregate, collation, cast, operator, operator class, text search object, foreign table's server, foreign server or publication that other objects depend on",
371
+ "No ALTER makes the change, and DROP refuses while a column, a view, an index, a function or another object depends on the object. Create the new definition under another name, move what depends on it, then drop the old.",
372
372
  `${DOCS}/sql-droptype.html`,
373
373
  ),
374
374
  SQLPG302: rule(
@@ -378,6 +378,34 @@ export const PG_CLASSIFIER_RULES = {
378
378
  "CREATE OR REPLACE AGGREGATE (14 and later) replaces the aggregate's functions and options in the catalog; no table is locked. Its argument types, its result and its kind (plain, ordered-set, hypothetical-set) have to stay the same, so a change of the state type or the final function is dropped and created instead (SQLPG300).",
379
379
  `${DOCS}/sql-createaggregate.html`,
380
380
  ),
381
+ SQLPG303: rule(
382
+ "SQLPG303",
383
+ "metadata",
384
+ "Alter a foreign data wrapper, foreign server or publication in place",
385
+ "ALTER FOREIGN DATA WRAPPER (handler, validator, options), ALTER SERVER (version, options) and ALTER PUBLICATION (SET options, ADD and DROP TABLE and TABLES IN SCHEMA) change the object's catalog rows under a brief lock; no table is rewritten. A publication's change reaches its subscribers as the walsender next reads it: a table added is not copied to a subscription until the subscriber runs REFRESH PUBLICATION.",
386
+ `${DOCS}/sql-alterpublication.html`,
387
+ ),
388
+ SQLPG304: rule(
389
+ "SQLPG304",
390
+ "metadata",
391
+ "Alter an operator in place",
392
+ "ALTER OPERATOR ... SET changes the operator's catalog row under a brief lock; no index or table is read. RESTRICT and JOIN can be set or removed (NONE) on every supported major. From 17, COMMUTATOR and NEGATOR can be set when the operator has none, and HASHES and MERGES turned on, never off (the Postgres 18 page: \"Can only be changed if the operator does not have an existing commutator\", \"Can only be enabled and not disabled\"); setting a commutator or negator also sets the other operator's. On 14 to 16 only RESTRICT and JOIN are alterable, so any other change drops and creates the operator (SQLPG300), as does a change of function or operands on any major.",
393
+ `${DOCS}/sql-alteroperator.html`,
394
+ ),
395
+ SQLPG305: rule(
396
+ "SQLPG305",
397
+ "metadata",
398
+ "Alter a text search configuration's mappings or a dictionary's options in place",
399
+ "ALTER TEXT SEARCH CONFIGURATION ADD MAPPING, ALTER MAPPING and DROP MAPPING change the token types mapped to dictionaries; ALTER TEXT SEARCH DICTIONARY sets a template's options, and an option named with no value is removed. Both change catalog rows under a brief lock and leave the parser or template as it is, so a parser or template change is dropped and created (SQLPG300). A tsvector already stored keeps the tokens it was built with: rebuild the columns and indexes that hold one when the mappings or options change what a word becomes. The same applies on 14 to 18.",
400
+ `${DOCS}/sql-altertsconfig.html`,
401
+ ),
402
+ SQLPG306: rule(
403
+ "SQLPG306",
404
+ "metadata",
405
+ "Alter a foreign table in place",
406
+ "ALTER FOREIGN TABLE adds and drops columns, changes a column's type, default, NOT NULL and OPTIONS, adds and drops CHECK and NOT NULL constraints, and sets the table's OPTIONS, all in the catalog. Nothing is checked against the remote side and no row is rewritten or scanned (\"It is the user's responsibility to ensure that the table definition matches the remote side\"), so a CHECK is added with no validation pass. A foreign table's server cannot be altered, so a change of server is dropped and created (SQLPG300).",
407
+ `${DOCS}/sql-alterforeigntable.html`,
408
+ ),
381
409
  SQLPG270: rule("SQLPG270", "drop", "Drop an object", "The object and, for a table, a materialized view or a sequence, its data are gone.", `${DOCS}/sql-droptable.html`),
382
410
  } as const satisfies Record<string, PgClassifierRule>;
383
411
 
@@ -106,7 +106,7 @@ export function pgSchemaFromBuildFile(path: string, defaultSchema = "public"): P
106
106
  */
107
107
  export function pgNamespace(kind: string): string {
108
108
  if (kind === "schema" || kind === "extension" || kind === "trigger" || kind === "policy" || kind === "role") return kind;
109
- if (kind === "collation" || kind === "cast" || kind === "eventTrigger") return kind;
109
+ if (kind === "collation" || kind === "cast" || kind === "eventTrigger" || kind === "operator" || kind === "operatorClass" || kind === "operatorFamily" || kind === "textSearchConfiguration" || kind === "textSearchDictionary" || kind === "statistics" || kind === "foreignDataWrapper" || kind === "foreignServer" || kind === "publication") return kind;
110
110
  if (kind === "grant" || kind === "defaultPrivileges") return "grant";
111
111
  if (kind === "enum" || kind === "domain" || kind === "composite" || kind === "range") return "type";
112
112
  if (kind === "function" || kind === "procedure" || kind === "aggregate") return "routine";
@@ -0,0 +1,78 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { classifyPgStatements } from "./statements";
3
+
4
+ const one = (sql: string) => classifyPgStatements([sql])[0]!;
5
+ const rules = (sql: string) => one(sql).changes.map((c) => c.rule);
6
+
7
+ describe("Postgres statements", () => {
8
+ test("CREATE INDEX without CONCURRENTLY is SQLPG241 with its SHARE lock", () => {
9
+ const v = one("CREATE INDEX orders_status_idx ON app.orders (status)");
10
+ expect(v.outcome).toBe("classified");
11
+ expect(v.changes).toMatchObject([{ rule: "SQLPG241", class: "concurrently", lock: "SHARE" }]);
12
+ });
13
+
14
+ test("CREATE INDEX CONCURRENTLY is SQLPG240", () => {
15
+ expect(one("CREATE UNIQUE INDEX CONCURRENTLY i ON t (a)").changes).toMatchObject([{ rule: "SQLPG240", lock: "SHARE UPDATE EXCLUSIVE" }]);
16
+ });
17
+
18
+ test("an index on a table the file creates needs no CONCURRENTLY", () => {
19
+ const [t, i] = classifyPgStatements(["CREATE TABLE t (id bigint PRIMARY KEY, a int)", "CREATE INDEX i ON t (a)"]);
20
+ expect(t!.changes[0]!.rule).toBe("SQLPG200");
21
+ expect(i!.changes[0]!.rule).toBe("SQLPG200");
22
+ });
23
+
24
+ test("ADD COLUMN splits by what the table must compute", () => {
25
+ expect(rules("ALTER TABLE t ADD COLUMN a int")).toEqual(["SQLPG201"]);
26
+ expect(rules("ALTER TABLE t ADD COLUMN a int NOT NULL DEFAULT 0")).toEqual(["SQLPG201"]);
27
+ expect(rules("ALTER TABLE t ADD COLUMN a int NOT NULL")).toEqual(["SQLPG203"]);
28
+ expect(rules("ALTER TABLE t ADD COLUMN a uuid DEFAULT gen_random_uuid()")).toEqual(["SQLPG202"]);
29
+ expect(rules("ALTER TABLE t ADD COLUMN a int GENERATED ALWAYS AS (b + 1) STORED")).toEqual(["SQLPG202"]);
30
+ });
31
+
32
+ test("a column type change is undecided between SQLPG206 and SQLPG207", () => {
33
+ const v = one("ALTER TABLE t ALTER COLUMN a TYPE bigint");
34
+ expect(v.outcome).toBe("undecided");
35
+ expect(v.candidates!.map((c) => c.rule)).toEqual(["SQLPG206", "SQLPG207"]);
36
+ expect(one("ALTER TABLE t ALTER COLUMN a TYPE int USING a::int").candidates!.map((c) => c.rule)).toEqual(["SQLPG206", "SQLPG207", "SQLPG208"]);
37
+ });
38
+
39
+ test("several actions in one ALTER TABLE are each classified", () => {
40
+ expect(rules("ALTER TABLE t ADD COLUMN a int, DROP COLUMN b, ALTER COLUMN c SET NOT NULL")).toEqual(["SQLPG201", "SQLPG204", "SQLPG210"]);
41
+ expect(one("ALTER TABLE t DROP COLUMN b").changes[0]!.destructive).toBe(true);
42
+ });
43
+
44
+ test("constraints", () => {
45
+ expect(rules("ALTER TABLE t ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES u (id) NOT VALID")).toEqual(["SQLPG217"]);
46
+ expect(rules("ALTER TABLE t ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES u (id)")).toEqual(["SQLPG219"]);
47
+ expect(rules("ALTER TABLE t ADD CONSTRAINT c CHECK (a > 0)")).toEqual(["SQLPG218"]);
48
+ expect(rules("ALTER TABLE t ADD PRIMARY KEY (id)")).toEqual(["SQLPG221"]);
49
+ expect(rules("ALTER TABLE t VALIDATE CONSTRAINT fk")).toEqual(["SQLPG220"]);
50
+ expect(rules("ALTER TABLE t DROP CONSTRAINT fk")).toEqual(["SQLPG223"]);
51
+ });
52
+
53
+ test("renames", () => {
54
+ expect(rules("ALTER TABLE t RENAME COLUMN a TO b")).toEqual(["SQLPG205"]);
55
+ expect(rules("ALTER TABLE t RENAME TO u")).toEqual(["SQLPG228"]);
56
+ expect(rules("ALTER INDEX i RENAME TO j")).toEqual(["SQLPG229"]);
57
+ });
58
+
59
+ test("DROP, GRANT, COMMENT and row security go through the parser or the verb", () => {
60
+ expect(rules("DROP INDEX CONCURRENTLY i")).toEqual(["SQLPG242"]);
61
+ expect(rules("DROP TABLE t")).toEqual(["SQLPG270"]);
62
+ expect(rules("GRANT SELECT ON TABLE t TO reader")).toEqual(["SQLPG296"]);
63
+ expect(rules("REVOKE SELECT ON TABLE t FROM reader")).toEqual(["SQLPG297"]);
64
+ expect(rules("COMMENT ON TABLE t IS 'x'")).toEqual(["SQLPG216"]);
65
+ expect(rules("ALTER TABLE t ENABLE ROW LEVEL SECURITY")).toEqual(["SQLPG293"]);
66
+ });
67
+
68
+ test("functions and views", () => {
69
+ expect(rules("CREATE OR REPLACE FUNCTION f() RETURNS int LANGUAGE sql AS $$ select 1; $$")).toEqual(["SQLPG280"]);
70
+ expect(one("CREATE OR REPLACE VIEW v AS SELECT 1 AS a").candidates!.map((c) => c.rule)).toEqual(["SQLPG250", "SQLPG251"]);
71
+ expect(rules("CREATE VIEW v AS SELECT 1 AS a")).toEqual(["SQLPG200"]);
72
+ });
73
+
74
+ test("data statements and unknown ALTERs", () => {
75
+ expect(one("UPDATE t SET a = 1").outcome).toBe("data");
76
+ expect(one("ALTER TABLE t OWNER TO app").outcome).toBe("unclassified");
77
+ });
78
+ });