@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
package/src/sql.ts ADDED
@@ -0,0 +1,152 @@
1
+ import type { Cell, Engine, Value, ValueKind } from './protocol/index.ts';
2
+
3
+ export type SortDirection = 'asc' | 'desc';
4
+
5
+ export interface ColumnSort {
6
+ readonly column: string;
7
+ readonly direction: SortDirection;
8
+ }
9
+
10
+ /* The table a statement written for a copy or a filter points at. */
11
+ export interface SqlTarget {
12
+ readonly engine: Engine;
13
+ readonly table: string;
14
+ readonly schema?: string;
15
+ }
16
+
17
+ /* What a literal can be made of: a cell as a result holds it, a whole value, or the column's default. */
18
+ export type LiteralValue = Cell | Value | { readonly kind: 'default' };
19
+
20
+ const NUMERIC_KINDS: ReadonlySet<ValueKind> = new Set(['integer', 'decimal', 'float']);
21
+ const NUMERIC_TEXT = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
22
+ const ORDER_ITEM = /\s*(`(?:[^`]|``)+`|"(?:[^"]|"")+"|[A-Za-z_][\w$]*)(?:\s+(asc|desc))?\s*(,|$)/iy;
23
+
24
+ const markOf = (engine: Engine): string => (engine === 'mysql' ? '`' : '"');
25
+
26
+ export const quoteIdentifier = (engine: Engine, name: string): string => {
27
+ const mark = markOf(engine);
28
+ return `${mark}${name.replaceAll(mark, mark + mark)}${mark}`;
29
+ };
30
+
31
+ /* `schema.table` with both parts quoted, or the table alone. */
32
+ export const qualifiedName = (target: SqlTarget): string =>
33
+ target.schema === undefined || target.schema === ''
34
+ ? quoteIdentifier(target.engine, target.table)
35
+ : `${quoteIdentifier(target.engine, target.schema)}.${quoteIdentifier(target.engine, target.table)}`;
36
+
37
+ const stringLiteral = (engine: Engine, text: string): string => {
38
+ // MySQL reads a backslash as an escape unless the server runs with NO_BACKSLASH_ESCAPES.
39
+ const escaped = engine === 'mysql' ? text.replaceAll('\\', '\\\\') : text;
40
+ return `'${escaped.replaceAll("'", "''")}'`;
41
+ };
42
+
43
+ const binaryLiteral = (engine: Engine, hex: string): string => (engine === 'mysql' && hex !== '' ? `0x${hex}` : `X'${hex}'`);
44
+
45
+ /* A value as SQL source. `kind` lets a number that arrived as text (a decimal, a big integer) stay a number. */
46
+ export const sqlLiteral = (engine: Engine, value: LiteralValue, kind?: ValueKind): string => {
47
+ if (value === null) {
48
+ return 'NULL';
49
+ }
50
+ switch (typeof value) {
51
+ case 'boolean':
52
+ return engine === 'mysql' ? (value ? 'TRUE' : 'FALSE') : value ? '1' : '0';
53
+ case 'number':
54
+ return Number.isFinite(value) ? String(value) : 'NULL';
55
+ case 'string': {
56
+ const trimmed = value.trim();
57
+ return kind !== undefined && NUMERIC_KINDS.has(kind) && NUMERIC_TEXT.test(trimmed) ? trimmed : stringLiteral(engine, value);
58
+ }
59
+ }
60
+ switch (value.kind) {
61
+ case 'default':
62
+ return 'DEFAULT';
63
+ case 'binary':
64
+ return binaryLiteral(engine, value.hex);
65
+ case 'longText':
66
+ return stringLiteral(engine, value.preview);
67
+ }
68
+ };
69
+
70
+ /* `col = literal`, or `col IS NULL` since `= NULL` never matches. */
71
+ export const equalsCondition = (engine: Engine, column: string, value: LiteralValue, kind?: ValueKind): string =>
72
+ value === null ? `${quoteIdentifier(engine, column)} IS NULL` : `${quoteIdentifier(engine, column)} = ${sqlLiteral(engine, value, kind)}`;
73
+
74
+ /* `col <> literal`, or `col IS NOT NULL` since `<> NULL` never matches. */
75
+ export const notEqualsCondition = (engine: Engine, column: string, value: LiteralValue, kind?: ValueKind): string =>
76
+ value === null ? `${quoteIdentifier(engine, column)} IS NOT NULL` : `${quoteIdentifier(engine, column)} <> ${sqlLiteral(engine, value, kind)}`;
77
+
78
+ export interface ColumnMatch {
79
+ readonly column: string;
80
+ readonly value: LiteralValue;
81
+ readonly kind?: ValueKind;
82
+ }
83
+
84
+ /* `a = 1 AND b IS NULL` for the columns of a key; the text a WHERE field holds. */
85
+ export const matchCondition = (engine: Engine, matches: readonly ColumnMatch[]): string =>
86
+ matches.map((match) => equalsCondition(engine, match.column, match.value, match.kind)).join(' AND ');
87
+
88
+ /* The text after `ORDER BY`: every column quoted, every direction spelled out. */
89
+ export const orderByClause = (engine: Engine, sorts: readonly ColumnSort[]): string =>
90
+ sorts.map((sort) => `${quoteIdentifier(engine, sort.column)} ${sort.direction === 'asc' ? 'ASC' : 'DESC'}`).join(', ');
91
+
92
+ const unquote = (engine: Engine, word: string): string | null => {
93
+ const mark = word[0]!;
94
+ if (mark !== '`' && mark !== '"') {
95
+ return word;
96
+ }
97
+ // A double quoted word is a string in MySQL, which sorts by a constant and so is not a column.
98
+ if (mark === '"' && engine === 'mysql') {
99
+ return null;
100
+ }
101
+ return word.slice(1, -1).replaceAll(mark + mark, mark);
102
+ };
103
+
104
+ const readOrderBy = (engine: Engine, text: string, resolve: (word: string) => string | undefined): ColumnSort[] | null => {
105
+ const source = text.trim();
106
+ if (source === '') {
107
+ return [];
108
+ }
109
+ const sorts: ColumnSort[] = [];
110
+ let position = 0;
111
+ while (position < source.length) {
112
+ ORDER_ITEM.lastIndex = position;
113
+ const match = ORDER_ITEM.exec(source);
114
+ if (match === null) {
115
+ return null;
116
+ }
117
+ const word = unquote(engine, match[1]!);
118
+ const column = word === null ? undefined : resolve(word);
119
+ if (column === undefined || sorts.some((sort) => sort.column === column)) {
120
+ return null;
121
+ }
122
+ sorts.push({ column, direction: match[2]?.toLowerCase() === 'desc' ? 'desc' : 'asc' });
123
+ position = ORDER_ITEM.lastIndex;
124
+ if (match[3] === '') {
125
+ return sorts;
126
+ }
127
+ if (position >= source.length) {
128
+ return null;
129
+ }
130
+ }
131
+ return sorts;
132
+ };
133
+
134
+ const matchColumn = (columns: readonly string[], word: string): string | undefined =>
135
+ columns.find((name) => name === word) ?? columns.find((name) => name.toLowerCase() === word.toLowerCase());
136
+
137
+ /*
138
+ * The sorts an ORDER BY text says, or `null` when it is anything but a plain list of the given
139
+ * columns, each with an optional direction. Empty text is an empty list. A column is found without
140
+ * regard to case, since both engines treat names that way.
141
+ */
142
+ export const parseOrderBy = (engine: Engine, text: string, columns: readonly string[]): ColumnSort[] | null =>
143
+ readOrderBy(engine, text, (word) => matchColumn(columns, word));
144
+
145
+ /* The same for a text read before the columns are known, so a column keeps the spelling the text gave it. */
146
+ export const parseOrderByLoose = (engine: Engine, text: string): ColumnSort[] | null => readOrderBy(engine, text, (word) => word);
147
+
148
+ /* The column a word names, which may be quoted the way the engine quotes, or `undefined` when none of the columns is meant. */
149
+ export const findColumn = (engine: Engine, word: string, columns: readonly string[]): string | undefined => {
150
+ const bare = unquote(engine, word);
151
+ return bare === null ? undefined : matchColumn(columns, bare);
152
+ };
@@ -0,0 +1,269 @@
1
+ import { useEffect, useState, type ReactNode, type Ref } from 'react';
2
+ import clsx from 'clsx';
3
+ import { CircleAlert, Copy, KeyRound, Link2 } from 'lucide-react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Banner, Button, EmptyState, Icon, Pill, Spinner, Tabs, copyText, messageOf } from '@adecore/ui';
6
+ import { useDatabaseClient } from '../client-context.ts';
7
+ import type { Connection } from '../client/types.ts';
8
+ import type { ColumnInfo, ForeignKeyInfo, IndexInfo, TableStructure } from '../protocol/index.ts';
9
+ import { useSchemaChange } from '../use-schema-change.ts';
10
+ import { referenceOf } from './structure-text.ts';
11
+
12
+ export interface StructureViewProps {
13
+ connection: Connection;
14
+ schema: string;
15
+ table: string;
16
+ className?: string;
17
+ ref?: Ref<HTMLDivElement>;
18
+ }
19
+
20
+ type Load =
21
+ | { readonly status: 'loading' }
22
+ | { readonly status: 'error'; readonly message: string }
23
+ | { readonly status: 'ready'; readonly structure: TableStructure };
24
+
25
+ /* What a load answered, with the request it answered, so an answer to an earlier request is never taken for this one. */
26
+ interface Answer {
27
+ readonly connection: Connection;
28
+ readonly schema: string;
29
+ readonly table: string;
30
+ readonly attempt: number;
31
+ readonly load: Load;
32
+ }
33
+
34
+ const TH = 'sticky top-0 h-7 bg-surface px-3 text-left text-xs font-medium whitespace-nowrap text-text-faint';
35
+ const TD = 'h-7 border-t border-border-soft px-3 text-xs whitespace-nowrap text-text select-text';
36
+ const MONO = 'font-mono text-code';
37
+
38
+ /* The columns, indexes, foreign keys and DDL of one table or view. */
39
+ export function StructureView({ connection, schema, table, className, ref }: StructureViewProps) {
40
+ const { t } = useTranslation('database');
41
+ const client = useDatabaseClient();
42
+ const [answer, setAnswer] = useState<Answer | null>(null);
43
+ const [attempt, setAttempt] = useState(0);
44
+ // A schema change reloads without leaving the structure on screen: the answer keeps its place until the new one arrives.
45
+ const [reloads, setReloads] = useState(0);
46
+ const answered = answer !== null && answer.connection === connection && answer.schema === schema && answer.table === table && answer.attempt === attempt;
47
+ const load: Load = answered ? answer.load : { status: 'loading' };
48
+
49
+ useSchemaChange(connection.id, schema, () => setReloads((now) => now + 1));
50
+
51
+ useEffect(() => {
52
+ let current = true;
53
+ const settle = (outcome: Load): void => {
54
+ if (current) {
55
+ setAnswer({ connection, schema, table, attempt, load: outcome });
56
+ }
57
+ };
58
+ client
59
+ .session(connection)
60
+ .structure(schema, table)
61
+ .then(
62
+ (structure) => settle({ status: 'ready', structure }),
63
+ (e: unknown) => settle({ status: 'error', message: messageOf(e, t('structure.failed')) })
64
+ );
65
+ return () => {
66
+ current = false;
67
+ };
68
+ }, [client, connection, schema, table, attempt, reloads, t]);
69
+
70
+ return (
71
+ <div ref={ref} className={clsx('flex min-h-0 min-w-0 flex-col', className)}>
72
+ {load.status === 'loading' && (
73
+ <div className="grid grow place-items-center py-8 text-text-muted">
74
+ <Spinner size={20} label={t('structure.loading')} />
75
+ </div>
76
+ )}
77
+ {load.status === 'error' && (
78
+ <div className="p-3">
79
+ <Banner icon={CircleAlert} tone="error" className="w-full" message={load.message}>
80
+ <Button variant="secondary" size="sm" onClick={() => setAttempt(attempt + 1)}>
81
+ {t('structure.retry')}
82
+ </Button>
83
+ </Banner>
84
+ </div>
85
+ )}
86
+ {load.status === 'ready' && <StructureTabs structure={load.structure} />}
87
+ </div>
88
+ );
89
+ }
90
+
91
+ export function StructureTabs({ structure }: { structure: TableStructure }) {
92
+ const { t } = useTranslation('database');
93
+ return (
94
+ <Tabs.Root defaultValue="columns" className="flex min-h-0 grow flex-col">
95
+ <Tabs.List className="shrink-0 px-3">
96
+ <Tabs.Tab value="columns">
97
+ {t('structure.columns')}
98
+ <Tabs.Count value={structure.columns.length} />
99
+ </Tabs.Tab>
100
+ <Tabs.Tab value="indexes">
101
+ {t('structure.indexes')}
102
+ <Tabs.Count value={structure.indexes.length} />
103
+ </Tabs.Tab>
104
+ <Tabs.Tab value="foreignKeys">
105
+ {t('structure.foreignKeys')}
106
+ <Tabs.Count value={structure.foreignKeys.length} />
107
+ </Tabs.Tab>
108
+ <Tabs.Tab value="ddl">{t('structure.ddl')}</Tabs.Tab>
109
+ </Tabs.List>
110
+ <Tabs.Panel value="columns" className="min-h-0 grow overflow-auto">
111
+ <ColumnsTable structure={structure} />
112
+ </Tabs.Panel>
113
+ <Tabs.Panel value="indexes" className="min-h-0 grow overflow-auto">
114
+ <IndexesTable indexes={structure.indexes} />
115
+ </Tabs.Panel>
116
+ <Tabs.Panel value="foreignKeys" className="min-h-0 grow overflow-auto">
117
+ <ForeignKeysTable foreignKeys={structure.foreignKeys} />
118
+ </Tabs.Panel>
119
+ <Tabs.Panel value="ddl" className="min-h-0 grow overflow-auto">
120
+ <Ddl ddl={structure.ddl} />
121
+ </Tabs.Panel>
122
+ </Tabs.Root>
123
+ );
124
+ }
125
+
126
+ function Grid({ heads, children }: { heads: readonly string[]; children: ReactNode }) {
127
+ return (
128
+ <table className="w-full border-collapse">
129
+ <thead>
130
+ <tr>
131
+ {heads.map((head) => (
132
+ <th key={head} scope="col" className={TH}>
133
+ {head}
134
+ </th>
135
+ ))}
136
+ </tr>
137
+ </thead>
138
+ <tbody>{children}</tbody>
139
+ </table>
140
+ );
141
+ }
142
+
143
+ function YesNo({ value }: { value: boolean }) {
144
+ const { t } = useTranslation('database');
145
+ return <>{value ? t('structure.yes') : t('structure.no')}</>;
146
+ }
147
+
148
+ function ColumnsTable({ structure }: { structure: TableStructure }) {
149
+ const { t } = useTranslation('database');
150
+ const heads = [
151
+ t('structure.column.name'),
152
+ t('structure.column.type'),
153
+ t('structure.column.nullable'),
154
+ t('structure.column.default'),
155
+ t('structure.column.extra'),
156
+ t('structure.column.comment')
157
+ ];
158
+ const foreign = new Set(structure.foreignKeys.flatMap((key) => key.columns));
159
+ if (structure.columns.length === 0) {
160
+ return <EmptyState>{t('structure.noColumns')}</EmptyState>;
161
+ }
162
+ return (
163
+ <Grid heads={heads}>
164
+ {structure.columns.map((column) => (
165
+ <ColumnRow key={column.name} column={column} primary={structure.primaryKey.includes(column.name)} foreign={foreign.has(column.name)} />
166
+ ))}
167
+ </Grid>
168
+ );
169
+ }
170
+
171
+ function ColumnRow({ column, primary, foreign }: { column: ColumnInfo; primary: boolean; foreign: boolean }) {
172
+ const { t } = useTranslation('database');
173
+ return (
174
+ <tr>
175
+ <td className={TD}>
176
+ <span className="flex items-center gap-1.5">
177
+ {primary ? (
178
+ <Icon icon={KeyRound} size={12} className="shrink-0 text-status-needs-you" />
179
+ ) : foreign ? (
180
+ <Icon icon={Link2} size={12} className="shrink-0 text-text-muted" />
181
+ ) : (
182
+ <span className="size-3 shrink-0" />
183
+ )}
184
+ {column.name}
185
+ </span>
186
+ </td>
187
+ <td className={clsx(TD, MONO)}>{column.type}</td>
188
+ <td className={clsx(TD, 'text-text-muted')}>
189
+ <YesNo value={column.nullable} />
190
+ </td>
191
+ <td className={clsx(TD, MONO)}>{column.defaultValue}</td>
192
+ <td className={TD}>
193
+ <span className="flex gap-1">
194
+ {column.autoIncrement && <Pill>{t('structure.autoIncrement')}</Pill>}
195
+ {column.generated && <Pill>{t('structure.generated')}</Pill>}
196
+ </span>
197
+ </td>
198
+ <td className={clsx(TD, 'text-text-muted')}>{column.comment}</td>
199
+ </tr>
200
+ );
201
+ }
202
+
203
+ function IndexesTable({ indexes }: { indexes: readonly IndexInfo[] }) {
204
+ const { t } = useTranslation('database');
205
+ if (indexes.length === 0) {
206
+ return <EmptyState>{t('structure.noIndexes')}</EmptyState>;
207
+ }
208
+ return (
209
+ <Grid heads={[t('structure.index.name'), t('structure.index.columns'), t('structure.index.kind')]}>
210
+ {indexes.map((index) => (
211
+ <tr key={index.name}>
212
+ <td className={TD}>{index.name}</td>
213
+ <td className={clsx(TD, MONO)}>{index.columns.join(', ')}</td>
214
+ <td className={TD}>
215
+ <span className="flex gap-1">
216
+ {index.primary && <Pill tone="accent">{t('structure.primary')}</Pill>}
217
+ {index.unique && !index.primary && <Pill>{t('structure.unique')}</Pill>}
218
+ </span>
219
+ </td>
220
+ </tr>
221
+ ))}
222
+ </Grid>
223
+ );
224
+ }
225
+
226
+ function ForeignKeysTable({ foreignKeys }: { foreignKeys: readonly ForeignKeyInfo[] }) {
227
+ const { t } = useTranslation('database');
228
+ if (foreignKeys.length === 0) {
229
+ return <EmptyState>{t('structure.noForeignKeys')}</EmptyState>;
230
+ }
231
+ const action = (value: string | null): string => value ?? t('structure.engineDefault');
232
+ return (
233
+ <Grid
234
+ heads={[
235
+ t('structure.foreignKey.name'),
236
+ t('structure.foreignKey.columns'),
237
+ t('structure.foreignKey.references'),
238
+ t('structure.foreignKey.onUpdate'),
239
+ t('structure.foreignKey.onDelete')
240
+ ]}
241
+ >
242
+ {foreignKeys.map((key, i) => (
243
+ <tr key={key.name ?? i}>
244
+ <td className={TD}>{key.name}</td>
245
+ <td className={clsx(TD, MONO)}>{key.columns.join(', ')}</td>
246
+ <td className={clsx(TD, MONO)}>{referenceOf(key)}</td>
247
+ <td className={clsx(TD, 'text-text-muted')}>{action(key.onUpdate)}</td>
248
+ <td className={clsx(TD, 'text-text-muted')}>{action(key.onDelete)}</td>
249
+ </tr>
250
+ ))}
251
+ </Grid>
252
+ );
253
+ }
254
+
255
+ function Ddl({ ddl }: { ddl: string | null }) {
256
+ const { t } = useTranslation('database');
257
+ if (ddl === null) {
258
+ return <EmptyState>{t('structure.noDdl')}</EmptyState>;
259
+ }
260
+ return (
261
+ <div className="relative">
262
+ <Button variant="secondary" size="xs" className="absolute top-2 right-3" onClick={() => copyText(ddl)}>
263
+ <Icon icon={Copy} size={12} />
264
+ {t('structure.copy')}
265
+ </Button>
266
+ <pre className={clsx(MONO, 'overflow-auto p-3 pr-24 whitespace-pre text-text select-text')}>{ddl}</pre>
267
+ </div>
268
+ );
269
+ }
@@ -0,0 +1,4 @@
1
+ import type { ForeignKeyInfo } from '../protocol/index.ts';
2
+
3
+ /* The target of a foreign key as SQL writes it: `schema.table(column, column)`. */
4
+ export const referenceOf = (key: ForeignKeyInfo): string => `${key.referencedSchema}.${key.referencedTable}(${key.referencedColumns.join(', ')})`;