feat(grid): add list and range filters
This commit is contained in:
1 parent
8710bc9069
commit
66d7b965dc
12 files changed
+802
-44
No files matched your search
@@ -50,18 +50,43 @@ export function filterModeNeedsValue(mode: DataGridContextFilterMode): boolean {
|
||||
return mode !== "is-null" && mode !== "is-not-null";
|
||||
}
|
||||
|
||||
export function parseFilterValue(rawValue: string, columnInfo?: Pick<DataGridColumnInfo, "data_type">): GridCellValue {
|
||||
export function filterModeUsesList(mode: DataGridContextFilterMode): boolean {
|
||||
return mode === "in" || mode === "not-in";
|
||||
}
|
||||
|
||||
export function filterModeUsesRange(mode: DataGridContextFilterMode): boolean {
|
||||
return mode === "between" || mode === "not-between";
|
||||
}
|
||||
|
||||
export function filterModeIsSupportedForDatabase(mode: DataGridContextFilterMode, databaseType?: DatabaseType): boolean {
|
||||
if (!filterModeUsesList(mode) && !filterModeUsesRange(mode)) return true;
|
||||
// These targets do not support all four new SQL predicates reliably.
|
||||
return databaseType !== "cassandra" && databaseType !== "influxdb" && databaseType !== "jdbc";
|
||||
}
|
||||
|
||||
export function filterModeHasCompleteValue(mode: DataGridContextFilterMode, rawValue: string, rawEndValue = ""): boolean {
|
||||
if (!filterModeNeedsValue(mode)) return true;
|
||||
if (filterModeUsesList(mode)) return parseFilterValues(rawValue).length > 0;
|
||||
if (filterModeUsesRange(mode)) return rawValue.trim().length > 0 && rawEndValue.trim().length > 0;
|
||||
return rawValue.trim().length > 0;
|
||||
}
|
||||
|
||||
export function parseFilterValue(rawValue: string, columnInfo?: Pick<DataGridColumnInfo, "data_type">, databaseType?: DatabaseType): GridCellValue {
|
||||
const unquoted = unwrapMatchingQuotes(rawValue.trim());
|
||||
const dataType = (columnInfo?.data_type ?? "").toLowerCase();
|
||||
|
||||
if (isBooleanType(dataType) && unquoted.toLowerCase() === "true") return true;
|
||||
if (isBooleanType(dataType) && unquoted.toLowerCase() === "false") return false;
|
||||
if (isBooleanType(dataType, databaseType) && unquoted.toLowerCase() === "true") return true;
|
||||
if (isBooleanType(dataType, databaseType) && unquoted.toLowerCase() === "false") return false;
|
||||
|
||||
if ((isNumericType(dataType) || !dataType) && isNumericLiteral(unquoted)) {
|
||||
if (isNumericType(dataType) && isNumericLiteral(unquoted)) {
|
||||
// Preserve the original decimal/integer spelling so the backend can emit it exactly.
|
||||
return unquoted;
|
||||
}
|
||||
|
||||
if (!dataType && isNumericLiteral(unquoted)) {
|
||||
const numeric = Number(unquoted);
|
||||
if (Number.isFinite(numeric)) {
|
||||
// Keep large integers as strings to avoid JS precision loss (> Number.MAX_SAFE_INTEGER).
|
||||
// The Rust backend parses strings exactly via serde_json::Number, so this is safe.
|
||||
if (Number.isInteger(numeric) && Math.abs(numeric) > Number.MAX_SAFE_INTEGER) {
|
||||
return unquoted;
|
||||
}
|
||||
@@ -72,6 +97,73 @@ export function parseFilterValue(rawValue: string, columnInfo?: Pick<DataGridCol
|
||||
return unquoted;
|
||||
}
|
||||
|
||||
export function parseFilterValues(rawValue: string, columnInfo?: Pick<DataGridColumnInfo, "data_type">, databaseType?: DatabaseType): GridCellValue[] {
|
||||
return splitFilterValues(rawValue).map(({ value, quoted }) => {
|
||||
if (!quoted && value.toLowerCase() === "null") return null;
|
||||
return parseFilterValue(value, columnInfo, databaseType);
|
||||
});
|
||||
}
|
||||
|
||||
type ParsedFilterValue = {
|
||||
value: string;
|
||||
quoted: boolean;
|
||||
};
|
||||
|
||||
function splitFilterValues(source: string): ParsedFilterValue[] {
|
||||
const tokens: string[] = [];
|
||||
let current = "";
|
||||
let quote: "'" | '"' | null = null;
|
||||
|
||||
for (let index = 0; index < source.length; index += 1) {
|
||||
const char = source[index];
|
||||
const next = source[index + 1];
|
||||
|
||||
if (quote) {
|
||||
current += char;
|
||||
if (char === quote) {
|
||||
if (next === quote) {
|
||||
current += next;
|
||||
index += 1;
|
||||
} else {
|
||||
quote = null;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((char === "'" || char === '"') && current.trim().length === 0) {
|
||||
quote = char;
|
||||
current += char;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "," || char === "\n" || char === "\r") {
|
||||
tokens.push(current);
|
||||
current = "";
|
||||
continue;
|
||||
}
|
||||
|
||||
current += char;
|
||||
}
|
||||
tokens.push(current);
|
||||
|
||||
return tokens.map(normalizeFilterValue).filter((value): value is ParsedFilterValue => !!value);
|
||||
}
|
||||
|
||||
function normalizeFilterValue(token: string): ParsedFilterValue | null {
|
||||
const text = token.trim();
|
||||
if (!text) return null;
|
||||
|
||||
const first = text[0];
|
||||
const last = text[text.length - 1];
|
||||
const quoted = text.length >= 2 && ((first === "'" && last === "'") || (first === '"' && last === '"'));
|
||||
if (!quoted) return { value: text, quoted: false };
|
||||
|
||||
const quote = first as "'" | '"';
|
||||
const value = text.slice(1, -1).replaceAll(`${quote}${quote}`, quote);
|
||||
return { value, quoted: true };
|
||||
}
|
||||
|
||||
function unwrapMatchingQuotes(text: string): string {
|
||||
if (text.length >= 2) {
|
||||
const first = text[0];
|
||||
@@ -87,8 +179,8 @@ function isNumericType(dataType: string): boolean {
|
||||
return ["int", "integer", "bigint", "smallint", "tinyint", "mediumint", "serial", "number", "numeric", "decimal", "float", "double", "real", "money"].some((part) => dataType.split(/[^a-z0-9]+/).includes(part));
|
||||
}
|
||||
|
||||
function isBooleanType(dataType: string): boolean {
|
||||
return dataType.split(/[^a-z0-9]+/).some((part) => part === "bool" || part === "boolean" || part === "bit");
|
||||
function isBooleanType(dataType: string, databaseType?: DatabaseType): boolean {
|
||||
return dataType.split(/[^a-z0-9]+/).some((part) => part === "bool" || part === "boolean" || (part === "bit" && databaseType !== "postgres"));
|
||||
}
|
||||
|
||||
function isNumericLiteral(text: string): boolean {
|
||||
|
||||
@@ -52,13 +52,15 @@ export interface DataGridCopyInsertStatementOptions {
|
||||
insertMode?: DataGridCopyInsertMode;
|
||||
}
|
||||
|
||||
export type DataGridContextFilterMode = "equals" | "not-equals" | "is-null" | "is-not-null" | "like" | "not-like" | "less-than" | "greater-than";
|
||||
export type DataGridContextFilterMode = "equals" | "not-equals" | "is-null" | "is-not-null" | "like" | "not-like" | "less-than" | "greater-than" | "in" | "not-in" | "between" | "not-between";
|
||||
|
||||
export interface DataGridContextFilterConditionOptions {
|
||||
databaseType?: DatabaseType;
|
||||
columnName: string;
|
||||
mode: DataGridContextFilterMode;
|
||||
value: GridCellValue;
|
||||
values?: GridCellValue[];
|
||||
endValue?: GridCellValue;
|
||||
columnInfo?: DataGridColumnInfo;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user