fix: avoid derived table wrapping for SQL Server TOP pagination
Replace derived table wrapping with direct TOP injection for SQL Server to prevent error 8155 when inner SELECT produces unnamed columns (e.g. SELECT @@version). Also preserves ORDER BY that was incorrectly stripped by the previous approach.
This commit is contained in:
parent
67572cd685
commit
e5e86eda30
|
|
@ -18,7 +18,9 @@
|
|||
color: #888;
|
||||
font-size: 13px;
|
||||
}
|
||||
#root:empty::after { content: "Loading…"; }
|
||||
#root:empty::after {
|
||||
content: "Loading…";
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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/**"],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue