1 parent
493e18755d
commit
d616e6b1a8
3 files changed
+178
-8
No files matched your search
+117
-1
@@ -1,5 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { dataGridConditionColumnOptions, dataGridConditionIdentifierQuote } from "@/lib/dataGrid/dataGridConditionCompletion";
|
||||
import { dataGridConditionColumnOptions, dataGridConditionIdentifierQuote, dataGridFilterColumns } from "@/lib/dataGrid/dataGridConditionCompletion";
|
||||
|
||||
function column(name: string, dataType: string) {
|
||||
return {
|
||||
name,
|
||||
data_type: dataType,
|
||||
is_nullable: true,
|
||||
column_default: null,
|
||||
is_primary_key: false,
|
||||
extra: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe("dataGridConditionColumnOptions", () => {
|
||||
it("reuses PostgreSQL completion quoting while preserving display metadata", () => {
|
||||
@@ -26,3 +37,108 @@ describe("dataGridConditionColumnOptions", () => {
|
||||
expect(dataGridConditionIdentifierQuote("kingbase", "`")).toBe("`");
|
||||
});
|
||||
});
|
||||
|
||||
describe("dataGridFilterColumns", () => {
|
||||
it.each(["ms", "us", "ns"])("adds metadata-less IoTDB Tree Time for %s precision", (precision) => {
|
||||
const value = column("value", "DOUBLE");
|
||||
|
||||
expect(
|
||||
dataGridFilterColumns({
|
||||
databaseType: "iotdb",
|
||||
context: "table-data",
|
||||
urlParams: "time_zone=Asia%2FShanghai",
|
||||
tableColumns: [value],
|
||||
resultColumns: ["Time", "value"],
|
||||
resultColumnTypes: [`TIMESTAMP(${precision})`, "DOUBLE"],
|
||||
}),
|
||||
).toEqual([{ name: "Time" }, { name: "value", columnInfo: value }]);
|
||||
});
|
||||
|
||||
it("lets URL params override the same connection-string dialect key", () => {
|
||||
const value = column("value", "DOUBLE");
|
||||
|
||||
expect(
|
||||
dataGridFilterColumns({
|
||||
databaseType: "iotdb",
|
||||
context: "table-data",
|
||||
connectionString: "jdbc:iotdb://localhost:6667/root.db?sql_dialect=table",
|
||||
urlParams: "sql_dialect=tree",
|
||||
tableColumns: [value],
|
||||
resultColumns: ["Time", "value"],
|
||||
resultColumnTypes: ["TIMESTAMP(ms)", "DOUBLE"],
|
||||
}),
|
||||
).toEqual([{ name: "Time" }, { name: "value", columnInfo: value }]);
|
||||
});
|
||||
|
||||
it("keeps explicit Tree Time metadata exactly once", () => {
|
||||
const time = column("Time", "TIMESTAMP");
|
||||
const value = column("value", "DOUBLE");
|
||||
|
||||
expect(
|
||||
dataGridFilterColumns({
|
||||
databaseType: "iotdb",
|
||||
context: "table-data",
|
||||
urlParams: "sql_dialect=tree",
|
||||
tableColumns: [time, value],
|
||||
resultColumns: ["Time", "value"],
|
||||
resultColumnTypes: ["TIMESTAMP(ms)", "DOUBLE"],
|
||||
}),
|
||||
).toEqual([
|
||||
{ name: "Time", columnInfo: time },
|
||||
{ name: "value", columnInfo: value },
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([{ urlParams: "sql_dialect=table" }, { urlParams: "DIALECT=TABLE" }, { connectionString: "jdbc:iotdb://localhost:6667/root.db?sql_dialect=table" }] as const)("does not synthesize Time for IoTDB Table dialect ($urlParams$connectionString)", (connection) => {
|
||||
const value = column("value", "DOUBLE");
|
||||
|
||||
expect(
|
||||
dataGridFilterColumns({
|
||||
databaseType: "iotdb",
|
||||
context: "table-data",
|
||||
...connection,
|
||||
tableColumns: [value],
|
||||
resultColumns: ["Time", "value"],
|
||||
resultColumnTypes: ["TIMESTAMP(ms)", "DOUBLE"],
|
||||
}),
|
||||
).toEqual([{ name: "value", columnInfo: value }]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ name: "query results", context: "results", databaseType: "iotdb", resultColumns: ["Time", "value"], resultColumnTypes: ["TIMESTAMP(ms)", "DOUBLE"] },
|
||||
{ name: "non-IoTDB", context: "table-data", databaseType: "mysql", resultColumns: ["Time", "value"], resultColumnTypes: ["TIMESTAMP(ms)", "DOUBLE"] },
|
||||
{ name: "non-leading Time", context: "table-data", databaseType: "iotdb", resultColumns: ["value", "Time"], resultColumnTypes: ["DOUBLE", "TIMESTAMP(ms)"] },
|
||||
{ name: "non-canonical time", context: "table-data", databaseType: "iotdb", resultColumns: ["time", "value"], resultColumnTypes: ["TIMESTAMP(ms)", "DOUBLE"] },
|
||||
{ name: "unqualified timestamp", context: "table-data", databaseType: "iotdb", resultColumns: ["Time", "value"], resultColumnTypes: ["TIMESTAMP", "DOUBLE"] },
|
||||
] as const)("keeps physical candidates unchanged for $name", ({ context, databaseType, resultColumns, resultColumnTypes }) => {
|
||||
const value = column("value", "DOUBLE");
|
||||
|
||||
expect(
|
||||
dataGridFilterColumns({
|
||||
databaseType,
|
||||
context,
|
||||
tableColumns: [value],
|
||||
resultColumns,
|
||||
resultColumnTypes,
|
||||
}),
|
||||
).toEqual([{ name: "value", columnInfo: value }]);
|
||||
});
|
||||
|
||||
it("does not duplicate a case-variant metadata Time column", () => {
|
||||
const time = column("time", "TIMESTAMP");
|
||||
const value = column("value", "DOUBLE");
|
||||
|
||||
expect(
|
||||
dataGridFilterColumns({
|
||||
databaseType: "iotdb",
|
||||
context: "table-data",
|
||||
tableColumns: [time, value],
|
||||
resultColumns: ["Time", "value"],
|
||||
resultColumnTypes: ["TIMESTAMP(ns)", "DOUBLE"],
|
||||
}),
|
||||
).toEqual([
|
||||
{ name: "time", columnInfo: time },
|
||||
{ name: "value", columnInfo: value },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,51 @@ import type { DataGridConditionColumnOption } from "@/composables/useDataGridCon
|
||||
import { codeMirrorSqlDialect } from "@/lib/database/jdbcDialect";
|
||||
import { quoteSqlIdentifier } from "@/lib/sql/sqlCompletion";
|
||||
import { sqlSemanticDialectFor } from "@/lib/sql/semantic/dialect";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
import type { ColumnInfo, DatabaseType } from "@/types/database";
|
||||
|
||||
export interface DataGridFilterColumn {
|
||||
name: string;
|
||||
columnInfo?: ColumnInfo;
|
||||
}
|
||||
|
||||
export interface DataGridFilterColumnsOptions {
|
||||
databaseType?: DatabaseType;
|
||||
context?: "results" | "table-data";
|
||||
urlParams?: string;
|
||||
connectionString?: string;
|
||||
tableColumns: readonly ColumnInfo[];
|
||||
resultColumns: readonly string[];
|
||||
resultColumnTypes?: readonly (string | null | undefined)[];
|
||||
}
|
||||
|
||||
export function dataGridFilterColumns(options: DataGridFilterColumnsOptions): DataGridFilterColumn[] {
|
||||
const physicalColumns = options.tableColumns.map((columnInfo) => ({ name: columnInfo.name, columnInfo }));
|
||||
if (!usesImplicitIoTDBTreeTime(options)) return physicalColumns;
|
||||
return [{ name: "Time" }, ...physicalColumns];
|
||||
}
|
||||
|
||||
function usesImplicitIoTDBTreeTime(options: DataGridFilterColumnsOptions): boolean {
|
||||
return (
|
||||
options.databaseType === "iotdb" &&
|
||||
options.context === "table-data" &&
|
||||
iotdbSqlDialect(options.urlParams, options.connectionString) !== "table" &&
|
||||
options.resultColumns[0] === "Time" &&
|
||||
/^TIMESTAMP\((ms|us|ns)\)$/i.test(options.resultColumnTypes?.[0]?.trim() ?? "") &&
|
||||
!options.tableColumns.some((column) => column.name.toLowerCase() === "time")
|
||||
);
|
||||
}
|
||||
|
||||
function iotdbSqlDialect(urlParams: string | undefined, connectionString: string | undefined): string | undefined {
|
||||
const merged = new Map<string, string>();
|
||||
addIoTDBConnectionParams(merged, connectionString?.split("?", 2)[1]?.split("#", 1)[0]);
|
||||
addIoTDBConnectionParams(merged, urlParams);
|
||||
return (merged.get("sql_dialect") || merged.get("dialect"))?.trim().toLowerCase();
|
||||
}
|
||||
|
||||
function addIoTDBConnectionParams(target: Map<string, string>, raw: string | undefined) {
|
||||
const params = new URLSearchParams((raw ?? "").trim().replace(/^\?/, ""));
|
||||
for (const [key, value] of params) target.set(key.toLowerCase(), value);
|
||||
}
|
||||
|
||||
export function dataGridConditionColumnOptions(columns: readonly DataGridConditionColumnOption[], databaseType?: DatabaseType): DataGridConditionColumnOption[] {
|
||||
const dialect = codeMirrorSqlDialect(databaseType);
|
||||
|
||||
Reference in new issue
Block a user