[BUG] Column profiling fails with SQL syntax errors on columns named after reserved keywords due to unforced identifier quoting in /api/db/profile
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 88/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- sql, typescript
- Domain
- databases
Research direction
Read src/lib/query-generators.ts around the always option, then inspect the two quoteIdentifier calls in src/app/api/db/profile/route.ts (around lines 339 and 347). Pass { always: true } at both call sites and check the profile route’s existing tests, adding or running coverage for a column named order; done means profiling returns statistics and samples without SQL syntax errors.
Written by the indexing model from the issue text.
Description
Description
When profiling a SQL database table via POST /api/db/profile, the route queries summary statistics (COUNT(*), COUNT(col), COUNT(DISTINCT col), MIN(col), MAX(col)) and samples the top rows.
To format column names for the target dialect, the route calls quoteIdentifier(col, capabilities) in src/app/api/db/profile/route.ts at line 339 and line 347. However, it omits { always: true }.
As documented in src/lib/query-generators.ts (lines 61–65):
* `always` quotes a name that would round-trip bare as well. The bare test is about case and
* characters only, so a lowercase reserved word passes it: `when`, `order`, `user` and `group`
* come back bare and the engine reads them as keywords (#1396).
Because { always: true } is not passed, any column whose name matches a standard SQL keyword (e.g. order, group, user, from, key, default, type, date, time, check, value, offset, limit) passes the /^[a-z_][a-z0-9_$]*$/ test and is returned bare (unquoted).
When profileSqlColumn executes:
SELECT COUNT(*) AS total_count, COUNT(order) AS non_null_count, COUNT(DISTINCT order) AS distinct_count, MIN(order) AS min_value, MAX(order) AS max_value FROM "table"
and:
SELECT COUNT(*) AS null_count FROM "table" WHERE order IS NULL
and:
SELECT order FROM "table" LIMIT 5
every SQL dialect (PostgreSQL, SQLite, MySQL, SQL Server, Oracle, ClickHouse) rejects the statements with an immediate SQL syntax error (near ")": syntax error or syntax error at or near "order"). The entire profile request fails or reports an error for that column rather than returning statistics.
Steps to Reproduce
- In SQLite, PostgreSQL, or MySQL, create a table containing a column whose name is a reserved SQL keyword:
CREATE TABLE customer_orders (id INT PRIMARY KEY, "order" INT); - In LibreDB Studio Schema Explorer, click "Profile" on the
customer_orderstable, or invokePOST /api/db/profiledirectly:{ "connection": { ... }, "tablePath": ["customer_orders"], "columns": ["order"] } - Inspect the response payload or UI card.
- Notice that profiling fails with a syntax error for the
"order"column.
Expected Behavior
The column name should be escaped/quoted using the target dialect's identifier quotes (e.g., "order" in SQLite/PostgreSQL/Oracle or `order` in MySQL) so that queries like COUNT("order") and WHERE "order" IS NULL execute cleanly.
Actual Behavior
quoteIdentifier("order", capabilities) returns order unquoted because { always: true } is omitted. The database throws a SQL syntax error, and profiling for that column fails.
Environment
- LibreDB Studio Version: 1.2.0
- Browser: All
- OS: macOS / Linux / Windows
- Node.js/Bun Version: Bun 1.4.3 / Node.js 24
- Database Type: PostgreSQL, MySQL, SQLite, Oracle, SQL Server, ClickHouse
- Database Version: All
Additional Context
src/components/DataImportModal.tsx already uses { always: true } for this exact reason:
return capabilities === undefined ? name : quoteIdentifier(name, capabilities, { always: true });
src/app/api/db/profile/route.ts is missing this argument at lines 339 and 347.
Possible Solution
In src/app/api/db/profile/route.ts, pass { always: true } to quoteIdentifier:
// Line 339
for (const col of profiled) {
columnProfiles.push(
await profileSqlColumn(provider, safeTable, col, quoteIdentifier(col, capabilities, { always: true }), totalRows),
);
}
// Line 347
const topCols = profiled.slice(0, 5);
const safeCols = topCols.map((c) => quoteIdentifier(c, capabilities, { always: true })).join(", ");
- 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] MCP isLoopbackBind() defaults to false when HOSTNAME is unset or bracketed, disabling Host validation and DNS rebinding protectionPossibly taken @saad-works claimed this 1 day 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
-
Difficulty 3/5 1-2 days Newbie friendliness 38/100
libredb/libredb-studio#2265 ·
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