Skip to content

fix(web-console): update query extracting logic from cursor #421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/browser-tests/cypress/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ Cypress.Commands.add("getGridRow", (n) =>
cy.get(".qg-r").filter(":visible").eq(n)
);

Cypress.Commands.add("getColumnName", (n) => {
cy.get(".qg-header-name").filter(":visible").eq(n);
})

Cypress.Commands.add("getGridCol", (n) =>
cy.get(".qg-c").filter(":visible").eq(n)
);
Expand Down Expand Up @@ -132,6 +136,12 @@ Cypress.Commands.add("runLineWithResponse", (response) => {
cy.wait("@exec");
});

Cypress.Commands.add("clickLine", (n) => {
cy.get(".monaco-editor .view-line")
.eq(n - 1)
.click();
});

Cypress.Commands.add("clickRun", () => {
cy.intercept("/exec*").as("exec");
return cy.get("button").contains("Run").click().wait("@exec");
Expand Down
72 changes: 69 additions & 3 deletions packages/browser-tests/cypress/integration/console/editor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,71 @@ const baseUrl = `http://localhost:9999${contextPath}`;
const getTabDragHandleByTitle = (title) =>
`.chrome-tab[data-tab-title="${title}"] .chrome-tab-drag-handle`;

describe("run query", () => {
beforeEach(() => {
cy.loadConsoleWithAuth();
cy.getEditorContent().should("be.visible");
cy.clearEditor();
});

it("should correctly run query in the first line", () => {
cy.typeQuery("select 1;\n\nselect 2;");
cy.clickLine(1);
cy.clickRun();
cy.getGridRow(0).should("contain", "1");
});

it("should run the correct query when there are multiple queries in the same line", () => {
cy.typeQuery(
"with longseq as (\nselect * from long_sequence(100)\n-- comment"
);
cy.typeQuery(" select count(*) from longseq;select 1;");

// go to the end of second query
cy.clickLine(4);
cy.clickRun();
cy.getGridCol(0).should("contain", "1");
cy.getGridRow(0).should("contain", "1");

// go inside the second query
cy.clickLine(4);
cy.realPress("ArrowLeft");
cy.realPress("ArrowLeft");
cy.clickRun();
cy.getColumnName(0).should("contain", "1");
cy.getGridRow(0).should("contain", "1");

// go to the end of first query
cy.clickLine(4);
for (let i = 0; i < 9; i++) {
cy.realPress("ArrowLeft");
}
cy.clickRun();
cy.getColumnName(0).should("contain", "count");
cy.getGridRow(0).should("contain", "100");

// go inside the first query
cy.clickLine(4);
for (let i = 0; i < 11; i++) {
cy.realPress("ArrowLeft");
}
cy.clickRun();
cy.getColumnName(0).should("contain", "count");
cy.getGridRow(0).should("contain", "100");
});

it("should not suggest any query for running if the cursor is in an empty line between queries", () => {
cy.typeQuery("select 1;\n\nselect 2;");
cy.getCursorQueryGlyph().should("be.visible");

cy.realPress("ArrowUp");
cy.getCursorQueryGlyph().should("not.exist");

cy.realPress("ArrowUp");
cy.getCursorQueryGlyph().should("be.visible");
});
});

describe("appendQuery", () => {
const consoleConfiguration = {
savedQueries: [
Expand Down Expand Up @@ -158,6 +223,7 @@ describe("&query URL param", () => {
it("should not append query if it already exists in editor", () => {
const query = "select x\nfrom long_sequence(1);\n\n-- a\n-- b\n-- c";
cy.typeQuery(query);
cy.clickLine(1);
cy.clickRun();
cy.visit(`${baseUrl}?query=${encodeURIComponent(query)}&executeQuery=true`);
cy.getEditorContent().should("be.visible");
Expand All @@ -167,7 +233,6 @@ describe("&query URL param", () => {
it("should append query and scroll to it", () => {
cy.typeQuery("select x from long_sequence(1);");
cy.typeQuery("\n".repeat(20));
cy.clickRun(); // take space so that query is not visible later, save by running

const appendedQuery = "-- hello world";
cy.visit(`${baseUrl}?query=${encodeURIComponent(appendedQuery)}`);
Expand Down Expand Up @@ -396,7 +461,7 @@ describe("running query with F9", () => {
"select * from long_sequence(1); -- comment\nselect * from long_sequence(2);{upArrow}{rightArrow}{rightArrow}"
);
cy.F9();
cy.getGridRows().should("have.length", 2);
cy.getGridRows().should("have.length", 1);
cy.getCursorQueryDecoration().should("have.length", 1);
});
});
Expand Down Expand Up @@ -487,7 +552,8 @@ describe("editor tabs", () => {
});
});

describe("editor tabs history", () => {
// TODO: This test is flaky because of the IndexedDB calls. Investigate the slow response time in test environment.
describe.skip("editor tabs history", () => {
before(() => {
cy.loadConsoleWithAuth();
cy.getEditorContent().should("be.visible");
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-tests/questdb
Submodule questdb updated 45 files
+1 −1 benchmarks/pom.xml
+1 −1 compat/pom.xml
+3 −3 core/pom.xml
+5 −0 core/src/main/java/io/questdb/cairo/BitmapIndexWriter.java
+11 −4 core/src/main/java/io/questdb/cairo/CairoEngine.java
+93 −77 core/src/main/java/io/questdb/cairo/SymbolMapWriter.java
+3 −1 core/src/main/java/io/questdb/cairo/TableUtils.java
+2 −0 core/src/main/java/io/questdb/cairo/TxWriter.java
+62 −9 core/src/main/java/io/questdb/cairo/mv/MatViewGraph.java
+9 −1 core/src/main/java/io/questdb/cairo/wal/OperationExecutor.java
+8 −2 core/src/main/java/io/questdb/cutlass/pgwire/modern/PGConnectionContextModern.java
+4 −0 core/src/main/java/io/questdb/cutlass/pgwire/modern/PGPipelineEntry.java
+26 −9 core/src/main/java/io/questdb/griffin/InsertRowImpl.java
+29 −15 core/src/main/java/io/questdb/griffin/SqlCompilerImpl.java
+44 −36 core/src/main/java/io/questdb/griffin/SqlOptimiser.java
+32 −27 core/src/main/java/io/questdb/griffin/SqlParser.java
+12 −11 core/src/main/java/io/questdb/griffin/SqlUtil.java
+1 −1 core/src/main/java/io/questdb/griffin/engine/functions/table/TableColumnsFunctionFactory.java
+2 −0 core/src/main/java/io/questdb/griffin/engine/ops/CreateMatViewOperation.java
+82 −29 core/src/main/java/io/questdb/griffin/engine/ops/CreateMatViewOperationImpl.java
+6 −1 core/src/main/java/io/questdb/griffin/engine/ops/CreateTableOperation.java
+15 −4 core/src/main/java/io/questdb/griffin/engine/ops/CreateTableOperationBuilderImpl.java
+26 −3 core/src/main/java/io/questdb/griffin/engine/ops/CreateTableOperationImpl.java
+1 −3 core/src/test/java/io/questdb/test/AbstractCairoTest.java
+1 −1 core/src/test/java/io/questdb/test/DynamicPropServerConfigurationTest.java
+11 −0 core/src/test/java/io/questdb/test/cairo/CairoEngineTest.java
+8 −9 core/src/test/java/io/questdb/test/cairo/MetadataCacheTest.java
+5 −6 core/src/test/java/io/questdb/test/cairo/TtlTest.java
+1 −1 core/src/test/java/io/questdb/test/cairo/TxnTest.java
+2 −2 core/src/test/java/io/questdb/test/cairo/fuzz/O3MaxLagFuzzTest.java
+324 −285 core/src/test/java/io/questdb/test/cairo/mv/CreateMatViewTest.java
+69 −8 core/src/test/java/io/questdb/test/cairo/mv/MatViewGraphAndStateStoreTest.java
+68 −0 core/src/test/java/io/questdb/test/cairo/mv/MatViewTest.java
+27 −12 core/src/test/java/io/questdb/test/cutlass/pgwire/PGJobContextTest.java
+1 −1 core/src/test/java/io/questdb/test/griffin/InsertNullTest.java
+26 −0 core/src/test/java/io/questdb/test/griffin/ShowCreateTableTest.java
+19 −5 core/src/test/java/io/questdb/test/griffin/ShowTablesTest.java
+2 −0 core/src/test/java/io/questdb/test/griffin/SqlCompilerImplTest.java
+4 −2 core/src/test/java/io/questdb/test/griffin/SqlParserTest.java
+13 −12 core/src/test/java/io/questdb/test/griffin/TimestampBoundsTest.java
+21 −21 core/src/test/java/io/questdb/test/griffin/TimestampQueryTest.java
+23 −12 core/src/test/java/io/questdb/test/griffin/engine/functions/str/LPadRPadFunctionsTest.java
+1 −1 examples/pom.xml
+1 −1 pom.xml
+1 −1 utils/pom.xml
6 changes: 6 additions & 0 deletions packages/web-console/src/scenes/Editor/Monaco/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ const MonacoEditor = () => {
editor: editor.IStandaloneCodeEditor,
) => {
const queryAtCursor = getQueryFromCursor(editor)

if (!queryAtCursor) {
decorationsRef.current?.clear()
return
}

const model = editor.getModel()
if (queryAtCursor && model !== null) {
const activeBufferId = activeBufferRef.current.id as number
Expand Down
89 changes: 67 additions & 22 deletions packages/web-console/src/scenes/Editor/Monaco/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,15 @@ export const getQueryFromCursor = (
let startRow = 0
let startCol = 0
let startPos = -1
let sql = null
let nextSql = null
let inQuote = false

if (!position) return

for (let i = 0; i < text.length; i++) {
if (sql !== null) {
let i = 0;

for (; i < text.length; i++) {
if (nextSql !== null) {
break
}

Expand All @@ -97,6 +99,8 @@ export const getQueryFromCursor = (
row: startRow,
col: startCol,
position: startPos,
endRow: row,
endCol: column,
limit: i,
})
startRow = row
Expand All @@ -105,7 +109,14 @@ export const getQueryFromCursor = (
column++
} else {
// empty queries, aka ;; , make sql.length === 0
sql = text.substring(startPos === -1 ? 0 : startPos, i)
nextSql = {
row: startRow,
col: startCol,
position: startPos,
endRow: row,
endCol: column,
limit: i,
}
}
break
}
Expand All @@ -130,7 +141,6 @@ export const getQueryFromCursor = (
startRow = row
startCol = column
startPos = i + 1
column++
}
break
}
Expand All @@ -148,28 +158,63 @@ export const getQueryFromCursor = (
}
}

if (sql === null) {
sql = startPos === -1 ? text : text.substring(startPos)
// lastStackItem is the last query that is completed before the current cursor position.
// nextSql is the next query that is not completed before the current cursor position, or started after the current cursor position.
const normalizedCurrentRow = position.lineNumber - 1
const lastStackItem = sqlTextStack.length > 0 ? sqlTextStack[sqlTextStack.length - 1] : null

if (!nextSql) {
const sqlText = startPos === - 1 ? text : text.substring(startPos)
if (sqlText.length > 0) {
nextSql = {
row: startRow,
col: startCol,
position: startPos === -1 ? 0 : startPos,
endRow: row,
endCol: column,
limit: i,
}
}
}

if (sql.length === 0) {
const prev = sqlTextStack.pop()

if (prev) {
const lastStackItemRowRange = lastStackItem ? {
start: lastStackItem.row,
end: lastStackItem.endRow,
} : null
const nextSqlRowRange = nextSql ? {
start: nextSql.row,
end: nextSql.endRow,
} : null
const isInLastStackItemRowRange = lastStackItemRowRange && normalizedCurrentRow >= lastStackItemRowRange.start && normalizedCurrentRow <= lastStackItemRowRange.end
const isInNextSqlRowRange = nextSqlRowRange && normalizedCurrentRow >= nextSqlRowRange.start && normalizedCurrentRow <= nextSqlRowRange.end

if (isInLastStackItemRowRange && !isInNextSqlRowRange) {
return {
query: text.substring(lastStackItem!.position, lastStackItem!.limit),
row: lastStackItem!.row,
column: lastStackItem!.col,
}
} else if (isInNextSqlRowRange && !isInLastStackItemRowRange) {
return {
query: text.substring(nextSql!.position, nextSql!.limit),
row: nextSql!.row,
column: nextSql!.col,
}
} else if (isInLastStackItemRowRange && isInNextSqlRowRange) {
const lastStackItemEndCol = lastStackItem!.endCol
const normalizedCurrentCol = position.column - 1
if (normalizedCurrentCol > lastStackItemEndCol + 1) {
return {
column: prev.col,
query: text.substring(prev.position, prev.limit),
row: prev.row,
query: text.substring(nextSql!.position, nextSql!.limit),
row: nextSql!.row,
column: nextSql!.col,
}
}

return
}

return {
column: startCol,
query: sql,
row: startRow,
return {
query: text.substring(lastStackItem!.position, lastStackItem!.limit),
row: lastStackItem!.row,
column: lastStackItem!.col,
}
}
}

Expand Down