Skip to content
Open
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
2 changes: 1 addition & 1 deletion e2e/questdb
Submodule questdb updated 60 files
+12 −6 ci/templates/hosted-jobs.yml
+5 −1 core/src/main/c/share/jit/aarch64.h
+156 −29 core/src/main/c/share/jit/avx2.h
+92 −3 core/src/main/c/share/jit/common.h
+12 −2 core/src/main/c/share/jit/compiler.cpp
+29 −5 core/src/main/c/share/jit/impl/avx2.h
+43 −9 core/src/main/c/share/jit/impl/x86.h
+14 −8 core/src/main/c/share/jit/x86.h
+8 −3 core/src/main/java/io/questdb/cairo/NanosTimestampDriver.java
+3 −2 core/src/main/java/io/questdb/cairo/TimestampDriver.java
+12 −2 core/src/main/java/io/questdb/cairo/idx/AbstractPostingIndexReader.java
+60 −19 core/src/main/java/io/questdb/cairo/idx/CoveringCompressor.java
+35 −5 core/src/main/java/io/questdb/cairo/idx/PostingIndexWriter.java
+2 −2 core/src/main/java/io/questdb/cairo/lv/LiveViewCheckpointStateCodec.java
+63 −18 core/src/main/java/io/questdb/cairo/lv/LiveViewRefreshJob.java
+19 −3 core/src/main/java/io/questdb/cairo/wal/WalWriter.java
+53 −0 core/src/main/java/io/questdb/cutlass/line/LineUtils.java
+8 −5 core/src/main/java/io/questdb/cutlass/line/tcp/LineProtocolException.java
+14 −2 core/src/main/java/io/questdb/cutlass/line/tcp/LineTcpMeasurementEvent.java
+14 −8 core/src/main/java/io/questdb/cutlass/line/tcp/LineWalAppender.java
+10 −0 core/src/main/java/io/questdb/cutlass/text/CairoTextWriter.java
+30 −3 core/src/main/java/io/questdb/cutlass/text/CsvFileIndexer.java
+5 −3 core/src/main/java/io/questdb/griffin/engine/functions/MonotonicTimestampFunction.java
+904 −21 core/src/main/java/io/questdb/jit/CompiledFilterIRSerializer.java
+ core/src/main/resources/io/questdb/bin/darwin-aarch64/libquestdb.dylib
+ core/src/main/resources/io/questdb/bin/linux-aarch64/libquestdb.so
+ core/src/main/resources/io/questdb/bin/linux-x86-64/libquestdb.so
+ core/src/main/resources/io/questdb/bin/windows-x86-64/libquestdb.dll
+2 −1 core/src/test/c/jittests/test_app.cpp
+525 −5 core/src/test/java/io/questdb/test/cairo/covering/CoveringCompressorTest.java
+352 −0 core/src/test/java/io/questdb/test/cairo/covering/CoveringIndexMaxValueTest.java
+67 −42 core/src/test/java/io/questdb/test/cairo/lv/LiveViewCheckpointRangeRingStateTest.java
+37 −1 core/src/test/java/io/questdb/test/cairo/lv/LiveViewCheckpointStateCodecTest.java
+278 −0 core/src/test/java/io/questdb/test/cairo/lv/LiveViewStartFromSeedRestartTest.java
+210 −1 core/src/test/java/io/questdb/test/cutlass/http/line/LineHttpSenderTest.java
+379 −0 core/src/test/java/io/questdb/test/cutlass/line/tcp/LineTcpBootstrapTest.java
+85 −0 core/src/test/java/io/questdb/test/cutlass/qwp/e2e/QwpSenderE2ETest.java
+94 −0 core/src/test/java/io/questdb/test/cutlass/text/ParallelCsvFileImporterTest.java
+175 −0 core/src/test/java/io/questdb/test/cutlass/text/TextLoaderTest.java
+1,491 −9 core/src/test/java/io/questdb/test/griffin/CompiledFilterRegressionTest.java
+73 −0 core/src/test/java/io/questdb/test/griffin/CopyImportTest.java
+16 −14 core/src/test/java/io/questdb/test/griffin/MonotonicTimestampPruningTest.java
+799 −0 core/src/test/java/io/questdb/test/griffin/TimestampBoundsTest.java
+8 −6 core/src/test/java/io/questdb/test/griffin/TimestampOffsetPushdownTest.java
+1 −1 core/src/test/java/io/questdb/test/griffin/engine/functions/InIPv4Test.java
+709 −0 core/src/test/java/io/questdb/test/griffin/fuzz/FilterShapeCoverageTest.java
+5 −2 core/src/test/java/io/questdb/test/griffin/fuzz/FuzzConfig.java
+24 −2 core/src/test/java/io/questdb/test/griffin/fuzz/FuzzTableFactory.java
+233 −13 core/src/test/java/io/questdb/test/griffin/fuzz/PredicateGenerator.java
+41 −12 core/src/test/java/io/questdb/test/griffin/fuzz/clauses/GroupByClause.java
+31 −7 core/src/test/java/io/questdb/test/griffin/fuzz/clauses/SampleByClause.java
+85 −2 core/src/test/java/io/questdb/test/griffin/fuzz/expr/ExpressionGenerator.java
+30 −2 core/src/test/java/io/questdb/test/griffin/fuzz/types/CharType.java
+55 −4 core/src/test/java/io/questdb/test/griffin/fuzz/types/ColumnKind.java
+26 −0 core/src/test/java/io/questdb/test/griffin/fuzz/types/FuzzColumnTypes.java
+52 −6 core/src/test/java/io/questdb/test/griffin/fuzz/types/IPv4Type.java
+5 −2 core/src/test/java/io/questdb/test/griffin/fuzz/types/Long256Type.java
+8 −1 core/src/test/java/io/questdb/test/griffin/fuzz/types/SymbolType.java
+6 −2 core/src/test/java/io/questdb/test/griffin/fuzz/types/UuidType.java
+820 −19 core/src/test/java/io/questdb/test/jit/CompiledFilterIRSerializerTest.java
27 changes: 27 additions & 0 deletions e2e/tests/console/result_charts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,31 @@ describe("questdb charts", () => {
.invoke("height")
.should("be.gt", 0)
})

it("keeps reset zoom available when the responsive slider disappears", () => {
cy.viewport(760, 900)
cy.typeQueryDirectly(
"SELECT rnd_timestamp(to_timestamp('2024-07-19:00:00:00.000000', 'yyyy-MM-dd:HH:mm:ss.SSSUUU'), to_timestamp('2024-07-20:00:00:00.000000', 'yyyy-MM-dd:HH:mm:ss.SSSUUU'), 0), x FROM long_sequence(500);",
)
cy.clickRunIconInLine(1)
cy.getByDataHook("chart-panel-button").should("be.visible").click()

cy.getByDataHook("result-chart")
.find("canvas")
.should("be.visible")
.then(($canvas) => {
const canvas = $canvas[0]
const sliderY = canvas.getBoundingClientRect().height - 44

cy.wrap(canvas).realMouseDown({ x: 54, y: sliderY })
cy.wrap(canvas).realMouseMove(130, sliderY)
cy.wrap(canvas).realMouseUp({ x: 130, y: sliderY })
})

cy.getByDataHook("result-chart-reset-zoom").should("be.visible")

cy.viewport(1280, 900)
cy.getByDataHook("result-chart-reset-zoom").should("be.visible").click()
cy.getByDataHook("result-chart-reset-zoom").should("not.exist")
})
})
40 changes: 39 additions & 1 deletion src/components/DropdownMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react"
import * as RadixDropdownMenu from "@radix-ui/react-dropdown-menu"
import { CaretRightIcon } from "@phosphor-icons/react"
import { CaretRightIcon, CheckIcon } from "@phosphor-icons/react"
import styled, { css } from "styled-components"
import {
menuContainerStyles,
Expand Down Expand Up @@ -76,6 +76,42 @@ const Item = React.forwardRef<

Item.displayName = "DropdownMenuItem"

const StyledCheckboxItem = styled(RadixDropdownMenu.CheckboxItem)`
${menuItemStyles}
`

const CheckSlot = styled.span`
display: inline-flex;
width: 1.6rem;
margin-left: auto;
align-items: center;
justify-content: center;
color: ${({ theme }) => theme.color.contentAccent};
`

type CheckboxItemProps = React.ComponentPropsWithoutRef<
typeof RadixDropdownMenu.CheckboxItem
> & {
icon?: React.ReactNode
}

const CheckboxItem = React.forwardRef<
React.ElementRef<typeof RadixDropdownMenu.CheckboxItem>,
CheckboxItemProps
>(({ icon, children, ...props }, ref) => (
<StyledCheckboxItem ref={ref} {...props}>
{icon != null && <MenuItemIcon>{icon}</MenuItemIcon>}
{children}
<CheckSlot>
<RadixDropdownMenu.ItemIndicator asChild>
<CheckIcon size={16} weight="bold" />
</RadixDropdownMenu.ItemIndicator>
</CheckSlot>
</StyledCheckboxItem>
))

CheckboxItem.displayName = "DropdownMenuCheckboxItem"

const StyledSubTrigger = styled(RadixDropdownMenu.SubTrigger)`
${menuItemStyles}
justify-content: space-between;
Expand Down Expand Up @@ -120,6 +156,8 @@ export const DropdownMenu = {

Item,

CheckboxItem,

Sub: RadixDropdownMenu.Sub,

SubTrigger,
Expand Down
156 changes: 105 additions & 51 deletions src/consts/shared-definitions.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/modules/ConsoleEventTracker/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export enum ConsoleEvent {
NOTEBOOK_CELL_EXPAND_WIDTH = "notebook.cell_expand_width",
NOTEBOOK_CELL_SIZE_RESET = "notebook.cell_size_reset",
NOTEBOOK_CELL_VIEW_CHANGE = "notebook.cell_view_change",
NOTEBOOK_CELL_VIEW_MAXIMIZE = "notebook.cell_view_maximize",
NOTEBOOK_CELL_EDITOR_TOGGLE = "notebook.cell_editor_toggle",
NOTEBOOK_CELL_MAXIMIZE = "notebook.cell_maximize",
NOTEBOOK_DRAW_TOGGLE = "notebook.draw_toggle",
NOTEBOOK_DRAW_REFUSED = "notebook.draw_refused",
Expand Down Expand Up @@ -163,12 +163,12 @@ export enum ConsoleEvent {
MCP_DUPLICATE_CELL = "mcp.duplicate_cell",
MCP_SET_LAYOUT_MODE = "mcp.set_layout_mode",
MCP_SET_CELL_LAYOUT = "mcp.set_cell_layout",
MCP_SET_CELL_DIMENSIONS = "mcp.set_cell_dimensions",
MCP_SET_CELL_MODE = "mcp.set_cell_mode",
MCP_SET_CELL_CHART_CONFIG = "mcp.set_cell_chart_config",
MCP_SET_CELL_AUTOREFRESH = "mcp.set_cell_autorefresh",
MCP_SET_NOTEBOOK_AUTOREFRESH = "mcp.set_notebook_autorefresh",
MCP_SET_CELL_NAME = "mcp.set_cell_name",
MCP_SET_CELL_VIEW_MAXIMIZED = "mcp.set_cell_view_maximized",
MCP_SET_CELL_MAXIMIZED = "mcp.set_cell_maximized",
MCP_APPLY_NOTEBOOK_STATE = "mcp.apply_notebook_state",
MCP_GET_TABLES = "mcp.get_tables",
Expand Down
113 changes: 113 additions & 0 deletions src/scenes/Editor/Monaco/importTabs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,119 @@ describe("sanitizeBuffer", () => {
})

describe("notebookViewState sanitization", () => {
it("imports one preferred view and maps main's legacy boolean", () => {
const input = {
label: "Notebook",
value: "",
position: 0,
notebookViewState: {
cells: [
{ id: "preferred", value: "SELECT 1", paneView: "result" },
{ id: "legacy-editor", value: "SELECT 2", paneView: "editor" },
{ id: "legacy-on", value: "SELECT 3", isViewMaximized: true },
{ id: "legacy-off", value: "SELECT 4", isViewMaximized: false },
{
id: "markdown",
value: "# Title",
type: "markdown",
paneView: "result",
},
],
},
}

const cells = sanitizeBuffer(input).notebookViewState?.cells
expect(cells?.map((cell) => cell.paneView)).toEqual([
"result",
"editor_result",
"result",
"editor_result",
undefined,
])
expect(cells?.every((cell) => !("isViewMaximized" in cell))).toBe(true)
})

it("strips run/draw sub-state from markdown cells", () => {
// Given a hand-crafted import smuggling SQL sub-state onto markdown
const input = {
label: "Notebook",
value: "",
position: 0,
notebookViewState: {
cells: [
{
id: "md",
value: "# Title",
type: "markdown",
mode: "draw",
autoRefresh: true,
chartConfig: { name: "Legacy title", xColumn: "ts", queries: [] },
},
],
},
}
// When the buffer is sanitized
const cell = sanitizeBuffer(input).notebookViewState?.cells[0]
// Then the legacy chart name still becomes the cell name, but the
// run/draw sub-state apply_notebook_state would reject is gone
expect(cell).toMatchObject({ type: "markdown", name: "Legacy title" })
expect(cell && "mode" in cell).toBe(false)
expect(cell && "chartConfig" in cell).toBe(false)
expect(cell && "autoRefresh" in cell).toBe(false)
})

it("normalizes legacy SQL run mode to the implicit default", () => {
const input = {
label: "Notebook",
value: "",
position: 0,
notebookViewState: {
cells: [
{ id: "run", value: "SELECT 1", mode: "run" },
{ id: "draw", value: "SELECT 2", mode: "draw" },
],
},
}

const cells = sanitizeBuffer(input).notebookViewState?.cells
expect(cells?.[0].mode).toBeUndefined()
expect(cells?.[1].mode).toBe("draw")
})

it("clamps imported pane heights to the floors and ceiling the UI enforces", () => {
// Given a hand-authored file pinning out-of-range heights
const input = {
label: "Notebook",
value: "",
position: 0,
notebookViewState: {
cells: [
{
id: "sql",
value: "SELECT 1",
topHeight: 0,
topResized: true,
bottomHeight: -500,
bottomResized: true,
},
{ id: "chart", value: "SELECT 1", mode: "draw", bottomHeight: 100 },
{ id: "md", value: "# t", type: "markdown", topHeight: 10 },
{ id: "huge", value: "SELECT 1", topHeight: 99999 },
{ id: "ok", value: "SELECT 1", topHeight: 300, bottomHeight: 250 },
],
},
}
// When the buffer is sanitized
const cells = sanitizeBuffer(input).notebookViewState?.cells
// Then every height lands inside its pane's floor and ceiling, and
// in-range values pass through untouched
expect(cells?.[0]).toMatchObject({ topHeight: 72, bottomHeight: 100 })
expect(cells?.[1].bottomHeight).toBe(296)
expect(cells?.[2].topHeight).toBe(56)
expect(cells?.[3].topHeight).toBe(2400)
expect(cells?.[4]).toMatchObject({ topHeight: 300, bottomHeight: 250 })
})

it("whitelists cell fields, reindexes positions, drops session state", () => {
const input = {
label: "Notebook",
Expand Down
44 changes: 34 additions & 10 deletions src/scenes/Editor/Monaco/importTabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ import type {
NotebookViewState,
} from "../../../store/notebook"
import type { ChartConfig, QueryChart } from "../Notebook/CellChart/chartTypes"
import { isAutoRefresh } from "../Notebook/notebookUtils"
import {
clampPaneHeight,
isAutoRefresh,
minBottomHeightFor,
minTopHeightFor,
} from "../Notebook/notebookUtils"
import { LINE_NUMBER_HARD_LIMIT } from "./index"
import {
isCellPaneView,
MAX_NOTEBOOK_CELLS,
MAX_CELL_LINES,
MAX_CELL_NAME_LENGTH,
Expand Down Expand Up @@ -267,15 +273,33 @@ const sanitizeNotebookCell = (
// Whitelist the kind so a hand-crafted import can't smuggle a bogus type
// (anything other than "markdown" collapses to the SQL default).
if (item.type === "markdown") cell.type = "markdown"
if (item.mode === "run" || item.mode === "draw") cell.mode = item.mode
const chartConfig = sanitizeChartConfig(item.chartConfig)
if (chartConfig) cell.chartConfig = chartConfig
if (isAutoRefresh(item.autoRefresh)) cell.autoRefresh = item.autoRefresh
if (typeof item.isViewMaximized === "boolean")
cell.isViewMaximized = item.isViewMaximized
if (typeof item.topHeight === "number") cell.topHeight = item.topHeight
if (typeof item.bottomHeight === "number")
cell.bottomHeight = item.bottomHeight
// Markdown cells carry no run/draw sub-state — gating it here keeps a
// hand-crafted import from producing a cell apply_notebook_state rejects.
if (item.type !== "markdown") {
if (item.mode === "draw") cell.mode = "draw"
const chartConfig = sanitizeChartConfig(item.chartConfig)
if (chartConfig) cell.chartConfig = chartConfig
if (isAutoRefresh(item.autoRefresh)) cell.autoRefresh = item.autoRefresh
if (isCellPaneView(item.paneView)) {
cell.paneView = item.paneView
} else if (item.isViewMaximized === true) {
cell.paneView = "result"
} else {
cell.paneView = "editor_result"
}
}
// Clamp to the same pane floors and ceiling every UI writer enforces, so a
// hand-authored file can't pin an invisible editor or an overlapping pane.
if (typeof item.topHeight === "number" && Number.isFinite(item.topHeight))
cell.topHeight = clampPaneHeight(minTopHeightFor(cell), item.topHeight)
if (
typeof item.bottomHeight === "number" &&
Number.isFinite(item.bottomHeight)
)
cell.bottomHeight = clampPaneHeight(
minBottomHeightFor(cell),
item.bottomHeight,
)
if (typeof item.topResized === "boolean") cell.topResized = item.topResized
if (typeof item.bottomResized === "boolean")
cell.bottomResized = item.bottomResized
Expand Down
Loading
Loading