@adecore/database 0.0.1 → 0.16.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -1
  3. package/dist/DatabaseProvider.d.ts +13 -0
  4. package/dist/DatabaseProvider.js +17 -0
  5. package/dist/actions.d.ts +40 -0
  6. package/dist/actions.js +1 -0
  7. package/dist/client/client.d.ts +5 -0
  8. package/dist/client/client.js +245 -0
  9. package/dist/client/index.d.ts +2 -0
  10. package/dist/client/index.js +2 -0
  11. package/dist/client/types.d.ts +97 -0
  12. package/dist/client/types.js +13 -0
  13. package/dist/client-context.d.ts +16 -0
  14. package/dist/client-context.js +17 -0
  15. package/dist/connections/ConnectionForm.d.ts +10 -0
  16. package/dist/connections/ConnectionForm.js +145 -0
  17. package/dist/connections/ConnectionManager.d.ts +12 -0
  18. package/dist/connections/ConnectionManager.js +93 -0
  19. package/dist/connections/EngineIcon.d.ts +9 -0
  20. package/dist/connections/EngineIcon.js +7 -0
  21. package/dist/connections/connection-config.d.ts +16 -0
  22. package/dist/connections/connection-config.js +107 -0
  23. package/dist/connections/docker.d.ts +9 -0
  24. package/dist/connections/docker.js +38 -0
  25. package/dist/connections/engine-icons.d.ts +3 -0
  26. package/dist/connections/engine-icons.js +3 -0
  27. package/dist/connections/use-docker-containers.d.ts +17 -0
  28. package/dist/connections/use-docker-containers.js +26 -0
  29. package/dist/console/DestructiveDialog.d.ts +9 -0
  30. package/dist/console/DestructiveDialog.js +9 -0
  31. package/dist/console/HistoryPanel.d.ts +10 -0
  32. package/dist/console/HistoryPanel.js +15 -0
  33. package/dist/console/QueryConsole.d.ts +14 -0
  34. package/dist/console/QueryConsole.js +261 -0
  35. package/dist/console/SchemaPicker.d.ts +11 -0
  36. package/dist/console/SchemaPicker.js +8 -0
  37. package/dist/console/SqlEditor.d.ts +12 -0
  38. package/dist/console/SqlEditor.js +61 -0
  39. package/dist/console/StatementResultView.d.ts +18 -0
  40. package/dist/console/StatementResultView.js +32 -0
  41. package/dist/console/TransactionControls.d.ts +13 -0
  42. package/dist/console/TransactionControls.js +12 -0
  43. package/dist/console/destructive.d.ts +7 -0
  44. package/dist/console/destructive.js +43 -0
  45. package/dist/console/editing.d.ts +12 -0
  46. package/dist/console/editing.js +60 -0
  47. package/dist/console/history.d.ts +13 -0
  48. package/dist/console/history.js +43 -0
  49. package/dist/console/labels.d.ts +1 -0
  50. package/dist/console/labels.js +6 -0
  51. package/dist/console/outcome.d.ts +6 -0
  52. package/dist/console/outcome.js +11 -0
  53. package/dist/console/schemas.d.ts +2 -0
  54. package/dist/console/schemas.js +5 -0
  55. package/dist/console/shortcuts.d.ts +2 -0
  56. package/dist/console/shortcuts.js +3 -0
  57. package/dist/console/useConsoleHistory.d.ts +7 -0
  58. package/dist/console/useConsoleHistory.js +22 -0
  59. package/dist/ddl/alter-mysql.d.ts +4 -0
  60. package/dist/ddl/alter-mysql.js +79 -0
  61. package/dist/ddl/alter-sqlite.d.ts +4 -0
  62. package/dist/ddl/alter-sqlite.js +80 -0
  63. package/dist/ddl/alter.d.ts +4 -0
  64. package/dist/ddl/alter.js +24 -0
  65. package/dist/ddl/column.d.ts +9 -0
  66. package/dist/ddl/column.js +62 -0
  67. package/dist/ddl/create.d.ts +12 -0
  68. package/dist/ddl/create.js +74 -0
  69. package/dist/ddl/dialect.d.ts +12 -0
  70. package/dist/ddl/dialect.js +34 -0
  71. package/dist/ddl/diff.d.ts +34 -0
  72. package/dist/ddl/diff.js +111 -0
  73. package/dist/ddl/draft.d.ts +46 -0
  74. package/dist/ddl/draft.js +70 -0
  75. package/dist/ddl/index.d.ts +10 -0
  76. package/dist/ddl/index.js +10 -0
  77. package/dist/ddl/parse.d.ts +17 -0
  78. package/dist/ddl/parse.js +131 -0
  79. package/dist/ddl/statements.d.ts +6 -0
  80. package/dist/ddl/statements.js +12 -0
  81. package/dist/ddl/types.d.ts +6 -0
  82. package/dist/ddl/types.js +52 -0
  83. package/dist/ddl/validate.d.ts +7 -0
  84. package/dist/ddl/validate.js +38 -0
  85. package/dist/designer/ColumnsEditor.d.ts +2 -0
  86. package/dist/designer/ColumnsEditor.js +24 -0
  87. package/dist/designer/DesignerView.d.ts +25 -0
  88. package/dist/designer/DesignerView.js +33 -0
  89. package/dist/designer/ForeignKeysEditor.d.ts +7 -0
  90. package/dist/designer/ForeignKeysEditor.js +24 -0
  91. package/dist/designer/IndexesEditor.d.ts +2 -0
  92. package/dist/designer/IndexesEditor.js +13 -0
  93. package/dist/designer/NamePicker.d.ts +11 -0
  94. package/dist/designer/NamePicker.js +13 -0
  95. package/dist/designer/OptionsEditor.d.ts +2 -0
  96. package/dist/designer/OptionsEditor.js +17 -0
  97. package/dist/designer/SqlPreview.d.ts +12 -0
  98. package/dist/designer/SqlPreview.js +17 -0
  99. package/dist/designer/TableDesigner.d.ts +10 -0
  100. package/dist/designer/TableDesigner.js +103 -0
  101. package/dist/designer/edit.d.ts +17 -0
  102. package/dist/designer/edit.js +115 -0
  103. package/dist/designer/editor-props.d.ts +10 -0
  104. package/dist/designer/editor-props.js +1 -0
  105. package/dist/designer/plan.d.ts +9 -0
  106. package/dist/designer/plan.js +13 -0
  107. package/dist/designer/recover.d.ts +2 -0
  108. package/dist/designer/recover.js +18 -0
  109. package/dist/designer/useReferenceColumns.d.ts +3 -0
  110. package/dist/designer/useReferenceColumns.js +36 -0
  111. package/dist/explorer/DatabaseExplorer.d.ts +13 -0
  112. package/dist/explorer/DatabaseExplorer.js +264 -0
  113. package/dist/explorer/RowMenu.d.ts +11 -0
  114. package/dist/explorer/RowMenu.js +89 -0
  115. package/dist/explorer/TableDialog.d.ts +15 -0
  116. package/dist/explorer/TableDialog.js +50 -0
  117. package/dist/explorer/expansion.d.ts +7 -0
  118. package/dist/explorer/expansion.js +31 -0
  119. package/dist/explorer/table-statements.d.ts +5 -0
  120. package/dist/explorer/table-statements.js +12 -0
  121. package/dist/explorer/tree.d.ts +125 -0
  122. package/dist/explorer/tree.js +295 -0
  123. package/dist/explorer/use-explorer-loads.d.ts +12 -0
  124. package/dist/explorer/use-explorer-loads.js +109 -0
  125. package/dist/explorer/use-stored-expansion.d.ts +9 -0
  126. package/dist/explorer/use-stored-expansion.js +47 -0
  127. package/dist/grid/CellEditor.d.ts +9 -0
  128. package/dist/grid/CellEditor.js +29 -0
  129. package/dist/grid/CopyAsMenu.d.ts +7 -0
  130. package/dist/grid/CopyAsMenu.js +10 -0
  131. package/dist/grid/DataGrid.d.ts +36 -0
  132. package/dist/grid/DataGrid.js +512 -0
  133. package/dist/grid/EnumPicker.d.ts +17 -0
  134. package/dist/grid/EnumPicker.js +53 -0
  135. package/dist/grid/GridHeaderCell.d.ts +36 -0
  136. package/dist/grid/GridHeaderCell.js +69 -0
  137. package/dist/grid/RecordView.d.ts +20 -0
  138. package/dist/grid/RecordView.js +126 -0
  139. package/dist/grid/ValueDock.d.ts +11 -0
  140. package/dist/grid/ValueDock.js +28 -0
  141. package/dist/grid/aggregates.d.ts +22 -0
  142. package/dist/grid/aggregates.js +62 -0
  143. package/dist/grid/cell-range.d.ts +14 -0
  144. package/dist/grid/cell-range.js +11 -0
  145. package/dist/grid/column-layout.d.ts +18 -0
  146. package/dist/grid/column-layout.js +49 -0
  147. package/dist/grid/column-selection.d.ts +11 -0
  148. package/dist/grid/column-selection.js +28 -0
  149. package/dist/grid/column-view.d.ts +10 -0
  150. package/dist/grid/column-view.js +23 -0
  151. package/dist/grid/copy-formats.d.ts +15 -0
  152. package/dist/grid/copy-formats.js +80 -0
  153. package/dist/grid/display.d.ts +20 -0
  154. package/dist/grid/display.js +81 -0
  155. package/dist/grid/edit-value.d.ts +5 -0
  156. package/dist/grid/edit-value.js +55 -0
  157. package/dist/grid/enum-type.d.ts +7 -0
  158. package/dist/grid/enum-type.js +52 -0
  159. package/dist/grid/focused-value.d.ts +4 -0
  160. package/dist/grid/focused-value.js +19 -0
  161. package/dist/grid/layout.d.ts +22 -0
  162. package/dist/grid/layout.js +74 -0
  163. package/dist/grid/navigation.d.ts +15 -0
  164. package/dist/grid/navigation.js +52 -0
  165. package/dist/grid/row-selection.d.ts +9 -0
  166. package/dist/grid/row-selection.js +24 -0
  167. package/dist/grid/sort.d.ts +7 -0
  168. package/dist/grid/sort.js +7 -0
  169. package/dist/grid/types.d.ts +30 -0
  170. package/dist/grid/types.js +9 -0
  171. package/dist/host/helper-path.d.ts +7 -0
  172. package/dist/host/helper-path.js +15 -0
  173. package/dist/host/host.d.ts +2 -0
  174. package/dist/host/host.js +343 -0
  175. package/dist/host/index.d.ts +5 -0
  176. package/dist/host/index.js +4 -0
  177. package/dist/host/spawn.d.ts +7 -0
  178. package/dist/host/spawn.js +98 -0
  179. package/dist/host/types.d.ts +19 -0
  180. package/dist/host/types.js +1 -0
  181. package/dist/host/validate.d.ts +10 -0
  182. package/dist/host/validate.js +339 -0
  183. package/dist/index.d.ts +14 -0
  184. package/dist/index.js +13 -0
  185. package/dist/locales/en.json +577 -0
  186. package/dist/locales/nl.json +577 -0
  187. package/dist/locales.d.ts +4 -0
  188. package/dist/locales.js +14 -0
  189. package/dist/protocol/connection.d.ts +56 -0
  190. package/dist/protocol/connection.js +1 -0
  191. package/dist/protocol/errors.d.ts +7 -0
  192. package/dist/protocol/errors.js +1 -0
  193. package/dist/protocol/index.d.ts +5 -0
  194. package/dist/protocol/index.js +3 -0
  195. package/dist/protocol/messages.d.ts +250 -0
  196. package/dist/protocol/messages.js +2 -0
  197. package/dist/protocol/schema.d.ts +53 -0
  198. package/dist/protocol/schema.js +1 -0
  199. package/dist/protocol/values.d.ts +21 -0
  200. package/dist/protocol/values.js +15 -0
  201. package/dist/sql-split.d.ts +15 -0
  202. package/dist/sql-split.js +201 -0
  203. package/dist/sql.d.ts +29 -0
  204. package/dist/sql.js +104 -0
  205. package/dist/structure/StructureView.d.ts +14 -0
  206. package/dist/structure/StructureView.js +99 -0
  207. package/dist/structure/structure-text.d.ts +2 -0
  208. package/dist/structure/structure-text.js +2 -0
  209. package/dist/table/CommandField.d.ts +14 -0
  210. package/dist/table/CommandField.js +157 -0
  211. package/dist/table/ImportDialog.d.ts +27 -0
  212. package/dist/table/ImportDialog.js +28 -0
  213. package/dist/table/TableStatusBar.d.ts +19 -0
  214. package/dist/table/TableStatusBar.js +22 -0
  215. package/dist/table/TableToolbar.d.ts +43 -0
  216. package/dist/table/TableToolbar.js +16 -0
  217. package/dist/table/TableView.d.ts +13 -0
  218. package/dist/table/TableView.js +508 -0
  219. package/dist/table/command-field.d.ts +57 -0
  220. package/dist/table/command-field.js +203 -0
  221. package/dist/table/grid-rows.d.ts +23 -0
  222. package/dist/table/grid-rows.js +55 -0
  223. package/dist/table/import-mapping.d.ts +7 -0
  224. package/dist/table/import-mapping.js +28 -0
  225. package/dist/table/layout-store.d.ts +13 -0
  226. package/dist/table/layout-store.js +43 -0
  227. package/dist/table/paging.d.ts +12 -0
  228. package/dist/table/paging.js +20 -0
  229. package/dist/table/pending.d.ts +32 -0
  230. package/dist/table/pending.js +94 -0
  231. package/dist/table/references.d.ts +12 -0
  232. package/dist/table/references.js +21 -0
  233. package/dist/table/useLoaded.d.ts +7 -0
  234. package/dist/table/useLoaded.js +36 -0
  235. package/dist/table/useStoredLayout.d.ts +8 -0
  236. package/dist/table/useStoredLayout.js +39 -0
  237. package/dist/table/useTableTransfer.d.ts +51 -0
  238. package/dist/table/useTableTransfer.js +125 -0
  239. package/dist/testing/fake.d.ts +21 -0
  240. package/dist/testing/fake.js +372 -0
  241. package/dist/testing/index.d.ts +1 -0
  242. package/dist/testing/index.js +1 -0
  243. package/dist/use-popup-press.d.ts +1 -0
  244. package/dist/use-popup-press.js +24 -0
  245. package/dist/use-schema-change.d.ts +1 -0
  246. package/dist/use-schema-change.js +13 -0
  247. package/dist/use-stable-callback.d.ts +1 -0
  248. package/dist/use-stable-callback.js +9 -0
  249. package/dist/value/ValuePanel.d.ts +14 -0
  250. package/dist/value/ValuePanel.js +103 -0
  251. package/dist/value/draft.d.ts +33 -0
  252. package/dist/value/draft.js +44 -0
  253. package/dist/value/hex.d.ts +14 -0
  254. package/dist/value/hex.js +46 -0
  255. package/dist/value/json.d.ts +9 -0
  256. package/dist/value/json.js +71 -0
  257. package/dist/value/views.d.ts +22 -0
  258. package/dist/value/views.js +54 -0
  259. package/dist/workbench/DatabaseWorkbench.d.ts +9 -0
  260. package/dist/workbench/DatabaseWorkbench.js +179 -0
  261. package/dist/workbench/DesignerTab.d.ts +10 -0
  262. package/dist/workbench/DesignerTab.js +22 -0
  263. package/dist/workbench/TableTab.d.ts +10 -0
  264. package/dist/workbench/TableTab.js +8 -0
  265. package/dist/workbench/tabs.d.ts +42 -0
  266. package/dist/workbench/tabs.js +139 -0
  267. package/package.json +79 -3
  268. package/src/DatabaseProvider.tsx +37 -0
  269. package/src/actions.ts +34 -0
  270. package/src/client/client.ts +324 -0
  271. package/src/client/index.ts +18 -0
  272. package/src/client/types.ts +161 -0
  273. package/src/client-context.ts +35 -0
  274. package/src/connections/ConnectionForm.tsx +474 -0
  275. package/src/connections/ConnectionManager.tsx +224 -0
  276. package/src/connections/EngineIcon.tsx +18 -0
  277. package/src/connections/connection-config.ts +143 -0
  278. package/src/connections/docker.ts +50 -0
  279. package/src/connections/engine-icons.ts +5 -0
  280. package/src/connections/use-docker-containers.ts +41 -0
  281. package/src/console/DestructiveDialog.tsx +40 -0
  282. package/src/console/HistoryPanel.tsx +67 -0
  283. package/src/console/QueryConsole.tsx +439 -0
  284. package/src/console/SchemaPicker.tsx +32 -0
  285. package/src/console/SqlEditor.tsx +95 -0
  286. package/src/console/StatementResultView.tsx +176 -0
  287. package/src/console/TransactionControls.tsx +53 -0
  288. package/src/console/destructive.ts +60 -0
  289. package/src/console/editing.ts +77 -0
  290. package/src/console/history.ts +62 -0
  291. package/src/console/labels.ts +7 -0
  292. package/src/console/outcome.ts +14 -0
  293. package/src/console/schemas.ts +7 -0
  294. package/src/console/shortcuts.ts +4 -0
  295. package/src/console/useConsoleHistory.ts +39 -0
  296. package/src/ddl/alter-mysql.ts +90 -0
  297. package/src/ddl/alter-sqlite.ts +93 -0
  298. package/src/ddl/alter.ts +29 -0
  299. package/src/ddl/column.ts +74 -0
  300. package/src/ddl/create.ts +92 -0
  301. package/src/ddl/dialect.ts +53 -0
  302. package/src/ddl/diff.ts +164 -0
  303. package/src/ddl/draft.ts +136 -0
  304. package/src/ddl/index.ts +10 -0
  305. package/src/ddl/parse.ts +161 -0
  306. package/src/ddl/statements.ts +20 -0
  307. package/src/ddl/types.ts +60 -0
  308. package/src/ddl/validate.ts +47 -0
  309. package/src/designer/ColumnsEditor.tsx +204 -0
  310. package/src/designer/DesignerView.tsx +184 -0
  311. package/src/designer/ForeignKeysEditor.tsx +132 -0
  312. package/src/designer/IndexesEditor.tsx +63 -0
  313. package/src/designer/NamePicker.tsx +44 -0
  314. package/src/designer/OptionsEditor.tsx +101 -0
  315. package/src/designer/SqlPreview.tsx +56 -0
  316. package/src/designer/TableDesigner.tsx +186 -0
  317. package/src/designer/edit.ts +137 -0
  318. package/src/designer/editor-props.ts +13 -0
  319. package/src/designer/plan.ts +25 -0
  320. package/src/designer/recover.ts +20 -0
  321. package/src/designer/useReferenceColumns.ts +54 -0
  322. package/src/explorer/DatabaseExplorer.tsx +450 -0
  323. package/src/explorer/RowMenu.tsx +134 -0
  324. package/src/explorer/TableDialog.tsx +80 -0
  325. package/src/explorer/expansion.ts +47 -0
  326. package/src/explorer/table-statements.ts +18 -0
  327. package/src/explorer/tree.ts +453 -0
  328. package/src/explorer/use-explorer-loads.ts +157 -0
  329. package/src/explorer/use-stored-expansion.ts +66 -0
  330. package/src/grid/CellEditor.tsx +53 -0
  331. package/src/grid/CopyAsMenu.tsx +33 -0
  332. package/src/grid/DataGrid.tsx +871 -0
  333. package/src/grid/EnumPicker.tsx +108 -0
  334. package/src/grid/GridHeaderCell.tsx +243 -0
  335. package/src/grid/RecordView.tsx +293 -0
  336. package/src/grid/ValueDock.tsx +52 -0
  337. package/src/grid/aggregates.ts +94 -0
  338. package/src/grid/cell-range.ts +26 -0
  339. package/src/grid/column-layout.ts +71 -0
  340. package/src/grid/column-selection.ts +42 -0
  341. package/src/grid/column-view.ts +35 -0
  342. package/src/grid/copy-formats.ts +108 -0
  343. package/src/grid/display.ts +109 -0
  344. package/src/grid/edit-value.ts +60 -0
  345. package/src/grid/enum-type.ts +62 -0
  346. package/src/grid/focused-value.ts +23 -0
  347. package/src/grid/layout.ts +91 -0
  348. package/src/grid/navigation.ts +74 -0
  349. package/src/grid/row-selection.ts +35 -0
  350. package/src/grid/sort.ts +12 -0
  351. package/src/grid/types.ts +59 -0
  352. package/src/host/helper-path.ts +41 -0
  353. package/src/host/host.ts +456 -0
  354. package/src/host/index.ts +5 -0
  355. package/src/host/spawn.ts +124 -0
  356. package/src/host/types.ts +46 -0
  357. package/src/host/validate.ts +402 -0
  358. package/src/index.ts +37 -0
  359. package/src/locales/en.json +577 -0
  360. package/src/locales/nl.json +577 -0
  361. package/src/locales.ts +18 -0
  362. package/src/protocol/connection.ts +86 -0
  363. package/src/protocol/errors.ts +38 -0
  364. package/src/protocol/index.ts +33 -0
  365. package/src/protocol/messages.ts +168 -0
  366. package/src/protocol/schema.ts +76 -0
  367. package/src/protocol/values.ts +52 -0
  368. package/src/sql-split.ts +220 -0
  369. package/src/sql.ts +152 -0
  370. package/src/structure/StructureView.tsx +269 -0
  371. package/src/structure/structure-text.ts +4 -0
  372. package/src/table/CommandField.tsx +318 -0
  373. package/src/table/ImportDialog.tsx +168 -0
  374. package/src/table/TableStatusBar.tsx +91 -0
  375. package/src/table/TableToolbar.tsx +217 -0
  376. package/src/table/TableView.tsx +880 -0
  377. package/src/table/command-field.ts +285 -0
  378. package/src/table/grid-rows.ts +80 -0
  379. package/src/table/import-mapping.ts +37 -0
  380. package/src/table/layout-store.ts +62 -0
  381. package/src/table/paging.ts +33 -0
  382. package/src/table/pending.ts +143 -0
  383. package/src/table/references.ts +38 -0
  384. package/src/table/useLoaded.ts +58 -0
  385. package/src/table/useStoredLayout.ts +53 -0
  386. package/src/table/useTableTransfer.ts +187 -0
  387. package/src/testing/fake.ts +533 -0
  388. package/src/testing/index.ts +1 -0
  389. package/src/use-popup-press.ts +27 -0
  390. package/src/use-schema-change.ts +19 -0
  391. package/src/use-stable-callback.ts +12 -0
  392. package/src/value/ValuePanel.tsx +254 -0
  393. package/src/value/draft.ts +72 -0
  394. package/src/value/hex.ts +62 -0
  395. package/src/value/json.ts +74 -0
  396. package/src/value/views.ts +68 -0
  397. package/src/workbench/DatabaseWorkbench.tsx +348 -0
  398. package/src/workbench/DesignerTab.tsx +37 -0
  399. package/src/workbench/TableTab.tsx +27 -0
  400. package/src/workbench/tabs.ts +184 -0
@@ -0,0 +1,176 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { ChevronLeft, ChevronRight, CircleAlert, Download, PanelRight } from 'lucide-react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Banner, IconButton, Menu, Spinner } from '@adecore/ui';
5
+ import { formatDecimal, formatNumber } from '@adecore/ui/format';
6
+ import { DataGrid } from '../grid/DataGrid.tsx';
7
+ import { previewValueOf } from '../grid/focused-value.ts';
8
+ import type { FocusedCell, GridRow } from '../grid/types.ts';
9
+ import { ValueDock } from '../grid/ValueDock.tsx';
10
+ import type { Engine, FileFormat, StatementResult } from '../protocol/index.ts';
11
+ import type { SqlTarget } from '../sql.ts';
12
+ import { ValuePanel } from '../value/ValuePanel.tsx';
13
+
14
+ /* What "Copy as SQL INSERT" names the table of a result that comes from no table. */
15
+ const RESULT_TABLE = 'result';
16
+
17
+ const EXPORT_FORMATS: readonly FileFormat[] = ['csv', 'tsv', 'json', 'sql'];
18
+
19
+ /* Paging through the rows of a statement that reads, one page at a time. */
20
+ export interface ResultPager {
21
+ /* Counted from 1. */
22
+ readonly page: number;
23
+ readonly loading: boolean;
24
+ onPrevious(): void;
25
+ onNext(): void;
26
+ }
27
+
28
+ export interface StatementResultViewProps {
29
+ result: StatementResult;
30
+ /* The rows before the first one shown, so a later page counts on from the page before it. */
31
+ offset?: number;
32
+ /* Draws the controls of the page under the grid, in place of the note that more rows exist. */
33
+ pager?: ResultPager;
34
+ /* Offers the export menu in the footer of a result set; told the format chosen. */
35
+ onExport?(format: FileFormat): void;
36
+ exporting?: boolean;
37
+ /* The engine the statement ran on, which decides how "Copy as SQL INSERT" quotes. Without it that format is not offered. */
38
+ engine?: Engine;
39
+ valuePanelOpen?: boolean;
40
+ /* Offers the toggle of the value panel in the footer of a result set. */
41
+ onValuePanelOpenChange?(open: boolean): void;
42
+ }
43
+
44
+ /* One statement's outcome: a result set in a read-only grid, a count of affected rows, or the error the server gave. */
45
+ export function StatementResultView({
46
+ result,
47
+ offset = 0,
48
+ pager,
49
+ onExport,
50
+ exporting = false,
51
+ engine,
52
+ valuePanelOpen = false,
53
+ onValuePanelOpenChange
54
+ }: StatementResultViewProps) {
55
+ const { t } = useTranslation('database');
56
+ const [focus, setFocus] = useState<FocusedCell | null>(null);
57
+ const rows = useMemo(
58
+ () => (result.kind === 'rows' ? result.rows.map((cells, index): GridRow => ({ key: `row:${index}`, number: offset + index + 1, cells })) : []),
59
+ [result, offset]
60
+ );
61
+ const sqlTarget = useMemo((): SqlTarget | undefined => (engine === undefined ? undefined : { engine, table: RESULT_TABLE }), [engine]);
62
+ const elapsed = t('console.elapsed', { value: formatDecimal(result.elapsedMs) });
63
+
64
+ if (result.kind === 'error') {
65
+ return (
66
+ <div className="flex min-h-0 flex-1 flex-col gap-3 p-3">
67
+ <Banner
68
+ icon={CircleAlert}
69
+ tone="error"
70
+ className="min-w-0"
71
+ message={
72
+ <>
73
+ {result.error.message}
74
+ {result.error.sqlState !== undefined && (
75
+ <span className="ml-2 font-mono text-xs text-text-muted">{t('console.sqlState', { state: result.error.sqlState })}</span>
76
+ )}
77
+ </>
78
+ }
79
+ />
80
+ <p className="px-1 text-xs text-text-muted tabular-nums">{elapsed}</p>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ if (result.kind === 'done') {
86
+ return (
87
+ <div className="flex flex-col gap-1 p-4 text-sm text-text">
88
+ <p>{t('console.affected', { count: result.affected, formatted: formatNumber(result.affected) })}</p>
89
+ {result.lastInsertId !== null && <p className="text-xs text-text-muted">{t('console.lastInsertId', { id: String(result.lastInsertId) })}</p>}
90
+ <p className="text-xs text-text-muted tabular-nums">{elapsed}</p>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ const focusedRow = valuePanelOpen && focus !== null ? rows.find((row) => row.key === focus.rowKey) : undefined;
96
+ const focusedCell = focus === null ? undefined : focusedRow?.cells[focus.column];
97
+
98
+ return (
99
+ <>
100
+ <ValueDock
101
+ open={valuePanelOpen}
102
+ panel={
103
+ <ValuePanel
104
+ column={focus === null ? null : (result.columns[focus.column] ?? null)}
105
+ value={focusedCell === undefined ? undefined : previewValueOf(focusedCell)}
106
+ loading={false}
107
+ editable={false}
108
+ onClose={() => onValuePanelOpenChange?.(false)}
109
+ className="min-w-0 flex-1"
110
+ />
111
+ }
112
+ >
113
+ <DataGrid
114
+ label={t('console.result')}
115
+ columns={result.columns}
116
+ rows={rows}
117
+ empty={t('console.noRows')}
118
+ sqlTarget={sqlTarget}
119
+ onFocusedCellChange={setFocus}
120
+ />
121
+ </ValueDock>
122
+ <footer className="flex h-10 shrink-0 items-center gap-4 border-t border-border px-3 text-xs text-text-muted">
123
+ <span className="text-text tabular-nums">
124
+ {offset > 0 || pager !== undefined
125
+ ? t('console.rowRange', { from: formatNumber(offset + Math.min(1, rows.length)), to: formatNumber(offset + rows.length) })
126
+ : t('console.rows', { rows: formatNumber(result.rows.length) })}
127
+ </span>
128
+ {pager === undefined && result.hasMore && <span>{t('console.hasMore')}</span>}
129
+ <span className="tabular-nums">{elapsed}</span>
130
+ <span className="ml-auto flex items-center gap-1">
131
+ {pager !== undefined && (
132
+ <>
133
+ {pager.loading && <Spinner size={14} label={t('console.loading')} />}
134
+ <IconButton
135
+ icon={ChevronLeft}
136
+ size="sm"
137
+ label={t('console.previousPage')}
138
+ disabled={pager.page <= 1 || pager.loading}
139
+ onClick={pager.onPrevious}
140
+ />
141
+ <span className="min-w-12 text-center tabular-nums">{t('console.page', { page: formatNumber(pager.page) })}</span>
142
+ <IconButton
143
+ icon={ChevronRight}
144
+ size="sm"
145
+ label={t('console.nextPage')}
146
+ disabled={!result.hasMore || pager.loading}
147
+ onClick={pager.onNext}
148
+ />
149
+ </>
150
+ )}
151
+ {onExport !== undefined && (
152
+ <Menu.Root>
153
+ <IconButton icon={Download} size="sm" label={t('console.export')} disabled={exporting} render={<Menu.Trigger />} />
154
+ <Menu.Popup>
155
+ {EXPORT_FORMATS.map((format) => (
156
+ <Menu.Item key={format} onClick={() => onExport(format)}>
157
+ {t('console.exportAs', { format: format.toUpperCase() })}
158
+ </Menu.Item>
159
+ ))}
160
+ </Menu.Popup>
161
+ </Menu.Root>
162
+ )}
163
+ {onValuePanelOpenChange !== undefined && (
164
+ <IconButton
165
+ icon={PanelRight}
166
+ size="sm"
167
+ label={t('console.valuePanel')}
168
+ aria-pressed={valuePanelOpen}
169
+ onClick={() => onValuePanelOpenChange(!valuePanelOpen)}
170
+ />
171
+ )}
172
+ </span>
173
+ </footer>
174
+ </>
175
+ );
176
+ }
@@ -0,0 +1,53 @@
1
+ import type { Ref } from 'react';
2
+ import clsx from 'clsx';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Button, Pill, Segmented, Tooltip } from '@adecore/ui';
5
+
6
+ export type TransactionMode = 'auto' | 'manual';
7
+
8
+ export interface TransactionControlsProps {
9
+ mode: TransactionMode;
10
+ onModeChange(mode: TransactionMode): void;
11
+ /* Whether a transaction is open on the session, whichever mode began it. */
12
+ open: boolean;
13
+ /* The console is running a statement or settling a transaction. */
14
+ busy: boolean;
15
+ onCommit(): void;
16
+ onRollback(): void;
17
+ className?: string;
18
+ ref?: Ref<HTMLDivElement>;
19
+ }
20
+
21
+ /* Auto or manual commits, and while a transaction is open, the way to end it. */
22
+ export function TransactionControls({ mode, onModeChange, open, busy, onCommit, onRollback, className, ref }: TransactionControlsProps) {
23
+ const { t } = useTranslation('database');
24
+ return (
25
+ <div ref={ref} className={clsx('flex items-center gap-2', className)}>
26
+ <Tooltip label={t('console.transaction.hint')}>
27
+ <div className="flex">
28
+ <Segmented
29
+ value={mode}
30
+ onValueChange={onModeChange}
31
+ disabled={busy}
32
+ label={t('console.transaction.mode')}
33
+ options={[
34
+ { id: 'auto', label: t('console.transaction.auto') },
35
+ { id: 'manual', label: t('console.transaction.manual') }
36
+ ]}
37
+ />
38
+ </div>
39
+ </Tooltip>
40
+ {open && (
41
+ <>
42
+ <Pill tone="needsYou">{t('console.transaction.open')}</Pill>
43
+ <Button size="xs" variant="secondary" disabled={busy} onClick={onCommit}>
44
+ {t('console.transaction.commit')}
45
+ </Button>
46
+ <Button size="xs" variant="danger-outline" disabled={busy} onClick={onRollback}>
47
+ {t('console.transaction.rollback')}
48
+ </Button>
49
+ </>
50
+ )}
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,60 @@
1
+ import type { Engine } from '../protocol/index.ts';
2
+ import { splitStatements, tokenize } from '../sql-split.ts';
3
+
4
+ export type DestructiveKind = 'drop' | 'truncate' | 'delete' | 'update' | 'alterDrop';
5
+
6
+ /* A statement that takes data or structure away with no way back, and why it counts. */
7
+ export interface DestructiveStatement {
8
+ readonly sql: string;
9
+ readonly kind: DestructiveKind;
10
+ }
11
+
12
+ interface Word {
13
+ readonly word: string;
14
+ /* How many parentheses are open at the word. */
15
+ readonly depth: number;
16
+ }
17
+
18
+ const wordsOf = (sql: string, engine: Engine): Word[] => {
19
+ const words: Word[] = [];
20
+ let depth = 0;
21
+ for (const token of tokenize(sql, engine)) {
22
+ const text = sql.slice(token.start, token.end);
23
+ if (token.kind === 'word') {
24
+ words.push({ word: text.toUpperCase(), depth });
25
+ } else if (token.kind === 'punct' && text === '(') {
26
+ depth += 1;
27
+ } else if (token.kind === 'punct' && text === ')') {
28
+ depth = Math.max(0, depth - 1);
29
+ }
30
+ }
31
+ return words;
32
+ };
33
+
34
+ const kindOf = (sql: string, engine: Engine): DestructiveKind | null => {
35
+ const words = wordsOf(sql, engine);
36
+ const first = words[0]?.word;
37
+ if (first === 'DROP') {
38
+ return 'drop';
39
+ }
40
+ if (first === 'TRUNCATE') {
41
+ return 'truncate';
42
+ }
43
+ if (first === 'ALTER') {
44
+ return words.some(({ word }) => word === 'DROP') ? 'alterDrop' : null;
45
+ }
46
+ // A statement behind a `WITH` is the first DELETE or UPDATE outside any parentheses.
47
+ const writer = first === 'WITH' ? words.find(({ word, depth }) => depth === 0 && (word === 'DELETE' || word === 'UPDATE')) : words[0];
48
+ if (writer?.word !== 'DELETE' && writer?.word !== 'UPDATE') {
49
+ return null;
50
+ }
51
+ const filtered = words.some(({ word, depth }) => word === 'WHERE' && depth === 0);
52
+ return filtered ? null : writer.word === 'DELETE' ? 'delete' : 'update';
53
+ };
54
+
55
+ /* The statements of a script that drop, truncate, or delete or update without a `WHERE`. */
56
+ export const findDestructive = (sql: string, engine: Engine): DestructiveStatement[] =>
57
+ splitStatements(sql, engine).flatMap((statement) => {
58
+ const kind = kindOf(statement.text, engine);
59
+ return kind === null ? [] : [{ sql: statement.text, kind }];
60
+ });
@@ -0,0 +1,77 @@
1
+ /* One text change: the range replaced, what replaces it, and where the selection lands in the new text. */
2
+ export interface TextEdit {
3
+ readonly from: number;
4
+ readonly to: number;
5
+ readonly insert: string;
6
+ readonly selectionStart: number;
7
+ readonly selectionEnd: number;
8
+ }
9
+
10
+ export const INDENT = ' ';
11
+
12
+ export const applyEdit = (value: string, edit: TextEdit): string => value.slice(0, edit.from) + edit.insert + value.slice(edit.to);
13
+
14
+ const lineStartOf = (value: string, at: number): number => value.lastIndexOf('\n', at - 1) + 1;
15
+
16
+ const lineEndOf = (value: string, at: number): number => {
17
+ const newline = value.indexOf('\n', at);
18
+ return newline === -1 ? value.length : newline;
19
+ };
20
+
21
+ /* The whitespace a line begins with. */
22
+ const indentOf = (line: string): string => /^[ \t]*/.exec(line)![0];
23
+
24
+ /* The lines a selection touches. A selection that ends at the start of a line leaves that line out. */
25
+ const touchedLines = (value: string, start: number, end: number): { from: number; to: number } => {
26
+ const from = lineStartOf(value, start);
27
+ const last = end > start && value[end - 1] === '\n' ? end - 1 : end;
28
+ return { from, to: lineEndOf(value, last) };
29
+ };
30
+
31
+ /* Tab: four spaces at the caret, or one indent level on every touched line when the selection spans lines. */
32
+ export const indentEdit = (value: string, start: number, end: number): TextEdit => {
33
+ if (!value.slice(start, end).includes('\n')) {
34
+ return { from: start, to: end, insert: INDENT, selectionStart: start + INDENT.length, selectionEnd: start + INDENT.length };
35
+ }
36
+ const { from, to } = touchedLines(value, start, end);
37
+ const lines = value.slice(from, to).split('\n');
38
+ return {
39
+ from,
40
+ to,
41
+ insert: lines.map((line) => (line === '' ? line : INDENT + line)).join('\n'),
42
+ selectionStart: start + (lines[0] === '' ? 0 : INDENT.length),
43
+ selectionEnd: end + lines.filter((line) => line !== '').length * INDENT.length
44
+ };
45
+ };
46
+
47
+ /* Shift+Tab: takes one level of indentation off every touched line, four spaces or one tab. */
48
+ export const outdentEdit = (value: string, start: number, end: number): TextEdit | null => {
49
+ const { from, to } = touchedLines(value, start, end);
50
+ const lines = value.slice(from, to).split('\n');
51
+ const removed = lines.map((line) => {
52
+ if (line.startsWith('\t')) {
53
+ return 1;
54
+ }
55
+ return Math.min(INDENT.length, /^ */.exec(line)![0].length);
56
+ });
57
+ const total = removed.reduce((sum, count) => sum + count, 0);
58
+ if (total === 0) {
59
+ return null;
60
+ }
61
+ const firstLineStart = from;
62
+ return {
63
+ from,
64
+ to,
65
+ insert: lines.map((line, index) => line.slice(removed[index]!)).join('\n'),
66
+ selectionStart: Math.max(firstLineStart, start - removed[0]!),
67
+ selectionEnd: Math.max(firstLineStart, end - total)
68
+ };
69
+ };
70
+
71
+ /* Enter: a new line that starts with the indentation of the line the caret is on, up to the caret. */
72
+ export const newlineEdit = (value: string, start: number, end: number): TextEdit => {
73
+ const lineStart = lineStartOf(value, start);
74
+ const indent = indentOf(value.slice(lineStart, start));
75
+ const insert = `\n${indent}`;
76
+ return { from: start, to: end, insert, selectionStart: start + insert.length, selectionEnd: start + insert.length };
77
+ };
@@ -0,0 +1,62 @@
1
+ /* One run of the console, as the history list keeps it. */
2
+ export interface HistoryEntry {
3
+ readonly sql: string;
4
+ /* Milliseconds since the epoch. */
5
+ readonly at: number;
6
+ /* The name the connection had at the time. */
7
+ readonly connection: string;
8
+ readonly ok: boolean;
9
+ /* Rows read or affected, summed over the statements; `null` when the run failed before any count. */
10
+ readonly rows: number | null;
11
+ }
12
+
13
+ export const HISTORY_LIMIT = 200;
14
+
15
+ export const historyKey = (connectionId: string): string => `database:console-history:${connectionId}`;
16
+
17
+ const isEntry = (value: unknown): value is HistoryEntry => {
18
+ if (value === null || typeof value !== 'object') {
19
+ return false;
20
+ }
21
+ const entry = value as Record<string, unknown>;
22
+ return (
23
+ typeof entry.sql === 'string' &&
24
+ typeof entry.at === 'number' &&
25
+ typeof entry.connection === 'string' &&
26
+ typeof entry.ok === 'boolean' &&
27
+ (entry.rows === null || typeof entry.rows === 'number')
28
+ );
29
+ };
30
+
31
+ /* Reads what `serializeHistory` wrote. Anything that does not fit gives an empty list, or leaves out the entries that do not fit. */
32
+ export const parseHistory = (raw: string | null): HistoryEntry[] => {
33
+ if (raw === null) {
34
+ return [];
35
+ }
36
+ try {
37
+ const parsed: unknown = JSON.parse(raw);
38
+ return Array.isArray(parsed) ? parsed.filter(isEntry).slice(0, HISTORY_LIMIT) : [];
39
+ } catch {
40
+ return [];
41
+ }
42
+ };
43
+
44
+ export const serializeHistory = (entries: readonly HistoryEntry[]): string => JSON.stringify(entries);
45
+
46
+ /* The list with the run in front. A run of the same SQL as the latest one replaces it. */
47
+ export const addEntry = (entries: readonly HistoryEntry[], entry: HistoryEntry): HistoryEntry[] => {
48
+ const rest = entries[0]?.sql.trim() === entry.sql.trim() ? entries.slice(1) : entries;
49
+ return [entry, ...rest].slice(0, HISTORY_LIMIT);
50
+ };
51
+
52
+ /* The entries whose SQL holds every word of the query, in any case. */
53
+ export const searchHistory = (entries: readonly HistoryEntry[], query: string): readonly HistoryEntry[] => {
54
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
55
+ if (words.length === 0) {
56
+ return entries;
57
+ }
58
+ return entries.filter((entry) => {
59
+ const sql = entry.sql.toLowerCase();
60
+ return words.every((word) => sql.includes(word));
61
+ });
62
+ };
@@ -0,0 +1,7 @@
1
+ const LABEL_LENGTH = 32;
2
+
3
+ /* The tab of a statement: its SQL on one line, cut where it gets long. */
4
+ export const statementLabel = (sql: string): string => {
5
+ const line = sql.replace(/\s+/g, ' ').trim();
6
+ return line.length > LABEL_LENGTH ? `${line.slice(0, LABEL_LENGTH - 1).trimEnd()}…` : line;
7
+ };
@@ -0,0 +1,14 @@
1
+ import type { Engine, StatementResult } from '../protocol/index.ts';
2
+ import { firstKeyword } from '../sql-split.ts';
3
+
4
+ /* How a run ended, for the history: whether every statement worked, and how many rows they read or changed. */
5
+ export const outcomeOf = (results: readonly StatementResult[]): { readonly ok: boolean; readonly rows: number } => ({
6
+ ok: results.every((result) => result.kind !== 'error'),
7
+ rows: results.reduce((sum, result) => sum + (result.kind === 'rows' ? result.rows.length : result.kind === 'done' ? result.affected : 0), 0)
8
+ });
9
+
10
+ /* Whether the helper can wrap the statement to fetch a page of it: a read that starts with `SELECT` or `WITH`. */
11
+ export const isPageable = (sql: string, engine: Engine): boolean => {
12
+ const keyword = firstKeyword(sql, engine);
13
+ return keyword === 'SELECT' || keyword === 'WITH';
14
+ };
@@ -0,0 +1,7 @@
1
+ import type { SchemaInfo } from '../protocol/index.ts';
2
+
3
+ /* The schemas a person works in first, the ones the server keeps for itself last. */
4
+ export const orderSchemas = (schemas: readonly SchemaInfo[]): SchemaInfo[] => [
5
+ ...schemas.filter((schema) => !schema.system),
6
+ ...schemas.filter((schema) => schema.system)
7
+ ];
@@ -0,0 +1,4 @@
1
+ import { shortcut } from '@adecore/ui';
2
+
3
+ export const RUN_SHORTCUT = shortcut('Mod+Enter');
4
+ export const RUN_ALL_SHORTCUT = shortcut('Mod+Shift+Enter');
@@ -0,0 +1,39 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useDatabaseStorage } from '../client-context.ts';
3
+ import { addEntry, historyKey, parseHistory, serializeHistory, type HistoryEntry } from './history.ts';
4
+
5
+ interface Loaded {
6
+ readonly key: string;
7
+ readonly entries: readonly HistoryEntry[];
8
+ /* Whether the list differs from what the storage holds. */
9
+ readonly changed: boolean;
10
+ }
11
+
12
+ export interface ConsoleHistory {
13
+ readonly entries: readonly HistoryEntry[];
14
+ record(entry: HistoryEntry): void;
15
+ clear(): void;
16
+ }
17
+
18
+ /* The runs of one connection, kept in the app's storage when it has one and for the life of the console when it has not. */
19
+ export function useConsoleHistory(connectionId: string): ConsoleHistory {
20
+ const storage = useDatabaseStorage();
21
+ const key = historyKey(connectionId);
22
+ const [state, setState] = useState<Loaded>(() => ({ key, entries: parseHistory(storage?.get(key) ?? null), changed: false }));
23
+
24
+ if (state.key !== key) {
25
+ setState({ key, entries: parseHistory(storage?.get(key) ?? null), changed: false });
26
+ }
27
+
28
+ useEffect(() => {
29
+ if (state.changed) {
30
+ storage?.set(state.key, state.entries.length === 0 ? null : serializeHistory(state.entries));
31
+ }
32
+ }, [state, storage]);
33
+
34
+ return {
35
+ entries: state.entries,
36
+ record: (entry) => setState((current) => ({ ...current, entries: addEntry(current.entries, entry), changed: true })),
37
+ clear: () => setState((current) => ({ ...current, entries: [], changed: true }))
38
+ };
39
+ }
@@ -0,0 +1,90 @@
1
+ import { columnSpecSql } from './column.ts';
2
+ import { foreignKeySql, mysqlOptionsSql } from './create.ts';
3
+ import { columnList, quote, tableName, type Dialect } from './dialect.ts';
4
+ import type { TableDiff } from './diff.ts';
5
+ import type { ColumnDraft, IndexDraft, TableDraft } from './draft.ts';
6
+
7
+ const positionSql = (dialect: Dialect, after: string | null): string => (after === null ? 'FIRST' : `AFTER ${quote(dialect, after)}`);
8
+
9
+ const indexSql = (dialect: Dialect, index: IndexDraft): string =>
10
+ [
11
+ 'ADD',
12
+ index.unique ? 'UNIQUE INDEX' : 'INDEX',
13
+ ...(index.name === '' ? [] : [quote(dialect, index.name)]),
14
+ `(${columnList(dialect, index.columns)})`
15
+ ].join(' ');
16
+
17
+ const withPosition = (clause: string, position: string | null): string => (position === null ? clause : `${clause} ${position}`);
18
+
19
+ /*
20
+ * One `ALTER TABLE`, or two when it has to be. A position clause names the columns as they are
21
+ * called after a rename, and a foreign key cannot be dropped and added again under the same name
22
+ * in one statement, so both put the drops and renames in a statement of their own, first.
23
+ */
24
+ export const mysqlAlterSql = (dialect: Dialect, schema: string, original: TableDraft, draft: TableDraft, diff: TableDiff): string[] => {
25
+ const early: string[] = [];
26
+ const late: string[] = [];
27
+ const column = (verb: 'CHANGE' | 'MODIFY' | 'ADD', target: ColumnDraft, previous: string | null, position: string | null): string => {
28
+ const spec = columnSpecSql(dialect, target);
29
+ const named = verb === 'CHANGE' ? `${quote(dialect, previous!)} ${quote(dialect, target.name)}` : quote(dialect, target.name);
30
+ return withPosition(`${verb} COLUMN ${named} ${spec}`, position);
31
+ };
32
+
33
+ const positioned = diff.keptColumns.some((kept) => kept.moved) || diff.addedColumns.some((added) => added.placed);
34
+ const reusesName = diff.droppedForeignKeys.some((dropped) => dropped.name !== '' && diff.addedForeignKeys.some((added) => added.name === dropped.name));
35
+ const split = (positioned && diff.keptColumns.some((kept) => kept.renamed)) || reusesName;
36
+
37
+ for (const foreignKey of diff.droppedForeignKeys) {
38
+ early.push(`DROP FOREIGN KEY ${quote(dialect, foreignKey.name)}`);
39
+ }
40
+ for (const index of diff.droppedIndexes) {
41
+ early.push(`DROP INDEX ${quote(dialect, index.name)}`);
42
+ }
43
+ for (const { from, to } of diff.renamedIndexes) {
44
+ early.push(`RENAME INDEX ${quote(dialect, from.name)} TO ${quote(dialect, to.name)}`);
45
+ }
46
+ if (diff.primaryKeyChanged && original.primaryKey.length > 0) {
47
+ early.push('DROP PRIMARY KEY');
48
+ }
49
+ for (const dropped of diff.droppedColumns) {
50
+ early.push(`DROP COLUMN ${quote(dialect, dropped.name)}`);
51
+ }
52
+
53
+ for (const kept of diff.keptColumns) {
54
+ const verb = kept.renamed ? 'CHANGE' : 'MODIFY';
55
+ if (split) {
56
+ if (kept.renamed || kept.redefined) {
57
+ early.push(column(verb, kept.column, kept.original.name, null));
58
+ }
59
+ if (kept.moved) {
60
+ late.push(column('MODIFY', kept.column, null, positionSql(dialect, kept.after)));
61
+ }
62
+ } else if (kept.renamed || kept.redefined || kept.moved) {
63
+ early.push(column(verb, kept.column, kept.original.name, kept.moved ? positionSql(dialect, kept.after) : null));
64
+ }
65
+ }
66
+ for (const added of diff.addedColumns) {
67
+ late.push(column('ADD', added.column, null, added.placed ? positionSql(dialect, added.after) : null));
68
+ }
69
+
70
+ if (diff.primaryKeyChanged && draft.primaryKey.length > 0) {
71
+ late.push(`ADD PRIMARY KEY (${columnList(dialect, draft.primaryKey)})`);
72
+ }
73
+ for (const index of diff.addedIndexes) {
74
+ late.push(indexSql(dialect, index));
75
+ }
76
+ for (const foreignKey of diff.addedForeignKeys) {
77
+ late.push(`ADD ${foreignKeySql(dialect, schema, foreignKey)}`);
78
+ }
79
+ const options = diff.optionsChanged ? mysqlOptionsSql(draft.options, original.options).join(' ') : '';
80
+ if (options !== '') {
81
+ late.push(options);
82
+ }
83
+ if (diff.renamed) {
84
+ late.push(`RENAME TO ${quote(dialect, draft.name)}`);
85
+ }
86
+
87
+ const target = tableName(dialect, schema, original.name);
88
+ const statement = (clauses: readonly string[]): string[] => (clauses.length === 0 ? [] : [`ALTER TABLE ${target}\n ${clauses.join(',\n ')}`]);
89
+ return split ? [...statement(early), ...statement(late)] : statement([...early, ...late]);
90
+ };