Problem
The schema tree currently generates SQL for a table directly into the active editor:
-
double-click replaces the active editor with:
SELECT * FROM db.table LIMIT 100
-
Shift-click fetches SHOW CREATE, formats the returned DDL, and replaces the active editor with it.
Both actions can destroy an unrelated query that the user was editing.
The application already has a normal tab-opening path:
app.actions.loadIntoNewTab(name, sql)
Generated table SQL should use that path instead of replacing the current document.
Goal
For table rows in the schema tree:
-
double-click opens the generated SELECT * in a new query tab;
-
Shift-click opens the formatted SHOW CREATE result in a new query tab;
-
the new tab is named with the unquoted qualified table name:
-
the previously active tab and its SQL remain unchanged.
Required behavior
Double-click
Given a table:
database: db1
table: orders
double-click opens a new active tab:
Tab name: db1.orders
SQL: SELECT * FROM db1.orders LIMIT 100
Identifier quoting remains SQL-safe:
Tab name: analytics.daily events
SQL: SELECT * FROM analytics.`daily events` LIMIT 100
The display name uses the original database and table names. The SQL uses the existing qualifyIdent() result.
Shift-click
Shift-click executes the existing two-step DDL retrieval:
SHOW CREATE db1.orders FORMAT JSON
followed by best-effort formatting through formatQuery().
After a successful response, open a new active tab:
Tab name: db1.orders
SQL: <formatted DDL>
If formatting fails, open the raw DDL, matching the existing fallback behavior.
If SHOW CREATE fails or returns no statement:
- do not create a tab;
- do not modify the current editor;
- preserve the existing error toast behavior.
Existing tab preservation
For both actions:
- current tab SQL is byte-for-byte unchanged;
- current tab name, result, saved-query link, panel configuration, and dirty state are unchanged;
- no SQL is automatically executed;
- no generated tab is automatically saved to the Library.
Expansion behavior
Keep the current cross-browser double-click model.
The first click of a quick two-click sequence may still perform the ordinary table expand/collapse action before the second click opens the new tab. Avoiding that would require delaying every single-click expansion and is outside this fix.
Database and column rows
Do not change other schema-row behavior in this issue:
- database double-click still inserts the database identifier at the cursor;
- database Shift-click keeps its existing
SHOW CREATE DATABASE behavior;
- column double-click and Shift-click keep their insert-at-cursor behavior;
- drag-and-drop behavior is unchanged.
Implementation
src/ui/schema.js
Replace the table double-click action:
app.actions.replaceEditor(
'SELECT * FROM ' + qname + ' LIMIT 100',
);
with:
app.actions.loadIntoNewTab(
key,
'SELECT * FROM ' + qname + ' LIMIT 100',
);
Here:
key = db.db + '.' + tb.name
is the requested human-readable tab name, while qname remains the SQL-safe qualified identifier.
Replace the table Shift-click action with a dedicated new-tab DDL action:
app.actions.openCreateInNewTab(qname, key);
Do not call the current editor-replacing insertCreate() path for a table.
Update the no-comment table hover text so the destination is explicit:
Click to expand · double-click SELECT * in new tab ·
shift-click SHOW CREATE in new tab · drag to insert name
src/ui/app.js
Separate fetching/formatting the DDL from deciding where it is placed.
A suitable shape is:
async function fetchCreateSql(target) {
await ensureConfig();
if (!(await getToken())) {
chCtx.onSignedOut();
return null;
}
try {
const show = await ch.queryJson(
chCtx,
'SHOW CREATE ' + target + ' FORMAT JSON',
);
const stmt =
(show.data && show.data[0] && show.data[0].statement) || '';
if (!stmt) return null;
try {
const formatted = await ch.queryJson(
chCtx,
'SELECT formatQuery(' + sqlString(stmt) + ') AS q FORMAT JSON',
);
return (
formatted.data &&
formatted.data[0] &&
formatted.data[0].q
) || stmt;
} catch {
return stmt;
}
} catch (error) {
flashToast(
'SHOW CREATE failed: ' +
String((error && error.message) || error),
{ document: doc },
);
return null;
}
}
Keep the existing editor-replacing action for its existing consumers:
async function insertCreate(target) {
const sql = await fetchCreateSql(target);
if (sql != null) app.editor.replaceDocument(sql);
}
Add:
async function openCreateInNewTab(target, name) {
const sql = await fetchCreateSql(target);
if (sql == null) return;
loadIntoNewTab(app, name, sql);
toEditorOnMobile();
}
Expose it through:
app.actions.openCreateInNewTab
Equivalent factoring is acceptable. The important contract is that fetching and formatting are shared rather than duplicated.
Tests
Schema dispatch
Update tests/unit/schema.test.js.
Double-clicking a table:
Shift-clicking a table:
Add a quoted-identifier case verifying:
display name: analytics.daily events
SQL: SELECT * FROM analytics.`daily events` LIMIT 100
DDL new-tab action
Update tests/unit/app.test.js.
On success:
- preserve the original active tab and its SQL;
- create exactly one additional tab;
- make it active;
- set its name to
db1.events;
- set its SQL to the formatted DDL;
- do not execute the generated SQL.
When formatting fails:
- create the tab with raw DDL.
When SHOW CREATE fails or returns no statement:
- create no tab;
- preserve the current tab;
- show the existing failure toast where applicable.
Regressions
Verify:
- existing
insertCreate() behavior remains covered for unchanged consumers;
loadIntoNewTab() still focuses the editor;
- database and column schema interactions remain unchanged;
- table single-click expansion and lazy column loading remain unchanged.
Files
Expected changes:
src/ui/schema.js
src/ui/app.js
tests/unit/schema.test.js
tests/unit/app.test.js
No new runtime dependency.
Acceptance criteria
Non-goals
- Preventing the first click of a double-click from toggling expansion.
- Changing database-row actions.
- Changing column-row actions.
- Reusing an already-open tab with the same name.
- Automatically running generated SQL.
- Automatically saving generated tabs.
Problem
The schema tree currently generates SQL for a table directly into the active editor:
double-click replaces the active editor with:
Shift-click fetches
SHOW CREATE, formats the returned DDL, and replaces the active editor with it.Both actions can destroy an unrelated query that the user was editing.
The application already has a normal tab-opening path:
Generated table SQL should use that path instead of replacing the current document.
Goal
For table rows in the schema tree:
double-click opens the generated
SELECT *in a new query tab;Shift-click opens the formatted
SHOW CREATEresult in a new query tab;the new tab is named with the unquoted qualified table name:
the previously active tab and its SQL remain unchanged.
Required behavior
Double-click
Given a table:
double-click opens a new active tab:
Identifier quoting remains SQL-safe:
The display name uses the original database and table names. The SQL uses the existing
qualifyIdent()result.Shift-click
Shift-click executes the existing two-step DDL retrieval:
followed by best-effort formatting through
formatQuery().After a successful response, open a new active tab:
If formatting fails, open the raw DDL, matching the existing fallback behavior.
If
SHOW CREATEfails or returns no statement:Existing tab preservation
For both actions:
Expansion behavior
Keep the current cross-browser double-click model.
The first click of a quick two-click sequence may still perform the ordinary table expand/collapse action before the second click opens the new tab. Avoiding that would require delaying every single-click expansion and is outside this fix.
Database and column rows
Do not change other schema-row behavior in this issue:
SHOW CREATE DATABASEbehavior;Implementation
src/ui/schema.jsReplace the table double-click action:
with:
Here:
is the requested human-readable tab name, while
qnameremains the SQL-safe qualified identifier.Replace the table Shift-click action with a dedicated new-tab DDL action:
Do not call the current editor-replacing
insertCreate()path for a table.Update the no-comment table hover text so the destination is explicit:
src/ui/app.jsSeparate fetching/formatting the DDL from deciding where it is placed.
A suitable shape is:
Keep the existing editor-replacing action for its existing consumers:
Add:
Expose it through:
Equivalent factoring is acceptable. The important contract is that fetching and formatting are shared rather than duplicated.
Tests
Schema dispatch
Update
tests/unit/schema.test.js.Double-clicking a table:
calls:
does not call
replaceEditor().Shift-clicking a table:
calls:
does not call
insertCreate();does not expand the table;
does not load columns.
Add a quoted-identifier case verifying:
DDL new-tab action
Update
tests/unit/app.test.js.On success:
db1.events;When formatting fails:
When
SHOW CREATEfails or returns no statement:Regressions
Verify:
insertCreate()behavior remains covered for unchanged consumers;loadIntoNewTab()still focuses the editor;Files
Expected changes:
src/ui/schema.jssrc/ui/app.jstests/unit/schema.test.jstests/unit/app.test.jsNo new runtime dependency.
Acceptance criteria
SELECT * FROM … LIMIT 100in a new active tab.SHOW CREATEDDL in a new active tab.db.tableusing original display names.qualifyIdent().SHOW CREATEfailure or an empty result creates no tab.Non-goals