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", () => {