[BUG] Inline row editing refuses SQL Server table-click, Generate Query, and SELECT TOP queries because selectsPlainColumn does not skip TOP
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 38/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- sql, typescript
- Domain
- database
Research direction
Read src/lib/sql/update-target.ts, especially selectsPlainColumn around lines 380–473, and the existing cases in tests/unit/sql/update-target.test.ts. Reproduce with the SQL Server SELECT TOP examples in the issue, then add coverage for supported TOP forms and cases that must remain accurate. Done when direct base-table columns behind TOP are recognized while aliases, expressions, and columns or functions named top are handled correctly.
Written by the indexing model from the issue text.
Description
Description
In src/lib/sql/update-target.ts, selectsPlainColumn (lines 380–473) strips ALL, DISTINCT, and DISTINCT ON (...) after SELECT (lines 390–404), but never skips a TOP row-count clause (TOP <n> or TOP (<expr>) [PERCENT] [WITH TIES]).
On SQL Server (mssql), both of LibreDB Studio's built-in Schema Explorer query actions emit SELECT TOP ...:
- Clicking a table in Schema Explorer (
generateTableQuery→MSSQL_SQL_PORT_GENERATORS.selectinsrc/lib/db/providers/sql/mssql-generators.ts, line 17):SELECT TOP 50 * FROM dbo.users; - Clicking "Generate Query" on a table in Schema Explorer (
generateSelectQuery→MSSQL_SQL_PORT_GENERATORS.selectListinsrc/lib/db/providers/sql/mssql-generators.ts, line 18):SELECT TOP 100 id, name FROM dbo.users WHERE 1=1;
When the user edits a cell in the Results Grid and clicks Apply Changes, handleApplyChanges in src/hooks/use-inline-editing.ts first calls resolveUpdateTarget(sql, activeConnection.type) (line 676), which succeeds and resolves { kind: "table", table: "dbo.users" } (explicitly covered by tests/unit/sql/update-target.test.ts line 62: expect(table("SELECT TOP 50 * FROM users;", "mssql")).toBe("users")).
Immediately afterward, handleApplyChanges checks selectsPlainColumn(currentTab.resultQuery ?? currentTab.query, pkColumn, activeConnection.type) (line 753). Because selectsPlainColumn does not advance start past TOP, the TOP tokens remain attached to the first item of items before the first comma:
| Query shape | resolveUpdateTarget(sql, "mssql") |
First item in selectsPlainColumn |
selectsPlainColumn(sql, "id", "mssql") |
handleApplyChanges outcome |
|---|---|---|---|---|
SELECT TOP 50 * FROM dbo.users; (Schema Explorer table click) |
{ kind: "table", table: "dbo.users" } |
[TOP, 5, 0, *] (isStar → false) |
false (for every column) |
Refused with destructive toast |
SELECT TOP 100\n id,\n name\nFROM dbo.users\nWHERE 1=1; (Schema Explorer "Generate Query") |
{ kind: "table", table: "dbo.users" } |
[TOP, 1, 0, 0, id] (isReference → false) |
false |
Refused with destructive toast |
SELECT TOP (50) * FROM [dbo].[users]; |
{ kind: "table", table: "[dbo].[users]" } |
[TOP, (, ), *] (isStar → false) |
false (for every column) |
Refused with destructive toast |
SELECT TOP (100) id, name FROM dbo.users; |
{ kind: "table", table: "dbo.users" } |
[TOP, (, ), id] (isReference → false) |
false |
Refused with destructive toast |
SELECT DISTINCT TOP 50 id, name FROM dbo.users; |
{ kind: "table", table: "dbo.users" } |
[TOP, 5, 0, id] (isReference → false) |
false |
Refused with destructive toast |
SELECT TOP 10 PERCENT WITH TIES id, name FROM dbo.users ORDER BY id; |
{ kind: "table", table: "dbo.users" } |
[TOP, 1, 0, PERCENT, WITH, TIES, id] |
false |
Refused with destructive toast |
Steps to Reproduce
- On
main(f9f03596e), run with Bun:
import { generateSelectQuery, generateTableQuery } from "@/lib/query-generators";
import { resolveUpdateTarget, selectsPlainColumn } from "@/lib/sql/update-target";
const mssqlCaps = { defaultPort: 1433, queryLanguage: "sql" } as any;
const cols = [
{ name: "id", type: "int", nullable: false },
{ name: "name", type: "nvarchar(100)", nullable: true },
];
const tableClickSql = generateTableQuery(["dbo", "users"], cols, mssqlCaps);
console.log("Table-click SQL:", tableClickSql);
console.log(" resolveUpdateTarget:", resolveUpdateTarget(tableClickSql, "mssql"));
console.log(" selectsPlainColumn(id):", selectsPlainColumn(tableClickSql, "id", "mssql"));
const generateQuerySql = generateSelectQuery(["dbo", "users"], cols, mssqlCaps);
console.log("\nGenerate-Query SQL:\n" + generateQuerySql);
console.log(" resolveUpdateTarget:", resolveUpdateTarget(generateQuerySql, "mssql"));
console.log(" selectsPlainColumn(id):", selectsPlainColumn(generateQuerySql, "id", "mssql"));
for (const sql of [
"SELECT TOP (50) * FROM [dbo].[users]",
"SELECT TOP (100) id, name FROM dbo.users",
"SELECT DISTINCT TOP 50 id, name FROM dbo.users",
"SELECT TOP 10 PERCENT WITH TIES id, name FROM dbo.users ORDER BY id",
]) {
console.log(`\n${sql}`);
console.log(" selectsPlainColumn(id):", selectsPlainColumn(sql, "id", "mssql"));
}
- Observed output on
main(f9f03596e):
Table-click SQL: SELECT TOP 50 * FROM dbo.users;
resolveUpdateTarget: { kind: "table", table: "dbo.users" }
selectsPlainColumn(id): false
Generate-Query SQL:
SELECT TOP 100
id,
name
FROM dbo.users
WHERE 1=1;
resolveUpdateTarget: { kind: "table", table: "dbo.users" }
selectsPlainColumn(id): false
SELECT TOP (50) * FROM [dbo].[users]
selectsPlainColumn(id): false
SELECT TOP (100) id, name FROM dbo.users
selectsPlainColumn(id): false
SELECT DISTINCT TOP 50 id, name FROM dbo.users
selectsPlainColumn(id): false
SELECT TOP 10 PERCENT WITH TIES id, name FROM dbo.users ORDER BY id
selectsPlainColumn(id): false
- In LibreDB Studio connected to SQL Server (
mssql), click any table with anidor*_idprimary key in Schema Explorer (or use Generate Query), double-click a cell in the Results Grid to edit it, and click Apply Changes.useInlineEditingaborts before issuing any query and shows the destructive toast:Cannot Apply Changes
id is not read straight from the table here, so it cannot identify a row to write to. Edit the SQL manually.
Expected Behavior
selectsPlainColumnskips a leadingTOP <n>orTOP (<expr>)clause (and optionalPERCENT/WITH TIESmodifiers) afterSELECT [ALL | DISTINCT], just as it already skipsALL,DISTINCT, andDISTINCT ON (...).- Inline row editing on SQL Server succeeds for Schema Explorer table-click queries (
SELECT TOP 50 * FROM ...), Generate Query tabs (SELECT TOP 100\n id, ...\nFROM ...), and hand-written single-tableSELECT TOP ...queries when the key column is selected directly from the base table. - Expressions or renames behind
TOP(such asSELECT TOP 50 sku AS id, name FROM productsorSELECT TOP 50 ROW_NUMBER() OVER (ORDER BY name) AS id, name FROM products), as well as a column or scalar function namedtop(SELECT top FROM boxes,SELECT top(x) AS id FROM users), continue to be handled accurately without false positives.
Actual Behavior
selectsPlainColumn leaves TOP ... inside the first select-list item, returning false for every column on SELECT TOP <n> * FROM ... and false for the first projected column on SELECT TOP <n> id, ... FROM ..., which causes useInlineEditing to refuse inline edits on SQL Server.
Environment
- LibreDB Studio Version: 1.0.0 (
main@f9f03596e) - OS: Linux (WSL2 on Windows 11)
- Node.js/Bun Version: Bun 1.4.2
- Database Type: SQL Server (
mssql), DuckDB (duckdb)
Possible Solution
In selectsPlainColumn (src/lib/sql/update-target.ts, lines 390–405), after advancing start past ALL / DISTINCT / DISTINCT ON (...), advance start past a TOP clause when isWord(top[start], "TOP") is followed by:
- a numeric literal (
top[start + 1]?.kind === "other" && /^[0-9]$/.test(top[start + 1].text)), stepping past contiguous digit/decimal pieces (/^[0-9.]$/), or - a parenthesised count (
top[start + 1]?.kind === "other" && top[start + 1].text === "(") whose closing)is followed by a select-list item rather than,,FROM, orAS(so a scalar function call likeSELECT top(x) AS id FROM usersorSELECT top(x) FROM usersis not mistaken for aTOPclause),
followed by optional PERCENT and optional WITH TIES.
I have reproduced this locally and can put together the fix and unit tests in tests/unit/sql/update-target.test.ts and tests/hooks/use-inline-editing.test.ts. Could you please assign this issue to me?
- Dominant language
- TypeScript
- Stars
- 810
- Forks
- 158
- Avg merge
- 9h 45m
- Merged PRs (30d)
- 539
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from libredb/libredb-studio
-
[BUG] Column profiling fails with SQL syntax errors on columns named after reserved keywords due to unforced identifier quoting in /api/db/profilePossibly taken @saad-works claimed this 1 day ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
libredb/libredb-studio#2245 · 2 comments · 1 assignee ·
Maintainers usually reply within 1 day
-
[BUG] MCP isLoopbackBind() defaults to false when HOSTNAME is unset or bracketed, disabling Host validation and DNS rebinding protectionPossibly taken @saad-works claimed this 2 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
libredb/libredb-studio#2244 · 1 comment ·
Maintainers usually reply within 1 day
-
[BUG] inspect_schema matches named container case-insensitively without preferring exact matchPossibly taken @Maaz2212 claimed this 6 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
libredb/libredb-studio#2128 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
libredb/libredb-studio#2267 · 3 comments ·
Maintainers usually reply within 1 day
-
Validate ClickHouse 26.9 for native read-only agent executionPossibly taken @saronic-tantona claimed this 4 days ago. Openai clickhouse
libredb/libredb-studio#2210 · 1 assignee ·
Maintainers usually reply within 1 day
All issues in libredb/libredb-studio
Similar issues
-
bug needs-triage potential-regression
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
aws/aws-sdk-js-v3#8408 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
colinhacks/zod#6743 ·
Maintainers usually reply within 1 day
-
[BUG] Packing list: the weight field drops digits on a slow connection and saves the wrong weightOpen
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
openai/openai-node#3124 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
anthropics/anthropic-sdk-typescript#1296 ·
Maintainers usually reply within 8 days