Skip to content
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
8 changes: 8 additions & 0 deletions packages/browser-tests/cypress/fixtures/nanos.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"timestamp","symbol","bids","asks"
"2025-09-12T23:26:12.696868898Z","USDCHF","[[0.7938],[72902.0]]","[[0.7942],[75254.0]]"
"2025-09-12T23:26:12.698402234Z","USDNOK","[[9.8337,9.8336],[56453.0,1.16831898E8]]","[[9.8341,9.8342],[64603.0,3.51343759E8]]"
"2025-09-12T23:26:12.701577329Z","USDCHF","[[0.7938],[65290.0]]","[[0.7942],[57934.0]]"
"2025-09-12T23:26:12.704578561Z","USDZAR","[[17.364],[53529.0]]","[[17.3644],[90619.0]]"
"2025-09-12T23:26:12.704809736Z","AUDNZD","[[1.1168,1.1167],[63576.0,8.71920887E8]]","[[1.1172,1.1173],[66756.0,2.540549E7]]"
"2025-09-12T23:26:12.706899510Z","USDHKD","[[7.778,7.7779],[91917.0,2.04926212E8]]","[[7.7784,7.7785],[68413.0,5.64775853E8]]"
"2025-09-12T23:26:12.710846084Z","USDZAR","[[17.364,17.3639],[98866.0,6.3619799E8]]","[[17.3644,17.3645],[67646.0,8.68348153E8]]"
67 changes: 65 additions & 2 deletions packages/browser-tests/cypress/integration/console/import.spec.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,80 @@
/// <reference types="cypress" />

describe("questdb import", () => {
before(() => {
beforeEach(() => {
cy.loadConsoleWithAuth();
});

afterEach(() => {
cy.loadConsoleWithAuth();
cy.typeQueryDirectly("drop all tables;");
cy.clickRunIconInLine(1);
cy.getByDataHook("success-notification").should("be.visible");
cy.clearEditor();
});

it("display import panel", () => {
cy.getByDataHook("import-panel-button").click();
cy.getByDataHook("import-dropbox").should("be.visible");
cy.getByDataHook("import-browse-from-disk").should("be.visible");

cy.get('input[type="file"]').selectFile("cypress/fixtures/test.csv", { force: true });
cy.get('input[type="file"]').selectFile("cypress/fixtures/test.csv", {
force: true,
});
cy.getByDataHook("import-table-column-schema").should("be.visible");
cy.getByDataHook("import-table-column-owner").should("not.exist");
});

it("should import csv with a nanosecond timestamp", () => {
cy.getByDataHook("import-panel-button").click();
cy.getByDataHook("import-dropbox").should("be.visible");
cy.getByDataHook("import-browse-from-disk").should("be.visible");

cy.get('input[type="file"]').selectFile("cypress/fixtures/nanos.csv", {
force: true,
});
cy.getByDataHook("import-table-column-schema").should("be.visible");
cy.getByDataHook("import-upload-button").should("be.enabled");
cy.getByDataHook("import-upload-button").click();

cy.getByDataHook("import-file-status").should("contain", "Imported 7 rows");
cy.getByDataHook("schema-table-title").should("contain", "nanos.csv");

cy.getByDataHook("schema-table-title").dblclick();
cy.getByDataHook("schema-folder-title").contains("Columns").dblclick();
cy.get('[data-id="questdb:expanded:tables:nanos.csv:columns:timestamp"]')
.should("be.visible")
.should("contain", "timestamp")
.should("contain", "TIMESTAMP_NS");
cy.getByDataHook("designated-timestamp-icon").should("not.exist");

cy.getByDataHook("table-schema-dialog-trigger")
.should("be.visible")
.should("contain", "4 cols");
cy.getByDataHook("table-schema-dialog-trigger").click();

cy.getByDataHook("create-table-panel").should("be.visible");
cy.getByDataHook("table-schema-dialog-column-0").should("be.visible");
cy.get("input[name='schemaColumns.0.name']").should(
"have.value",
"timestamp"
);

cy.get("select[name='schemaColumns.0.type']")
.get("option[value='TIMESTAMP_NS']")
.should("be.selected");

cy.getByDataHook("table-schema-dialog-column-0-designated-button").click();
cy.getByDataHook("form-submit-button").click();

cy.getByDataHook("create-table-panel").should("not.be.visible");

cy.get('select[name="overwrite"]').select("true");

cy.getByDataHook("import-upload-button").should("be.enabled");
cy.getByDataHook("import-upload-button").click();

cy.getByDataHook("import-file-status").should("contain", "Imported 7 rows");
cy.getByDataHook("designated-timestamp-icon").should("be.visible");
});
});
2 changes: 1 addition & 1 deletion packages/browser-tests/questdb
Submodule questdb updated 41 files
+142 −0 .github/workflows/rebuild_async_profiler.yml
+4 −0 .gitmodules
+19 −0 ci/docker-release-pipeline.yml
+77 −2 core/docker-entrypoint.sh
+20 −0 core/src/main/assembly/rt-unix.xml
+ core/src/main/bin/linux-x86-64/asprof
+ core/src/main/bin/linux-x86-64/jfrconv
+ core/src/main/bin/linux-x86-64/libasyncProfiler.so
+234 −18 core/src/main/bin/questdb.sh
+1 −0 core/src/main/c/share/async-profiler
+6 −0 core/src/main/java/io/questdb/cairo/CairoConfiguration.java
+2 −2 core/src/main/java/io/questdb/cairo/CairoEngine.java
+1 −1 core/src/main/java/io/questdb/cairo/SymbolMapUtil.java
+0 −3 core/src/main/java/io/questdb/cairo/TableReader.java
+10 −0 core/src/main/java/io/questdb/cairo/TableUtils.java
+1 −1 core/src/main/java/io/questdb/cairo/TableWriter.java
+14 −3 core/src/main/java/io/questdb/cairo/TableWriterSegmentFileCache.java
+1 −1 core/src/main/java/io/questdb/cairo/mv/MatViewRefreshJob.java
+3 −2 core/src/main/java/io/questdb/cairo/mv/WalTxnRangeLoader.java
+1 −1 core/src/main/java/io/questdb/cairo/pool/WriterPool.java
+11 −1 core/src/main/java/io/questdb/cairo/vm/MemoryCMORImpl.java
+12 −2 core/src/main/java/io/questdb/cairo/vm/MemoryCMRImpl.java
+6 −2 core/src/main/java/io/questdb/cairo/vm/Vm.java
+1 −1 core/src/main/java/io/questdb/cairo/wal/ApplyWal2TableJob.java
+5 −4 core/src/main/java/io/questdb/cairo/wal/WalEventReader.java
+2 −2 core/src/main/java/io/questdb/cairo/wal/WalReader.java
+2 −3 core/src/main/java/io/questdb/cairo/wal/WalTxnDetails.java
+1 −1 core/src/main/java/io/questdb/cairo/wal/WalUtils.java
+7 −6 core/src/main/java/io/questdb/cairo/wal/seq/SequencerMetadata.java
+1 −1 core/src/main/java/io/questdb/cairo/wal/seq/TableSequencerImpl.java
+7 −6 core/src/main/java/io/questdb/cairo/wal/seq/TableTransactionLog.java
+10 −8 core/src/main/java/io/questdb/cairo/wal/seq/TableTransactionLogV1.java
+1 −1 core/src/main/java/io/questdb/cutlass/pgwire/PGCleartextPasswordAuthenticator.java
+3 −1 core/src/test/java/io/questdb/test/cairo/fuzz/AbstractFuzzTest.java
+12 −3 core/src/test/java/io/questdb/test/cairo/fuzz/DedupInsertFuzzTest.java
+1 −1 core/src/test/java/io/questdb/test/cairo/mv/MatViewTest.java
+33 −1 core/src/test/java/io/questdb/test/cairo/o3/AbstractO3Test.java
+55 −0 core/src/test/java/io/questdb/test/cairo/pool/WriterPoolTest.java
+61 −4 core/src/test/java/io/questdb/test/cairo/wal/WalWriterTest.java
+16 −18 core/src/test/java/io/questdb/test/cutlass/http/HttpServerTest.java
+1 −0 core/src/test/java/io/questdb/test/mp/TestWorkerPool.java
3 changes: 2 additions & 1 deletion packages/web-console/serve-dist.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const server = http.createServer((req, res) => {
reqPathName.startsWith("/exec") ||
reqPathName.startsWith("/settings") ||
reqPathName.startsWith("/warnings") ||
reqPathName.startsWith("/chk")
reqPathName.startsWith("/chk") ||
reqPathName.startsWith("/imp")
) {
// proxy /exec requests to localhost:9000
const options = {
Expand Down
13 changes: 9 additions & 4 deletions packages/web-console/src/components/TableSchemaDialog/column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { Form } from "../../components/Form"
import {IconWithTooltip, Link, Text} from "../../components"
import { Box } from "../../components/Box"
import { Button } from "@questdb/react-components"
import { DEFAULT_TIMESTAMP_FORMAT } from "./const"
import { DEFAULT_TIMESTAMP_FORMAT, DEFAULT_TIMESTAMP_FORMAT_NS } from "../../scenes/Import/ImportCSVFiles/const"
import styled from "styled-components"
import { SchemaColumn } from "utils"
import { Controls } from "./controls"
import { Action } from "./types"
import { DocsLink } from "./docs-link"
import { isTimestamp } from "../../scenes/Import/ImportCSVFiles/utils"
import { getTimestampFormat } from "../../scenes/Import/ImportCSVFiles/utils"

const supportedColumnTypes: { label: string; value: string }[] = [
{ label: "AUTO", value: "" },
Expand All @@ -29,6 +31,7 @@ const supportedColumnTypes: { label: string; value: string }[] = [
{ label: "VARCHAR", value: "VARCHAR" },
{ label: "SYMBOL", value: "SYMBOL" },
{ label: "TIMESTAMP", value: "TIMESTAMP" },
{ label: "TIMESTAMP_NS", value: "TIMESTAMP_NS" },
{ label: "UUID", value: "UUID" },
]

Expand Down Expand Up @@ -85,6 +88,7 @@ export const Column = ({
key={column.name}
odd={index % 2 !== 0}
disabled={disabled}
data-hook={`table-schema-dialog-column-${index}`}
onFocus={() => onFocus(index)}
>
<Index>
Expand Down Expand Up @@ -117,18 +121,18 @@ export const Column = ({
</Form.Item>
</Controls>

{column.type === "TIMESTAMP" && (
{isTimestamp(column.type) && (
<Box flexDirection="column" gap="1rem">
<TimestampControls>
{action === "import" && (
<Form.Item name={`schemaColumns.${index}.pattern`}>
<Form.Input
name={`schemaColumns.${index}.pattern`}
placeholder="Pattern"
placeholder="Dataset pattern"
defaultValue={
column.pattern !== ""
? column.pattern
: DEFAULT_TIMESTAMP_FORMAT
: getTimestampFormat(column.type)
}
required
/>
Expand All @@ -139,6 +143,7 @@ export const Column = ({
icon={
<Button
disabled={name === ""}
data-hook={`table-schema-dialog-column-${index}-designated-button`}
skin={
timestamp !== "" &&
column.name !== "" &&
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Panel } from "../../components/Panel"
import { Actions } from "./actions"

const StyledContentWrapper = styled(Drawer.ContentWrapper)`
--columns: auto 120px; /* magic numbers to fit input, type dropdown and remove button nicely */
--columns: auto 140px; /* magic numbers to fit input, type dropdown and remove button nicely */
`

const Items = styled(Box).attrs({ gap: "0", flexDirection: "column" })`
Expand Down Expand Up @@ -185,6 +185,7 @@ export const Dialog = ({
trigger={
trigger ?? (
<Button
data-hook="table-schema-dialog-trigger"
skin={columnCount > 0 ? "transparent" : "secondary"}
prefixIcon={
columnCount > 0 ? <Edit size="18px" /> : <TableIcon size="18px" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const DEFAULT_TIMESTAMP_FORMAT = "yyyy-MM-ddTHH:mm:ss.SSSUUUz"
export const DEFAULT_TIMESTAMP_FORMAT_NS = "yyyy-MM-ddTHH:mm:ss.SSSUUUNNNz"
export const MAX_UNCOMMITTED_ROWS = 500000
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const mapStatusToLabel = (
export const FileStatus = ({ file }: { file: ProcessedFile }) => {
const mappedStatus = mapStatusToLabel(file)
return mappedStatus ? (
<Box gap="1rem" align="flex-start" flexDirection="column">
<Box gap="1rem" align="flex-start" flexDirection="column" data-hook="import-file-status">
<Badge type={mappedStatus.type}>
<StyledBox>
{mappedStatus.icon} {mappedStatus.label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { pick, UploadResult, FileCheckStatus, Parameter } from "../../../utils"
import * as QuestDB from "../../../utils/questdb"
import { useSelector } from "react-redux"
import { selectors } from "../../../store"
import { DEFAULT_TIMESTAMP_FORMAT, MAX_UNCOMMITTED_ROWS } from "./const"
import { getTimestampFormat, isTimestamp } from "./utils"
import { MAX_UNCOMMITTED_ROWS } from "./const"
import { useIsVisible } from "../../../components"
import {
extractPrecionFromGeohash,
Expand Down Expand Up @@ -123,7 +124,7 @@ export const ImportCSVFiles = ({ onViewData, onUpload }: Props) => {
return columnResponse.data.map((column) => ({
name: column.column,
type: mapColumnTypeToUI(column.type),
pattern: DEFAULT_TIMESTAMP_FORMAT,
pattern: getTimestampFormat(column.type),
precision: isGeoHash(column.type)
? extractPrecionFromGeohash(column.type)
: "",
Expand Down Expand Up @@ -273,10 +274,10 @@ export const ImportCSVFiles = ({ onViewData, onUpload }: Props) => {
...{
type: mapColumnTypeToUI(c.type),
pattern:
c.type === "TIMESTAMP"
isTimestamp(c.type)
? match?.pattern
? match?.pattern
: DEFAULT_TIMESTAMP_FORMAT
: getTimestampFormat(c.type)
: "",
precision: isGeoHash(c.type)
? extractPrecionFromGeohash(c.type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const UploadActions = ({
/>
<Button
disabled={file.isUploading}
data-hook="import-upload-button"
skin="primary"
prefixIcon={<Upload2 size="18px" />}
onClick={() => onUpload(file.id)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { SchemaColumn } from "components/TableSchemaDialog/types"
import { DEFAULT_TIMESTAMP_FORMAT, DEFAULT_TIMESTAMP_FORMAT_NS } from "./const"

export const isGeoHash = (type: string) => type.startsWith("GEOHASH")

const arrayRegex = /^[a-z][a-z0-9]*(\[\])+$/i

export const isArray = (type: string) => arrayRegex.test(type)

export const isTimestamp = (type: string) => ["TIMESTAMP", "TIMESTAMP_NS"].includes(type)

export const getTimestampFormat = (type: string) => type === "TIMESTAMP_NS" ? DEFAULT_TIMESTAMP_FORMAT_NS : DEFAULT_TIMESTAMP_FORMAT

export const extractPrecionFromGeohash = (geohash: string) => {
const regex = /\(([^)]+)\)/g
const matches = regex.exec(geohash)
Expand Down
4 changes: 2 additions & 2 deletions packages/web-console/src/scenes/Schema/Row/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ const TYPE_ICONS = {
icon: Tag
},
time: {
types: ["TIMESTAMP", "INTERVAL"],
types: ["TIMESTAMP", "INTERVAL", "TIMESTAMP_NS"],
icon: SortDown
},
network: {
Expand Down Expand Up @@ -255,7 +255,7 @@ const ColumnIcon = ({
if (isDesignatedTimestamp) {
return (
<IconWithTooltip
icon={<SortDownIcon size="14px" />}
icon={<SortDownIcon data-hook="designated-timestamp-icon" size="14px" />}
placement="top"
tooltip="Designated timestamp"
/>
Expand Down
3 changes: 2 additions & 1 deletion packages/web-console/src/utils/formatTableSchemaQuery.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { trim } from "ramda"
import { formatSql } from "../utils"
import { isTimestamp } from "../scenes/Import/ImportCSVFiles/utils"

type Column = {
column: string
Expand Down Expand Up @@ -35,7 +36,7 @@ export const formatTableSchemaQuery = ({
}: Props) => {
const hasValidTimestamp =
timestamp &&
schemaColumns.find((c) => c.column === timestamp && c.type === "TIMESTAMP")
schemaColumns.find((c) => c.column === timestamp && isTimestamp(c.type))

let query = `CREATE TABLE '${name}' (`

Expand Down