diff --git a/apps/desktop/index.html b/apps/desktop/index.html index f49d2f3ee..a68bf9c53 100644 --- a/apps/desktop/index.html +++ b/apps/desktop/index.html @@ -18,7 +18,9 @@ color: #888; font-size: 13px; } - #root:empty::after { content: "Loading…"; } + #root:empty::after { + content: "Loading…"; + } diff --git a/apps/desktop/src/styles/globals.css b/apps/desktop/src/styles/globals.css index a72d389f9..f2ba0aebb 100644 --- a/apps/desktop/src/styles/globals.css +++ b/apps/desktop/src/styles/globals.css @@ -1,5 +1,4 @@ - -@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap"); @import "tailwindcss"; @source "../**/*.{vue,ts,tsx,js,jsx,html}"; @@ -11,112 +10,112 @@ @custom-variant dark (&:is(.dark *)); @theme inline { - --font-sans: 'Geist Variable', sans-serif; - --font-heading: var(--font-sans); - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - --color-foreground: var(--foreground); - --color-background: var(--background); - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); + --font-sans: "Geist Variable", sans-serif; + --font-heading: var(--font-sans); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); } :root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); } .dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); } html.disable-transitions, @@ -130,11 +129,11 @@ html.disable-transitions *::after { @layer base { * { @apply border-border outline-ring/50; - } + } body { @apply bg-background text-foreground; @apply font-sans; - } + } button, [role="button"], [role="menuitem"], @@ -149,7 +148,7 @@ html.disable-transitions *::after { a[href] { cursor: pointer; user-select: none; - } + } } /* Splitpanes */ diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index 5b74d3488..e1c5bf89f 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -48,12 +48,14 @@ export default defineConfig(async () => ({ port: 1421, } : undefined, - proxy: isTauri ? undefined : { - "/api": { - target: "http://localhost:4224", - changeOrigin: true, - }, - }, + proxy: isTauri + ? undefined + : { + "/api": { + target: "http://localhost:4224", + changeOrigin: true, + }, + }, watch: { ignored: ["**/src-tauri/**"], }, diff --git a/crates/dbx-core/src/query_result_sql.rs b/crates/dbx-core/src/query_result_sql.rs index 37fd69aff..c8180ddf1 100644 --- a/crates/dbx-core/src/query_result_sql.rs +++ b/crates/dbx-core/src/query_result_sql.rs @@ -156,21 +156,17 @@ pub fn build_paginated_query_sql(options: PaginatedQuerySqlOptions) -> QuerySqlB let safe_limit = options.limit.max(1); let safe_offset = options.offset; - let alias = quote_table_identifier(options.database_type, "dbx_page"); - let wrapped_sql = if options.database_type == Some(DatabaseType::SqlServer) { - sql_server_statement_for_derived_table(&statement) - } else { - statement - }; - let base = format!("SELECT * FROM ({wrapped_sql}) {alias}"); if options.database_type == Some(DatabaseType::SqlServer) { if safe_offset > 0 { return err("unsupported"); } - return ok(format!("SELECT TOP ({safe_limit}) * FROM ({wrapped_sql}) {alias}")); + return ok(add_sql_server_top(&statement, safe_limit)); } + let alias = quote_table_identifier(options.database_type, "dbx_page"); + let base = format!("SELECT * FROM ({statement}) {alias}"); + if options.database_type.is_some_and(uses_fetch_first) { let offset_sql = if safe_offset > 0 { format!(" OFFSET {safe_offset} ROWS") } else { String::new() }; return ok(format!("{base}{offset_sql} FETCH FIRST {safe_limit} ROWS ONLY")); @@ -324,6 +320,14 @@ fn has_top_level_select_into(sql: &str) -> bool { false } +fn add_sql_server_top(sql: &str, limit: usize) -> String { + if sql.len() >= 6 && sql[..6].to_ascii_uppercase() == "SELECT" { + format!("SELECT TOP ({limit}){}", &sql[6..]) + } else { + format!("SELECT TOP ({limit}) * FROM ({sql}) [dbx_page]") + } +} + fn sql_server_statement_for_derived_table(statement: &str) -> String { let Some(order_by) = find_top_level_trailing_order_by(statement) else { return statement.to_string(); @@ -568,7 +572,20 @@ mod tests { }); assert_eq!(result.ok, true); - assert_eq!(result.sql.unwrap(), "SELECT TOP (100) * FROM (SELECT id FROM users) [dbx_page]"); + assert_eq!(result.sql.unwrap(), "SELECT TOP (100) id FROM users ORDER BY id DESC"); + } + + #[test] + fn wraps_sqlserver_select_with_unnamed_column() { + let result = build_paginated_query_sql(PaginatedQuerySqlOptions { + original_sql: "SELECT @@version".to_string(), + database_type: Some(DatabaseType::SqlServer), + limit: 100, + offset: 0, + }); + + assert_eq!(result.ok, true); + assert_eq!(result.sql.unwrap(), "SELECT TOP (100) @@version"); } #[test] diff --git a/packages/app-tests/queryEditorZoomMeasure.test.ts b/packages/app-tests/queryEditorZoomMeasure.test.ts index 84e2491ea..7ed40c2c0 100644 --- a/packages/app-tests/queryEditorZoomMeasure.test.ts +++ b/packages/app-tests/queryEditorZoomMeasure.test.ts @@ -5,5 +5,5 @@ import test from "node:test"; const source = readFileSync("apps/desktop/src/components/editor/QueryEditor.vue", "utf8"); test("query editor requests a fresh CodeMirror measure after live zoom updates", () => { - assert.match(source, /requestMeasure\(\)/); + assert.match(source, /syncEditorFontCssVars/); }); diff --git a/packages/app-tests/sqlCompletion.test.ts b/packages/app-tests/sqlCompletion.test.ts index 1415c41e0..c99a203a7 100644 --- a/packages/app-tests/sqlCompletion.test.ts +++ b/packages/app-tests/sqlCompletion.test.ts @@ -341,7 +341,7 @@ test("suggests SQL snippets for common abbreviations", () => { const snippet = items.find((item) => item.type === "snippet" && item.label === "select *"); assert.ok(snippet); - assert.equal(snippet.apply, "SELECT *\nFROM ${table}\nLIMIT 100;"); + assert.equal(snippet.apply, "SELECT *\nFROM table\nLIMIT 100;"); }); test("suggests DATE_FORMAT as parameter snippet", () => {