@libredb/studio 0.9.7

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 (572) hide show
  1. package/.claude/settings.local.json +127 -0
  2. package/.cursorrules +426 -0
  3. package/.devin/wiki.json +143 -0
  4. package/.dockerignore +80 -0
  5. package/.env.example +159 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
  8. package/.github/PULL_REQUEST_TEMPLATE.md +57 -0
  9. package/.github/workflows/ci.yml +185 -0
  10. package/.github/workflows/codeql.yml +57 -0
  11. package/.github/workflows/docker-build-push.yml +118 -0
  12. package/.github/workflows/helm-release.yml +113 -0
  13. package/CLAUDE.md +265 -0
  14. package/CODE_OF_CONDUCT.md +124 -0
  15. package/CONTRIBUTING.md +154 -0
  16. package/Dockerfile +73 -0
  17. package/LICENSE +21 -0
  18. package/README.md +614 -0
  19. package/SECURITY.md +107 -0
  20. package/artifacthub-repo.yml +4 -0
  21. package/bun.lock +1714 -0
  22. package/bunfig.toml +3 -0
  23. package/charts/libredb-studio/.helmignore +11 -0
  24. package/charts/libredb-studio/Chart.lock +6 -0
  25. package/charts/libredb-studio/Chart.yaml +50 -0
  26. package/charts/libredb-studio/README.md +206 -0
  27. package/charts/libredb-studio/templates/NOTES.txt +59 -0
  28. package/charts/libredb-studio/templates/_helpers.tpl +135 -0
  29. package/charts/libredb-studio/templates/configmap.yaml +37 -0
  30. package/charts/libredb-studio/templates/deployment.yaml +184 -0
  31. package/charts/libredb-studio/templates/hpa.yaml +32 -0
  32. package/charts/libredb-studio/templates/ingress.yaml +41 -0
  33. package/charts/libredb-studio/templates/networkpolicy.yaml +50 -0
  34. package/charts/libredb-studio/templates/pdb.yaml +18 -0
  35. package/charts/libredb-studio/templates/pvc.yaml +23 -0
  36. package/charts/libredb-studio/templates/secret.yaml +30 -0
  37. package/charts/libredb-studio/templates/seed-configmap.yaml +11 -0
  38. package/charts/libredb-studio/templates/service.yaml +22 -0
  39. package/charts/libredb-studio/templates/serviceaccount.yaml +13 -0
  40. package/charts/libredb-studio/values.schema.json +246 -0
  41. package/charts/libredb-studio/values.yaml +286 -0
  42. package/components.json +22 -0
  43. package/conductor/code_styleguides/typescript.md +43 -0
  44. package/conductor/product-guidelines.md +43 -0
  45. package/conductor/product.md +3 -0
  46. package/conductor/setup_state.json +1 -0
  47. package/conductor/tech-stack.md +39 -0
  48. package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +8 -0
  49. package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +44 -0
  50. package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +31 -0
  51. package/conductor/tracks.md +8 -0
  52. package/conductor/workflow.md +333 -0
  53. package/database-compose.yml +55 -0
  54. package/docker/postgres-init/01-extensions.sql +10 -0
  55. package/docker/postgres-init/02-sample-data.sql +585 -0
  56. package/docker/postgres.yml +68 -0
  57. package/docker-compose.yml +38 -0
  58. package/docs/AI_PLAN.md +74 -0
  59. package/docs/API_DOCS.md +875 -0
  60. package/docs/ARCHITECTURE.md +218 -0
  61. package/docs/DATABASE_PROVIDERS.md +358 -0
  62. package/docs/FEATURES.md +116 -0
  63. package/docs/HELM_CHART.md +252 -0
  64. package/docs/LOGIN_PAGE.md +178 -0
  65. package/docs/MONACO_EDITOR_PERFORMANCE.md +315 -0
  66. package/docs/OIDC_ARCH.md +681 -0
  67. package/docs/OIDC_SETUP.md +322 -0
  68. package/docs/POSTGRES_METRICS.md +516 -0
  69. package/docs/QUERY_OPTIMIZATION.md +370 -0
  70. package/docs/SEED_CONNECTIONS.md +468 -0
  71. package/docs/SQL_ALIAS_COMPLETION.md +190 -0
  72. package/docs/STORAGE_ARCHITECTURE.md +565 -0
  73. package/docs/STORAGE_QUICK_SETUP.md +419 -0
  74. package/docs/TECHNICAL_PLAN.md +36 -0
  75. package/docs/THEMING.md +345 -0
  76. package/docs/adding-a-new-database-provider.md +642 -0
  77. package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +360 -0
  78. package/docs/backlogs/001-INLINE_DATA_EDITING.md +118 -0
  79. package/docs/backlogs/002-DATA_IMPORT.md +215 -0
  80. package/docs/backlogs/003-QUERY_TIME_MACHINE.md +183 -0
  81. package/docs/backlogs/004-AI_DATA_STORYTELLER.md +292 -0
  82. package/docs/backlogs/005-QUERY_PLAYGROUND.md +352 -0
  83. package/docs/backlogs/006-DATA_MASKING.md +418 -0
  84. package/docs/enterprise-features.md +718 -0
  85. package/docs/kubernetes-helm-chart-artifacthub-plan.md +803 -0
  86. package/docs/medium-koyeb-article-en.md +215 -0
  87. package/docs/plans/test-plans.md +445 -0
  88. package/docs/releases/RELEASE.V0.3.0.md +22 -0
  89. package/docs/releases/RELEASE.V0.4.0.md +154 -0
  90. package/docs/releases/RELEASE.V0.5.0.md +252 -0
  91. package/docs/releases/RELEASE_v0.5.6.md +145 -0
  92. package/docs/releases/RELEASE_v0.6.1.md +303 -0
  93. package/docs/releases/RELEASE_v0.6.7.md +292 -0
  94. package/docs/releases/RELEASE_v0.7.0.md +332 -0
  95. package/docs/releases/RELEASE_v0.8.0.md +521 -0
  96. package/docs/sampledb/titanic.sql +1379 -0
  97. package/docs/superpowers/plans/2026-03-25-seed-connections.md +1362 -0
  98. package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +590 -0
  99. package/e2e/admin-dashboard.spec.ts +64 -0
  100. package/e2e/connection-management.spec.ts +58 -0
  101. package/e2e/export.spec.ts +34 -0
  102. package/e2e/login.spec.ts +85 -0
  103. package/e2e/query-execution.spec.ts +35 -0
  104. package/e2e/tab-management.spec.ts +64 -0
  105. package/eslint.config.mjs +28 -0
  106. package/fly.toml +43 -0
  107. package/next.config.ts +32 -0
  108. package/package.json +130 -0
  109. package/playwright.config.ts +34 -0
  110. package/postcss.config.mjs +7 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/file.svg +1 -0
  114. package/public/globe.svg +1 -0
  115. package/public/logo.svg +32 -0
  116. package/public/next.svg +1 -0
  117. package/public/screenshots/code-generator.png +0 -0
  118. package/public/screenshots/connection-modal.png +0 -0
  119. package/public/screenshots/data-profiler.png +0 -0
  120. package/public/screenshots/erd-diagram.png +0 -0
  121. package/public/screenshots/hero-editor.png +0 -0
  122. package/public/screenshots/nl2sql.png +0 -0
  123. package/public/vercel.svg +1 -0
  124. package/public/window.svg +1 -0
  125. package/render.yaml +58 -0
  126. package/scripts/merge-lcov.mjs +239 -0
  127. package/sonar-project.properties +16 -0
  128. package/src/app/admin/error.tsx +46 -0
  129. package/src/app/admin/page.tsx +10 -0
  130. package/src/app/api/admin/audit/route.ts +52 -0
  131. package/src/app/api/admin/fleet-health/route.ts +81 -0
  132. package/src/app/api/ai/autopilot/route.ts +105 -0
  133. package/src/app/api/ai/chat/route.ts +132 -0
  134. package/src/app/api/ai/describe-schema/route.ts +52 -0
  135. package/src/app/api/ai/explain/route.ts +86 -0
  136. package/src/app/api/ai/impact/route.ts +97 -0
  137. package/src/app/api/ai/index-advisor/route.ts +98 -0
  138. package/src/app/api/ai/nl2sql/route.ts +87 -0
  139. package/src/app/api/ai/query-safety/route.ts +87 -0
  140. package/src/app/api/auth/login/route.ts +62 -0
  141. package/src/app/api/auth/logout/route.ts +25 -0
  142. package/src/app/api/auth/me/route.ts +10 -0
  143. package/src/app/api/auth/oidc/callback/route.ts +82 -0
  144. package/src/app/api/auth/oidc/login/route.ts +43 -0
  145. package/src/app/api/connections/managed/route.ts +35 -0
  146. package/src/app/api/db/cancel/route.ts +42 -0
  147. package/src/app/api/db/disconnect/route.ts +28 -0
  148. package/src/app/api/db/health/route.ts +49 -0
  149. package/src/app/api/db/maintenance/route.ts +72 -0
  150. package/src/app/api/db/monitoring/route.ts +62 -0
  151. package/src/app/api/db/multi-query/route.ts +116 -0
  152. package/src/app/api/db/pool-stats/route.ts +37 -0
  153. package/src/app/api/db/profile/route.ts +144 -0
  154. package/src/app/api/db/provider-meta/route.ts +49 -0
  155. package/src/app/api/db/query/route.ts +50 -0
  156. package/src/app/api/db/schema/route.ts +47 -0
  157. package/src/app/api/db/schema-snapshot/route.ts +42 -0
  158. package/src/app/api/db/test-connection/route.ts +55 -0
  159. package/src/app/api/db/transaction/route.ts +111 -0
  160. package/src/app/api/storage/[collection]/route.ts +67 -0
  161. package/src/app/api/storage/config/route.ts +17 -0
  162. package/src/app/api/storage/migrate/route.ts +45 -0
  163. package/src/app/api/storage/route.ts +32 -0
  164. package/src/app/error.tsx +49 -0
  165. package/src/app/global-error.tsx +55 -0
  166. package/src/app/globals.css +146 -0
  167. package/src/app/icon.svg +42 -0
  168. package/src/app/layout.tsx +34 -0
  169. package/src/app/login/login-form.tsx +301 -0
  170. package/src/app/login/page.tsx +11 -0
  171. package/src/app/monitoring/page.tsx +8 -0
  172. package/src/app/not-found.tsx +29 -0
  173. package/src/app/page.tsx +5 -0
  174. package/src/components/AIAutopilotPanel.tsx +238 -0
  175. package/src/components/CodeGenerator.tsx +271 -0
  176. package/src/components/CommandPalette.tsx +227 -0
  177. package/src/components/ConnectionModal.tsx +759 -0
  178. package/src/components/CreateTableModal.tsx +281 -0
  179. package/src/components/DataCharts.tsx +962 -0
  180. package/src/components/DataImportModal.tsx +582 -0
  181. package/src/components/DataProfiler.tsx +335 -0
  182. package/src/components/DatabaseDocs.tsx +251 -0
  183. package/src/components/MaskingSettings.tsx +414 -0
  184. package/src/components/MobileNav.tsx +50 -0
  185. package/src/components/NL2SQLPanel.tsx +281 -0
  186. package/src/components/PivotTable.tsx +257 -0
  187. package/src/components/QueryEditor.tsx +760 -0
  188. package/src/components/QueryHistory.tsx +344 -0
  189. package/src/components/QuerySafetyDialog.tsx +290 -0
  190. package/src/components/ResultsGrid.tsx +644 -0
  191. package/src/components/SaveQueryModal.tsx +104 -0
  192. package/src/components/SavedQueries.tsx +128 -0
  193. package/src/components/SchemaDiagram.tsx +473 -0
  194. package/src/components/SchemaDiff.tsx +473 -0
  195. package/src/components/SnapshotTimeline.tsx +116 -0
  196. package/src/components/Studio.tsx +639 -0
  197. package/src/components/TestDataGenerator.tsx +261 -0
  198. package/src/components/VisualExplain.tsx +820 -0
  199. package/src/components/admin/AdminDashboard.tsx +163 -0
  200. package/src/components/admin/tabs/AuditTab.tsx +531 -0
  201. package/src/components/admin/tabs/MonitoringEmbed.tsx +11 -0
  202. package/src/components/admin/tabs/OperationsTab.tsx +646 -0
  203. package/src/components/admin/tabs/OverviewTab.tsx +1328 -0
  204. package/src/components/admin/tabs/SecurityTab.tsx +284 -0
  205. package/src/components/community-section.tsx +92 -0
  206. package/src/components/icons/db-icons.tsx +84 -0
  207. package/src/components/libredb-logo.tsx +61 -0
  208. package/src/components/monitoring/MonitoringDashboard.tsx +345 -0
  209. package/src/components/monitoring/tabs/MetricChart.tsx +82 -0
  210. package/src/components/monitoring/tabs/OverviewTab.tsx +263 -0
  211. package/src/components/monitoring/tabs/PerformanceTab.tsx +254 -0
  212. package/src/components/monitoring/tabs/PoolTab.tsx +174 -0
  213. package/src/components/monitoring/tabs/QueriesTab.tsx +287 -0
  214. package/src/components/monitoring/tabs/SessionsTab.tsx +316 -0
  215. package/src/components/monitoring/tabs/StorageTab.tsx +335 -0
  216. package/src/components/monitoring/tabs/TablesTab.tsx +300 -0
  217. package/src/components/results-grid/ResultCard.tsx +111 -0
  218. package/src/components/results-grid/RowDetailSheet.tsx +178 -0
  219. package/src/components/results-grid/StatsBar.tsx +201 -0
  220. package/src/components/results-grid/index.ts +1 -0
  221. package/src/components/results-grid/utils.ts +23 -0
  222. package/src/components/schema-explorer/ColumnList.tsx +53 -0
  223. package/src/components/schema-explorer/SchemaExplorer.tsx +182 -0
  224. package/src/components/schema-explorer/TableItem.tsx +210 -0
  225. package/src/components/schema-explorer/index.ts +1 -0
  226. package/src/components/sidebar/ConnectionItem.tsx +105 -0
  227. package/src/components/sidebar/ConnectionsList.tsx +62 -0
  228. package/src/components/sidebar/Sidebar.tsx +130 -0
  229. package/src/components/sidebar/index.ts +2 -0
  230. package/src/components/studio/BottomPanel.tsx +286 -0
  231. package/src/components/studio/QueryToolbar.tsx +180 -0
  232. package/src/components/studio/StudioDesktopHeader.tsx +114 -0
  233. package/src/components/studio/StudioMobileHeader.tsx +340 -0
  234. package/src/components/studio/StudioTabBar.tsx +82 -0
  235. package/src/components/studio/index.ts +5 -0
  236. package/src/components/ui/accordion.tsx +66 -0
  237. package/src/components/ui/alert-dialog.tsx +157 -0
  238. package/src/components/ui/alert.tsx +66 -0
  239. package/src/components/ui/aspect-ratio.tsx +11 -0
  240. package/src/components/ui/avatar.tsx +53 -0
  241. package/src/components/ui/badge.tsx +46 -0
  242. package/src/components/ui/breadcrumb.tsx +109 -0
  243. package/src/components/ui/button-group.tsx +83 -0
  244. package/src/components/ui/button.tsx +60 -0
  245. package/src/components/ui/calendar.tsx +216 -0
  246. package/src/components/ui/card.tsx +92 -0
  247. package/src/components/ui/carousel.tsx +241 -0
  248. package/src/components/ui/chart.tsx +357 -0
  249. package/src/components/ui/checkbox.tsx +32 -0
  250. package/src/components/ui/collapsible.tsx +33 -0
  251. package/src/components/ui/command.tsx +184 -0
  252. package/src/components/ui/context-menu.tsx +252 -0
  253. package/src/components/ui/dialog.tsx +143 -0
  254. package/src/components/ui/drawer.tsx +135 -0
  255. package/src/components/ui/dropdown-menu.tsx +257 -0
  256. package/src/components/ui/empty.tsx +104 -0
  257. package/src/components/ui/field.tsx +248 -0
  258. package/src/components/ui/form.tsx +167 -0
  259. package/src/components/ui/hover-card.tsx +44 -0
  260. package/src/components/ui/input-group.tsx +170 -0
  261. package/src/components/ui/input-otp.tsx +77 -0
  262. package/src/components/ui/input.tsx +21 -0
  263. package/src/components/ui/item.tsx +193 -0
  264. package/src/components/ui/kbd.tsx +28 -0
  265. package/src/components/ui/label.tsx +24 -0
  266. package/src/components/ui/menubar.tsx +276 -0
  267. package/src/components/ui/navigation-menu.tsx +168 -0
  268. package/src/components/ui/pagination.tsx +127 -0
  269. package/src/components/ui/popover.tsx +48 -0
  270. package/src/components/ui/progress.tsx +31 -0
  271. package/src/components/ui/radio-group.tsx +45 -0
  272. package/src/components/ui/resizable.tsx +56 -0
  273. package/src/components/ui/scroll-area.tsx +58 -0
  274. package/src/components/ui/select.tsx +187 -0
  275. package/src/components/ui/separator.tsx +28 -0
  276. package/src/components/ui/sheet.tsx +139 -0
  277. package/src/components/ui/sidebar.tsx +726 -0
  278. package/src/components/ui/skeleton.tsx +13 -0
  279. package/src/components/ui/slider.tsx +63 -0
  280. package/src/components/ui/sonner.tsx +40 -0
  281. package/src/components/ui/spinner.tsx +16 -0
  282. package/src/components/ui/switch.tsx +31 -0
  283. package/src/components/ui/table.tsx +116 -0
  284. package/src/components/ui/tabs.tsx +66 -0
  285. package/src/components/ui/textarea.tsx +18 -0
  286. package/src/components/ui/toggle-group.tsx +83 -0
  287. package/src/components/ui/toggle.tsx +47 -0
  288. package/src/components/ui/tooltip.tsx +61 -0
  289. package/src/exports/components.ts +15 -0
  290. package/src/exports/index.ts +4 -0
  291. package/src/exports/providers.ts +4 -0
  292. package/src/exports/types.ts +26 -0
  293. package/src/hooks/use-ai-chat.ts +182 -0
  294. package/src/hooks/use-all-connections.ts +66 -0
  295. package/src/hooks/use-api-call.ts +71 -0
  296. package/src/hooks/use-auth.ts +51 -0
  297. package/src/hooks/use-connection-form.ts +349 -0
  298. package/src/hooks/use-connection-manager.ts +169 -0
  299. package/src/hooks/use-connection-payload.ts +15 -0
  300. package/src/hooks/use-inline-editing.ts +109 -0
  301. package/src/hooks/use-mobile.ts +20 -0
  302. package/src/hooks/use-monitoring-data.ts +270 -0
  303. package/src/hooks/use-provider-metadata.ts +62 -0
  304. package/src/hooks/use-query-execution.ts +478 -0
  305. package/src/hooks/use-storage-sync.ts +259 -0
  306. package/src/hooks/use-tab-manager.ts +231 -0
  307. package/src/hooks/use-toast.ts +20 -0
  308. package/src/hooks/use-transaction-control.ts +64 -0
  309. package/src/lib/api/error-codes.ts +30 -0
  310. package/src/lib/api/errors.ts +236 -0
  311. package/src/lib/api/with-error-handler.ts +41 -0
  312. package/src/lib/audit.ts +105 -0
  313. package/src/lib/auth.ts +87 -0
  314. package/src/lib/connection-string-parser.ts +172 -0
  315. package/src/lib/data-masking.ts +385 -0
  316. package/src/lib/db/base-provider.ts +325 -0
  317. package/src/lib/db/errors.ts +317 -0
  318. package/src/lib/db/factory.ts +324 -0
  319. package/src/lib/db/index.ts +123 -0
  320. package/src/lib/db/providers/document/index.ts +6 -0
  321. package/src/lib/db/providers/document/mongodb.ts +992 -0
  322. package/src/lib/db/providers/keyvalue/redis.ts +554 -0
  323. package/src/lib/db/providers/sql/index.ts +11 -0
  324. package/src/lib/db/providers/sql/mssql.ts +1065 -0
  325. package/src/lib/db/providers/sql/mysql.ts +978 -0
  326. package/src/lib/db/providers/sql/oracle.ts +1044 -0
  327. package/src/lib/db/providers/sql/postgres.ts +1179 -0
  328. package/src/lib/db/providers/sql/sql-base.ts +174 -0
  329. package/src/lib/db/providers/sql/sqlite.ts +721 -0
  330. package/src/lib/db/types.ts +437 -0
  331. package/src/lib/db/utils/pool-manager.ts +287 -0
  332. package/src/lib/db/utils/query-limiter.ts +239 -0
  333. package/src/lib/db-ui-config.ts +86 -0
  334. package/src/lib/editor/mongodb-completions.ts +172 -0
  335. package/src/lib/editor/sql-completions.ts +280 -0
  336. package/src/lib/llm/base-provider.ts +117 -0
  337. package/src/lib/llm/factory.ts +102 -0
  338. package/src/lib/llm/index.ts +90 -0
  339. package/src/lib/llm/providers/custom.ts +181 -0
  340. package/src/lib/llm/providers/gemini.ts +126 -0
  341. package/src/lib/llm/providers/ollama.ts +154 -0
  342. package/src/lib/llm/providers/openai.ts +146 -0
  343. package/src/lib/llm/types.ts +173 -0
  344. package/src/lib/llm/utils/config.ts +187 -0
  345. package/src/lib/llm/utils/retry.ts +119 -0
  346. package/src/lib/llm/utils/streaming.ts +202 -0
  347. package/src/lib/logger.ts +127 -0
  348. package/src/lib/monitoring-thresholds.ts +44 -0
  349. package/src/lib/oidc.ts +262 -0
  350. package/src/lib/query-generators.ts +61 -0
  351. package/src/lib/schema-diff/diff-engine.ts +273 -0
  352. package/src/lib/schema-diff/migration-generator.ts +208 -0
  353. package/src/lib/schema-diff/types.ts +55 -0
  354. package/src/lib/seed/config-loader.ts +79 -0
  355. package/src/lib/seed/connection-filter.ts +49 -0
  356. package/src/lib/seed/credential-resolver.ts +62 -0
  357. package/src/lib/seed/index.ts +40 -0
  358. package/src/lib/seed/resolve-connection.ts +57 -0
  359. package/src/lib/seed/types.ts +69 -0
  360. package/src/lib/sql/alias-extractor.ts +267 -0
  361. package/src/lib/sql/index.ts +8 -0
  362. package/src/lib/sql/statement-splitter.ts +167 -0
  363. package/src/lib/sql/types.ts +40 -0
  364. package/src/lib/ssh/tunnel.ts +142 -0
  365. package/src/lib/storage/factory.ts +84 -0
  366. package/src/lib/storage/index.ts +14 -0
  367. package/src/lib/storage/local-storage.ts +99 -0
  368. package/src/lib/storage/providers/postgres.ts +225 -0
  369. package/src/lib/storage/providers/sqlite.ts +153 -0
  370. package/src/lib/storage/storage-facade.ts +272 -0
  371. package/src/lib/storage/types.ts +75 -0
  372. package/src/lib/time-series-buffer.ts +58 -0
  373. package/src/lib/types.ts +173 -0
  374. package/src/lib/utils.ts +6 -0
  375. package/src/proxy.ts +104 -0
  376. package/src/types/db-drivers.d.ts +23 -0
  377. package/src/types/html2canvas.d.ts +9 -0
  378. package/tests/api/admin/audit.test.ts +178 -0
  379. package/tests/api/admin/fleet-health.test.ts +183 -0
  380. package/tests/api/ai/autopilot.test.ts +174 -0
  381. package/tests/api/ai/chat.test.ts +250 -0
  382. package/tests/api/ai/describe-schema.test.ts +266 -0
  383. package/tests/api/ai/explain.test.ts +199 -0
  384. package/tests/api/ai/impact.test.ts +168 -0
  385. package/tests/api/ai/index-advisor.test.ts +171 -0
  386. package/tests/api/ai/nl2sql.test.ts +202 -0
  387. package/tests/api/ai/query-safety.test.ts +196 -0
  388. package/tests/api/auth/login.test.ts +170 -0
  389. package/tests/api/auth/logout.test.ts +140 -0
  390. package/tests/api/auth/me.test.ts +73 -0
  391. package/tests/api/auth/oidc-callback.test.ts +215 -0
  392. package/tests/api/auth/oidc-login.test.ts +127 -0
  393. package/tests/api/db/cancel.test.ts +198 -0
  394. package/tests/api/db/disconnect.test.ts +124 -0
  395. package/tests/api/db/health.test.ts +222 -0
  396. package/tests/api/db/maintenance.test.ts +263 -0
  397. package/tests/api/db/monitoring.test.ts +221 -0
  398. package/tests/api/db/multi-query.test.ts +316 -0
  399. package/tests/api/db/pool-stats.test.ts +135 -0
  400. package/tests/api/db/profile.test.ts +330 -0
  401. package/tests/api/db/provider-meta.test.ts +193 -0
  402. package/tests/api/db/query.test.ts +314 -0
  403. package/tests/api/db/schema-snapshot.test.ts +170 -0
  404. package/tests/api/db/schema.test.ts +191 -0
  405. package/tests/api/db/test-connection.test.ts +185 -0
  406. package/tests/api/db/transaction.test.ts +314 -0
  407. package/tests/api/proxy.test.ts +191 -0
  408. package/tests/api/seed/managed-route.test.ts +113 -0
  409. package/tests/api/storage/config.test.ts +42 -0
  410. package/tests/api/storage/storage-routes.test.ts +309 -0
  411. package/tests/components/AIAutopilotPanel.test.tsx +756 -0
  412. package/tests/components/AdminPage.test.tsx +33 -0
  413. package/tests/components/CodeGenerator.test.tsx +182 -0
  414. package/tests/components/CommandPalette.test.tsx +428 -0
  415. package/tests/components/CommunitySection.test.tsx +91 -0
  416. package/tests/components/ConnectionModal.mobile.test.tsx +284 -0
  417. package/tests/components/ConnectionModal.test.tsx +570 -0
  418. package/tests/components/CreateTableModal.test.tsx +383 -0
  419. package/tests/components/DataCharts.test.tsx +739 -0
  420. package/tests/components/DataImportModal.test.tsx +751 -0
  421. package/tests/components/DataProfiler.test.tsx +589 -0
  422. package/tests/components/DatabaseDocs.test.tsx +353 -0
  423. package/tests/components/LoginPage.test.tsx +163 -0
  424. package/tests/components/LoginPageOIDC.test.tsx +92 -0
  425. package/tests/components/MaskingSettings.test.tsx +498 -0
  426. package/tests/components/MobileNav.test.tsx +30 -0
  427. package/tests/components/MonitoringPage.test.tsx +32 -0
  428. package/tests/components/NL2SQLPanel.test.tsx +621 -0
  429. package/tests/components/Page.test.tsx +33 -0
  430. package/tests/components/PivotTable.test.tsx +350 -0
  431. package/tests/components/QueryEditor.test.tsx +1730 -0
  432. package/tests/components/QueryHistory.test.tsx +572 -0
  433. package/tests/components/QuerySafetyDialog.test.tsx +586 -0
  434. package/tests/components/ResultsGrid.test.tsx +804 -0
  435. package/tests/components/RootLayout.test.tsx +83 -0
  436. package/tests/components/SaveQueryModal.test.tsx +25 -0
  437. package/tests/components/SavedQueries.test.tsx +43 -0
  438. package/tests/components/SchemaDiagram.test.tsx +1034 -0
  439. package/tests/components/SchemaDiff.test.tsx +906 -0
  440. package/tests/components/SnapshotTimeline.test.tsx +174 -0
  441. package/tests/components/Studio.test.tsx +1030 -0
  442. package/tests/components/TestDataGenerator.test.tsx +291 -0
  443. package/tests/components/VisualExplain.test.tsx +704 -0
  444. package/tests/components/admin/AdminDashboard.test.tsx +205 -0
  445. package/tests/components/admin/AuditTab.test.tsx +220 -0
  446. package/tests/components/admin/MonitoringEmbed.test.tsx +58 -0
  447. package/tests/components/admin/OperationsTab.test.tsx +975 -0
  448. package/tests/components/admin/OverviewTab.test.tsx +254 -0
  449. package/tests/components/admin/SecurityTab.test.tsx +467 -0
  450. package/tests/components/monitoring/MetricChart.test.tsx +111 -0
  451. package/tests/components/monitoring/MonitoringDashboard.test.tsx +259 -0
  452. package/tests/components/monitoring/OverviewTab.test.tsx +78 -0
  453. package/tests/components/monitoring/PerformanceTab.test.tsx +87 -0
  454. package/tests/components/monitoring/PoolTab.test.tsx +42 -0
  455. package/tests/components/monitoring/QueriesTab.test.tsx +80 -0
  456. package/tests/components/monitoring/SessionsTab.test.tsx +154 -0
  457. package/tests/components/monitoring/StorageTab.test.tsx +127 -0
  458. package/tests/components/monitoring/TablesTab.test.tsx +153 -0
  459. package/tests/components/results-grid/ResultCard.test.tsx +105 -0
  460. package/tests/components/results-grid/RowDetailSheet.test.tsx +308 -0
  461. package/tests/components/results-grid/StatsBar.test.tsx +162 -0
  462. package/tests/components/schema-explorer/ColumnList.test.tsx +151 -0
  463. package/tests/components/schema-explorer/SchemaExplorer.test.tsx +461 -0
  464. package/tests/components/schema-explorer/TableItem.test.tsx +415 -0
  465. package/tests/components/sidebar/ConnectionItem.test.tsx +201 -0
  466. package/tests/components/sidebar/ConnectionsList.test.tsx +176 -0
  467. package/tests/components/sidebar/Sidebar.test.tsx +187 -0
  468. package/tests/components/studio/BottomPanel.test.tsx +383 -0
  469. package/tests/components/studio/QueryToolbar.test.tsx +321 -0
  470. package/tests/components/studio/StudioDesktopHeader.test.tsx +377 -0
  471. package/tests/components/studio/StudioMobileHeader.test.tsx +198 -0
  472. package/tests/components/studio/StudioTabBar.test.tsx +331 -0
  473. package/tests/fixtures/connections.ts +96 -0
  474. package/tests/fixtures/masking-configs.ts +86 -0
  475. package/tests/fixtures/query-results.ts +71 -0
  476. package/tests/fixtures/schemas.ts +64 -0
  477. package/tests/fixtures/seed-connections/invalid-config.yaml +7 -0
  478. package/tests/fixtures/seed-connections/minimal-config.yaml +8 -0
  479. package/tests/fixtures/seed-connections/mixed-credentials.yaml +23 -0
  480. package/tests/fixtures/seed-connections/multi-role-config.yaml +30 -0
  481. package/tests/fixtures/seed-connections/valid-config.json +15 -0
  482. package/tests/fixtures/seed-connections/valid-config.yaml +51 -0
  483. package/tests/helpers/mock-fetch.ts +59 -0
  484. package/tests/helpers/mock-monaco.ts +112 -0
  485. package/tests/helpers/mock-navigation.ts +28 -0
  486. package/tests/helpers/mock-next.ts +80 -0
  487. package/tests/helpers/mock-provider.ts +133 -0
  488. package/tests/helpers/mock-sonner.ts +29 -0
  489. package/tests/helpers/render-with-providers.tsx +19 -0
  490. package/tests/hooks/use-ai-chat.test.ts +600 -0
  491. package/tests/hooks/use-auth.test.ts +371 -0
  492. package/tests/hooks/use-connection-form.test.ts +743 -0
  493. package/tests/hooks/use-connection-manager.test.ts +466 -0
  494. package/tests/hooks/use-inline-editing.test.ts +321 -0
  495. package/tests/hooks/use-mobile.test.ts +177 -0
  496. package/tests/hooks/use-monitoring-data.test.ts +819 -0
  497. package/tests/hooks/use-provider-metadata.test.ts +228 -0
  498. package/tests/hooks/use-query-execution.test.ts +1212 -0
  499. package/tests/hooks/use-tab-manager.test.ts +756 -0
  500. package/tests/hooks/use-toast.test.ts +74 -0
  501. package/tests/hooks/use-transaction-control.test.ts +211 -0
  502. package/tests/integration/db/mongodb-provider.test.ts +698 -0
  503. package/tests/integration/db/mssql-provider.test.ts +840 -0
  504. package/tests/integration/db/mysql-provider.test.ts +872 -0
  505. package/tests/integration/db/oracle-provider.test.ts +843 -0
  506. package/tests/integration/db/postgres-provider.test.ts +1382 -0
  507. package/tests/integration/db/redis-provider.test.ts +526 -0
  508. package/tests/integration/db/sqlite-provider.test.ts +480 -0
  509. package/tests/integration/seed/seed-pipeline.test.ts +102 -0
  510. package/tests/isolated/factory-singleton.test.ts +150 -0
  511. package/tests/isolated/use-storage-sync.test.ts +389 -0
  512. package/tests/run-components.sh +196 -0
  513. package/tests/setup-dom.ts +58 -0
  514. package/tests/setup.ts +40 -0
  515. package/tests/unit/api-errors.test.ts +210 -0
  516. package/tests/unit/code-generator-functions.test.ts +271 -0
  517. package/tests/unit/components/column-list.test.tsx +190 -0
  518. package/tests/unit/components/data-import-modal.test.tsx +441 -0
  519. package/tests/unit/components/studio-mobile-header.test.tsx +327 -0
  520. package/tests/unit/data-charts-functions.test.ts +496 -0
  521. package/tests/unit/data-import-functions.test.ts +320 -0
  522. package/tests/unit/data-import-utils.test.ts +125 -0
  523. package/tests/unit/db/base-provider.test.ts +517 -0
  524. package/tests/unit/db/errors.test.ts +403 -0
  525. package/tests/unit/db/factory.test.ts +436 -0
  526. package/tests/unit/db/pool-manager.test.ts +440 -0
  527. package/tests/unit/db/query-limiter.test.ts +387 -0
  528. package/tests/unit/db/sql-base.test.ts +438 -0
  529. package/tests/unit/lib/api/error-codes.test.ts +39 -0
  530. package/tests/unit/lib/audit.test.ts +326 -0
  531. package/tests/unit/lib/auth.test.ts +146 -0
  532. package/tests/unit/lib/connection-string-parser.test.ts +424 -0
  533. package/tests/unit/lib/data-masking.test.ts +583 -0
  534. package/tests/unit/lib/db-icons.test.tsx +41 -0
  535. package/tests/unit/lib/monitoring-thresholds.test.ts +133 -0
  536. package/tests/unit/lib/oidc.test.ts +509 -0
  537. package/tests/unit/lib/query-generators.test.ts +127 -0
  538. package/tests/unit/lib/storage/factory.test.ts +71 -0
  539. package/tests/unit/lib/storage/local-storage.test.ts +114 -0
  540. package/tests/unit/lib/storage/providers/postgres.test.ts +312 -0
  541. package/tests/unit/lib/storage/providers/sqlite.test.ts +232 -0
  542. package/tests/unit/lib/storage/storage-facade-extended.test.ts +331 -0
  543. package/tests/unit/lib/storage/storage-facade.test.ts +184 -0
  544. package/tests/unit/lib/storage.test.ts +317 -0
  545. package/tests/unit/lib/time-series-buffer.test.ts +212 -0
  546. package/tests/unit/lib/utils.test.ts +24 -0
  547. package/tests/unit/llm/base-provider.test.ts +238 -0
  548. package/tests/unit/llm/config.test.ts +262 -0
  549. package/tests/unit/llm/custom-provider.test.ts +281 -0
  550. package/tests/unit/llm/gemini-provider.test.ts +248 -0
  551. package/tests/unit/llm/llm-factory.test.ts +155 -0
  552. package/tests/unit/llm/ollama-provider.test.ts +288 -0
  553. package/tests/unit/llm/openai-provider.test.ts +324 -0
  554. package/tests/unit/llm/retry.test.ts +180 -0
  555. package/tests/unit/llm/streaming.test.ts +355 -0
  556. package/tests/unit/logger.test.ts +198 -0
  557. package/tests/unit/mongodb-completions.test.ts +516 -0
  558. package/tests/unit/pivot-table-functions.test.ts +76 -0
  559. package/tests/unit/query-cancelled-error.test.ts +81 -0
  560. package/tests/unit/schema-diff/diff-engine.test.ts +367 -0
  561. package/tests/unit/schema-diff/migration-generator.test.ts +513 -0
  562. package/tests/unit/seed/config-loader.test.ts +73 -0
  563. package/tests/unit/seed/connection-filter.test.ts +91 -0
  564. package/tests/unit/seed/credential-resolver.test.ts +85 -0
  565. package/tests/unit/seed/index.test.ts +72 -0
  566. package/tests/unit/seed/resolve-connection.test.ts +74 -0
  567. package/tests/unit/seed/types.test.ts +129 -0
  568. package/tests/unit/sql/alias-extractor.test.ts +444 -0
  569. package/tests/unit/sql/statement-splitter.test.ts +348 -0
  570. package/tests/unit/sql-completions.test.ts +463 -0
  571. package/tests/unit/ssh-tunnel.test.ts +465 -0
  572. package/tsconfig.json +42 -0
@@ -0,0 +1,444 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { extractAliases, resolveAlias, getAliasSchema } from '@/lib/sql/alias-extractor';
3
+
4
+ // ============================================================================
5
+ // extractAliases — FROM clause
6
+ // ============================================================================
7
+
8
+ describe('extractAliases: FROM clause', () => {
9
+ test('FROM table alias (implicit)', () => {
10
+ const { aliases } = extractAliases('SELECT * FROM users u');
11
+ const alias = aliases.get('u');
12
+ expect(alias).toBeDefined();
13
+ expect(alias!.alias).toBe('u');
14
+ expect(alias!.tableName).toBe('users');
15
+ expect(alias!.source).toBe('from');
16
+ });
17
+
18
+ test('FROM table AS alias (explicit)', () => {
19
+ const { aliases } = extractAliases('SELECT * FROM users AS u');
20
+ const alias = aliases.get('u');
21
+ expect(alias).toBeDefined();
22
+ expect(alias!.tableName).toBe('users');
23
+ expect(alias!.source).toBe('from');
24
+ });
25
+
26
+ test('schema.table alias extracts schema', () => {
27
+ const { aliases } = extractAliases('SELECT * FROM public.users u');
28
+ const alias = aliases.get('u');
29
+ expect(alias).toBeDefined();
30
+ expect(alias!.tableName).toBe('users');
31
+ expect(alias!.schema).toBe('public');
32
+ expect(alias!.source).toBe('from');
33
+ });
34
+
35
+ test('SQL keyword after table name is not treated as alias', () => {
36
+ const { aliases } = extractAliases('SELECT * FROM users WHERE id = 1');
37
+ expect(aliases.has('where')).toBe(false);
38
+ expect(aliases.size).toBe(0);
39
+ });
40
+
41
+ test('same-name alias is skipped (FROM users users)', () => {
42
+ const { aliases } = extractAliases('SELECT * FROM users users');
43
+ expect(aliases.size).toBe(0);
44
+ });
45
+ });
46
+
47
+ // ============================================================================
48
+ // extractAliases — JOIN clause
49
+ // ============================================================================
50
+
51
+ describe('extractAliases: JOIN clause', () => {
52
+ test('JOIN table alias', () => {
53
+ const { aliases } = extractAliases('SELECT * FROM users u JOIN orders o ON u.id = o.user_id');
54
+ const alias = aliases.get('o');
55
+ expect(alias).toBeDefined();
56
+ expect(alias!.tableName).toBe('orders');
57
+ expect(alias!.source).toBe('join');
58
+ });
59
+
60
+ test('LEFT JOIN alias', () => {
61
+ const { aliases } = extractAliases('SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id');
62
+ expect(aliases.get('o')?.source).toBe('join');
63
+ expect(aliases.get('o')?.tableName).toBe('orders');
64
+ });
65
+
66
+ test('RIGHT JOIN alias', () => {
67
+ const { aliases } = extractAliases('SELECT * FROM users u RIGHT JOIN orders o ON u.id = o.user_id');
68
+ expect(aliases.get('o')?.tableName).toBe('orders');
69
+ });
70
+
71
+ test('INNER JOIN alias', () => {
72
+ const { aliases } = extractAliases('SELECT * FROM users u INNER JOIN orders o ON u.id = o.user_id');
73
+ expect(aliases.get('o')?.tableName).toBe('orders');
74
+ });
75
+
76
+ test('CROSS JOIN alias', () => {
77
+ const { aliases } = extractAliases('SELECT * FROM users u CROSS JOIN products p');
78
+ expect(aliases.get('p')?.tableName).toBe('products');
79
+ expect(aliases.get('p')?.source).toBe('join');
80
+ });
81
+
82
+ test('JOIN with schema.table', () => {
83
+ const { aliases } = extractAliases('SELECT * FROM users u JOIN sales.orders o ON u.id = o.user_id');
84
+ const alias = aliases.get('o');
85
+ expect(alias?.schema).toBe('sales');
86
+ expect(alias?.tableName).toBe('orders');
87
+ });
88
+
89
+ test('FULL JOIN alias', () => {
90
+ const { aliases } = extractAliases('SELECT * FROM users u FULL JOIN orders o ON u.id = o.user_id');
91
+ expect(aliases.get('o')?.tableName).toBe('orders');
92
+ expect(aliases.get('o')?.source).toBe('join');
93
+ });
94
+
95
+ test('NATURAL JOIN alias', () => {
96
+ const { aliases } = extractAliases('SELECT * FROM users u NATURAL JOIN orders o');
97
+ expect(aliases.get('o')?.tableName).toBe('orders');
98
+ expect(aliases.get('o')?.source).toBe('join');
99
+ });
100
+
101
+ test('FULL OUTER JOIN alias', () => {
102
+ const { aliases } = extractAliases('SELECT * FROM users u FULL OUTER JOIN orders o ON u.id = o.user_id');
103
+ // FULL OUTER — regex matches FULL prefix, then JOIN
104
+ expect(aliases.get('o')?.tableName).toBe('orders');
105
+ });
106
+
107
+ test('JOIN with explicit AS keyword', () => {
108
+ const { aliases } = extractAliases('SELECT * FROM users u LEFT JOIN orders AS o ON u.id = o.user_id');
109
+ expect(aliases.get('o')?.tableName).toBe('orders');
110
+ expect(aliases.get('o')?.source).toBe('join');
111
+ });
112
+
113
+ test('JOIN same-name alias is skipped (JOIN orders orders)', () => {
114
+ const { aliases } = extractAliases('SELECT * FROM users u JOIN orders orders ON u.id = orders.user_id');
115
+ expect(aliases.has('u')).toBe(true);
116
+ // 'orders' alias = 'orders' table — should be skipped
117
+ expect(aliases.has('orders')).toBe(false);
118
+ });
119
+
120
+ test('multiple consecutive JOINs', () => {
121
+ const sql = 'SELECT * FROM users u JOIN orders o ON u.id = o.user_id JOIN products p ON o.product_id = p.id JOIN categories c ON p.cat_id = c.id';
122
+ const { aliases } = extractAliases(sql);
123
+ expect(aliases.size).toBe(4);
124
+ expect(aliases.get('u')?.tableName).toBe('users');
125
+ expect(aliases.get('o')?.tableName).toBe('orders');
126
+ expect(aliases.get('p')?.tableName).toBe('products');
127
+ expect(aliases.get('c')?.tableName).toBe('categories');
128
+ });
129
+ });
130
+
131
+ // ============================================================================
132
+ // extractAliases — CTE clause
133
+ // ============================================================================
134
+
135
+ describe('extractAliases: CTE clause', () => {
136
+ test('WITH cte AS (...) extracts CTE alias', () => {
137
+ const { aliases } = extractAliases('WITH active_users AS (SELECT * FROM users WHERE active = true) SELECT * FROM active_users au');
138
+ const cte = aliases.get('active_users');
139
+ expect(cte).toBeDefined();
140
+ expect(cte!.source).toBe('cte');
141
+ expect(cte!.tableName).toBe('active_users');
142
+ });
143
+
144
+ test('includeCTEs=false excludes CTE aliases', () => {
145
+ const { aliases } = extractAliases(
146
+ 'WITH cte AS (SELECT 1) SELECT * FROM cte c',
147
+ { includeCTEs: false }
148
+ );
149
+ expect(aliases.has('cte')).toBe(false);
150
+ // FROM alias 'c' should still be found
151
+ expect(aliases.has('c')).toBe(true);
152
+ });
153
+
154
+ test('WITH RECURSIVE CTE extracts alias', () => {
155
+ const sql = 'WITH RECURSIVE hierarchy AS (SELECT id, parent_id FROM categories UNION ALL SELECT c.id, c.parent_id FROM categories c JOIN hierarchy h ON c.parent_id = h.id) SELECT * FROM hierarchy h2';
156
+ const { aliases } = extractAliases(sql);
157
+ expect(aliases.has('hierarchy')).toBe(true);
158
+ expect(aliases.get('hierarchy')?.source).toBe('cte');
159
+ });
160
+
161
+ test('multiple CTEs extracted', () => {
162
+ const sql = 'WITH active AS (SELECT * FROM users WHERE active), orders_cte AS (SELECT * FROM orders WHERE total > 0) SELECT * FROM active a JOIN orders_cte oc ON a.id = oc.user_id';
163
+ const { aliases } = extractAliases(sql);
164
+ expect(aliases.has('active')).toBe(true);
165
+ expect(aliases.get('active')?.source).toBe('cte');
166
+ expect(aliases.has('orders_cte')).toBe(true);
167
+ expect(aliases.get('orders_cte')?.source).toBe('cte');
168
+ });
169
+
170
+ test('CTE name that looks like keyword is skipped', () => {
171
+ // 'select' is a keyword and should be skipped as CTE name
172
+ // but real CTE names should be captured
173
+ const sql = 'WITH my_data AS (SELECT 1) SELECT * FROM my_data md';
174
+ const { aliases } = extractAliases(sql);
175
+ expect(aliases.has('my_data')).toBe(true);
176
+ expect(aliases.get('my_data')?.source).toBe('cte');
177
+ });
178
+
179
+ test('CTE does not overwrite existing alias', () => {
180
+ // If same name appears twice as CTE, first wins
181
+ const sql = 'WITH cte1 AS (SELECT 1), cte1 AS (SELECT 2) SELECT * FROM cte1';
182
+ const { aliases } = extractAliases(sql);
183
+ // First CTE definition wins
184
+ expect(aliases.has('cte1')).toBe(true);
185
+ });
186
+ });
187
+
188
+ // ============================================================================
189
+ // extractAliases — Multiple aliases & edge cases
190
+ // ============================================================================
191
+
192
+ describe('extractAliases: multiple and edge cases', () => {
193
+ test('multiple aliases in one query', () => {
194
+ const { aliases } = extractAliases(
195
+ 'SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id'
196
+ );
197
+ expect(aliases.size).toBe(2);
198
+ expect(aliases.get('u')?.tableName).toBe('users');
199
+ expect(aliases.get('o')?.tableName).toBe('orders');
200
+ });
201
+
202
+ test('no table references returns hasTableReferences=false', () => {
203
+ const result = extractAliases('SELECT 1 + 2');
204
+ expect(result.hasTableReferences).toBe(false);
205
+ expect(result.aliases.size).toBe(0);
206
+ });
207
+
208
+ test('query with table refs returns hasTableReferences based on aliases found', () => {
209
+ const result = extractAliases('SELECT * FROM users u');
210
+ expect(result.hasTableReferences).toBe(true);
211
+ });
212
+
213
+ test('comments are removed before extraction', () => {
214
+ const sql = `
215
+ SELECT * FROM users u
216
+ -- JOIN orders o ON u.id = o.user_id
217
+ `;
218
+ const { aliases } = extractAliases(sql);
219
+ expect(aliases.has('u')).toBe(true);
220
+ // The commented-out join should not be extracted
221
+ expect(aliases.has('o')).toBe(false);
222
+ });
223
+
224
+ test('block comments are removed before extraction', () => {
225
+ const sql = `
226
+ SELECT * FROM users u
227
+ /* JOIN orders o ON u.id = o.user_id */
228
+ `;
229
+ const { aliases } = extractAliases(sql);
230
+ expect(aliases.has('u')).toBe(true);
231
+ expect(aliases.has('o')).toBe(false);
232
+ });
233
+
234
+ test('string literals do not produce false aliases', () => {
235
+ const sql = "SELECT * FROM users u WHERE name = 'FROM orders o'";
236
+ const { aliases } = extractAliases(sql);
237
+ expect(aliases.has('u')).toBe(true);
238
+ // 'o' from inside string should NOT be extracted
239
+ expect(aliases.has('o')).toBe(false);
240
+ });
241
+
242
+ test('case insensitive by default — uppercase alias found via lowercase key', () => {
243
+ const { aliases } = extractAliases('SELECT * FROM Users U');
244
+ expect(aliases.has('u')).toBe(true);
245
+ });
246
+
247
+ test('caseInsensitive=false preserves case', () => {
248
+ const { aliases } = extractAliases('SELECT * FROM Users U', { caseInsensitive: false });
249
+ expect(aliases.has('U')).toBe(true);
250
+ expect(aliases.has('u')).toBe(false);
251
+ });
252
+
253
+ test('empty string returns empty aliases and hasTableReferences=false', () => {
254
+ const result = extractAliases('');
255
+ expect(result.aliases.size).toBe(0);
256
+ expect(result.hasTableReferences).toBe(false);
257
+ });
258
+
259
+ test('whitespace only returns empty aliases', () => {
260
+ const result = extractAliases(' \n\t ');
261
+ expect(result.aliases.size).toBe(0);
262
+ expect(result.hasTableReferences).toBe(false);
263
+ });
264
+
265
+ test('FROM without alias still detects hasTableReferences via aliases', () => {
266
+ // FROM users WHERE ... — no alias extracted, but query has FROM keyword
267
+ // hasTableReferences is based on aliases.size > 0
268
+ const result = extractAliases('SELECT * FROM users WHERE id = 1');
269
+ expect(result.hasTableReferences).toBe(false);
270
+ expect(result.aliases.size).toBe(0);
271
+ });
272
+
273
+ test('multiple FROM tables with comma', () => {
274
+ // Only the first table after FROM gets an alias with the current pattern
275
+ const { aliases } = extractAliases('SELECT * FROM users u, orders o');
276
+ expect(aliases.get('u')?.tableName).toBe('users');
277
+ // second comma-separated table won't be caught by FROM pattern (no JOIN/FROM prefix)
278
+ });
279
+
280
+ test('subquery in FROM does not produce false aliases', () => {
281
+ const sql = 'SELECT * FROM (SELECT id FROM users) sub';
282
+ const { aliases } = extractAliases(sql);
283
+ // The inner FROM users has no alias, outer FROM (...) sub might or might not match
284
+ // Main point: no crash
285
+ expect(aliases).toBeDefined();
286
+ });
287
+
288
+ test('double-quoted identifiers are replaced in preprocessing', () => {
289
+ const sql = 'SELECT * FROM users u WHERE name = "FROM orders o"';
290
+ const { aliases } = extractAliases(sql);
291
+ expect(aliases.has('u')).toBe(true);
292
+ // double-quoted string should be replaced, so no 'o' alias
293
+ expect(aliases.has('o')).toBe(false);
294
+ });
295
+
296
+ test('mixed single-line and block comments', () => {
297
+ const sql = `
298
+ SELECT * FROM users u
299
+ -- FROM orders o1
300
+ /* FROM products p1 */
301
+ JOIN items i ON u.id = i.user_id
302
+ `;
303
+ const { aliases } = extractAliases(sql);
304
+ expect(aliases.has('u')).toBe(true);
305
+ expect(aliases.has('i')).toBe(true);
306
+ expect(aliases.has('o1')).toBe(false);
307
+ expect(aliases.has('p1')).toBe(false);
308
+ });
309
+
310
+ test('first alias wins — duplicate alias key not overwritten', () => {
311
+ // Two FROM clauses with same alias key
312
+ const sql = 'SELECT * FROM users u UNION SELECT * FROM orders u';
313
+ const { aliases } = extractAliases(sql);
314
+ // First alias for 'u' should be 'users'
315
+ expect(aliases.get('u')?.tableName).toBe('users');
316
+ });
317
+ });
318
+
319
+ // ============================================================================
320
+ // resolveAlias
321
+ // ============================================================================
322
+
323
+ describe('resolveAlias', () => {
324
+ test('resolves known alias to table name', () => {
325
+ const { aliases } = extractAliases('SELECT * FROM users u');
326
+ expect(resolveAlias('u', aliases)).toBe('users');
327
+ });
328
+
329
+ test('returns input unchanged when alias not found', () => {
330
+ const { aliases } = extractAliases('SELECT * FROM users u');
331
+ expect(resolveAlias('unknown', aliases)).toBe('unknown');
332
+ });
333
+
334
+ test('resolves alias case-insensitively', () => {
335
+ const { aliases } = extractAliases('SELECT * FROM users U');
336
+ expect(resolveAlias('u', aliases)).toBe('users');
337
+ });
338
+ });
339
+
340
+ // ============================================================================
341
+ // getAliasSchema
342
+ // ============================================================================
343
+
344
+ describe('getAliasSchema', () => {
345
+ test('returns schema when alias has schema defined', () => {
346
+ const { aliases } = extractAliases('SELECT * FROM public.users u');
347
+ expect(getAliasSchema('u', aliases)).toBe('public');
348
+ });
349
+
350
+ test('returns undefined when alias has no schema', () => {
351
+ const { aliases } = extractAliases('SELECT * FROM users u');
352
+ expect(getAliasSchema('u', aliases)).toBeUndefined();
353
+ });
354
+
355
+ test('returns undefined when alias not found', () => {
356
+ const { aliases } = extractAliases('SELECT * FROM users u');
357
+ expect(getAliasSchema('unknown', aliases)).toBeUndefined();
358
+ });
359
+
360
+ test('returns schema for JOIN alias with schema prefix', () => {
361
+ const { aliases } = extractAliases('SELECT * FROM users u JOIN sales.orders o ON u.id = o.user_id');
362
+ expect(getAliasSchema('o', aliases)).toBe('sales');
363
+ });
364
+ });
365
+
366
+ // ============================================================================
367
+ // caseInsensitive=false — preserves original case keys
368
+ // ============================================================================
369
+
370
+ describe('extractAliases: caseInsensitive=false', () => {
371
+ test('FROM alias preserves case', () => {
372
+ const { aliases } = extractAliases('SELECT * FROM Users U', { caseInsensitive: false });
373
+ expect(aliases.has('U')).toBe(true);
374
+ expect(aliases.has('u')).toBe(false);
375
+ });
376
+
377
+ test('JOIN alias preserves case', () => {
378
+ const { aliases } = extractAliases('SELECT * FROM Users U JOIN Orders O ON U.id = O.user_id', { caseInsensitive: false });
379
+ expect(aliases.has('O')).toBe(true);
380
+ expect(aliases.has('o')).toBe(false);
381
+ });
382
+
383
+ test('CTE alias preserves case', () => {
384
+ const { aliases } = extractAliases('WITH MyData AS (SELECT 1) SELECT * FROM MyData md', { caseInsensitive: false });
385
+ expect(aliases.has('MyData')).toBe(true);
386
+ expect(aliases.has('mydata')).toBe(false);
387
+ });
388
+ });
389
+
390
+ // ============================================================================
391
+ // Additional edge cases for coverage
392
+ // ============================================================================
393
+
394
+ describe('extractAliases: additional edge cases', () => {
395
+ test('schema-qualified table with explicit AS keyword', () => {
396
+ const { aliases } = extractAliases('SELECT * FROM public.users AS u');
397
+ const alias = aliases.get('u');
398
+ expect(alias).toBeDefined();
399
+ expect(alias!.tableName).toBe('users');
400
+ expect(alias!.schema).toBe('public');
401
+ expect(alias!.source).toBe('from');
402
+ });
403
+
404
+ test('SQL keyword as JOIN alias gets filtered out', () => {
405
+ const { aliases } = extractAliases('SELECT * FROM users u JOIN orders on ON u.id = on.user_id');
406
+ expect(aliases.has('on')).toBe(false);
407
+ expect(aliases.has('u')).toBe(true);
408
+ });
409
+
410
+ test('CTE name that IS a SQL keyword is skipped', () => {
411
+ const sql = 'WITH select AS (SELECT 1) SELECT * FROM select s';
412
+ const { aliases } = extractAliases(sql);
413
+ // 'select' is a keyword — should not be added as CTE
414
+ expect(aliases.get('select')?.source).not.toBe('cte');
415
+ });
416
+
417
+ test('escaped quotes inside string literals are handled by preprocessing', () => {
418
+ const sql = "SELECT * FROM users u WHERE name = 'it\\'s FROM orders o'";
419
+ const { aliases } = extractAliases(sql);
420
+ expect(aliases.has('u')).toBe(true);
421
+ // The escaped string should be removed, no false 'o' alias
422
+ expect(aliases.has('o')).toBe(false);
423
+ });
424
+
425
+ test('query with only WITH keyword (no FROM/JOIN)', () => {
426
+ const sql = 'WITH cte AS (SELECT 1) SELECT * FROM cte';
427
+ const { aliases } = extractAliases(sql);
428
+ expect(aliases.has('cte')).toBe(true);
429
+ expect(aliases.get('cte')?.source).toBe('cte');
430
+ });
431
+
432
+ test('resolveAlias with case-insensitive lookup (uppercase input)', () => {
433
+ const { aliases } = extractAliases('SELECT * FROM users u');
434
+ // resolveAlias uses .toLowerCase() internally
435
+ expect(resolveAlias('U', aliases)).toBe('users');
436
+ });
437
+
438
+ test('duplicate alias key in JOIN — first wins', () => {
439
+ const sql = 'SELECT * FROM users u JOIN orders o1 ON u.id = o1.uid JOIN products o1 ON o1.pid = o1.id';
440
+ const { aliases } = extractAliases(sql);
441
+ // First 'o1' is orders
442
+ expect(aliases.get('o1')?.tableName).toBe('orders');
443
+ });
444
+ });