Skip to content

Commit bcd86dc

Browse files
mkfreemanAnnie Zhang
andauthored
Add missing filter options (#331)
* Add empty string and NaN checks to null filter * Add filters for defined and not defined * Only check that the first value is a primitive type * Add valid and not valid filters * Add filters for dates * Add isValid function and export it * Pass the correct value to isValid * Remove unused nd and d filters * Remove prettierignore file * Revert "Only check that the first value is a primitive type" This reverts commit b701de1. * Remove old comment * Extract validators into separate functions to avoid redundant checks * Rename getValidator getTypeValidator * Add getValidator tests * Create null/not null filters for valid/invalid column values * Don't repeatedly re-define source Co-authored-by: Annie Zhang <[email protected]> Co-authored-by: Annie Zhang <[email protected]>
1 parent b7e5313 commit bcd86dc

File tree

3 files changed

+144
-3
lines changed

3 files changed

+144
-3
lines changed

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ export {
88
arrayIsPrimitive,
99
isDataArray,
1010
isDatabaseClient,
11-
__table as applyDataTableOperations
11+
__table as applyDataTableOperations,
12+
getTypeValidator
1213
} from "./table.js";

src/table.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ function appendWhereEntry({type, operands}, args, escaper) {
398398
if (operands.length === 2) {
399399
if (["in", "nin"].includes(type)) {
400400
// Fallthrough to next parent block.
401-
} else if (["c", "nc"].includes(type)) {
401+
} else if (["c", "nc", "v", "nv"].includes(type)) {
402402
// TODO: Case (in)sensitive?
403403
appendOperand(operands[0], args, escaper);
404404
switch (type) {
@@ -408,6 +408,14 @@ function appendWhereEntry({type, operands}, args, escaper) {
408408
case "nc":
409409
appendSql(` NOT LIKE `, args);
410410
break;
411+
// JavaScript "not valid" filter translate to a SQL "IS NULL"
412+
case "nv":
413+
appendSql(` IS NULL`, args);
414+
break;
415+
// JavaScript "valid" filter translate to a SQL "IS NOT NULL"
416+
case "v":
417+
appendSql(` IS NOT NULL`, args);
418+
break;
411419
}
412420
appendOperand(likeOperand(operands[1]), args, escaper);
413421
return;
@@ -480,6 +488,42 @@ function likeOperand(operand) {
480488
return {...operand, value: `%${operand.value}%`};
481489
}
482490

491+
// Functions for checking type validity
492+
const isValidNumber = (value) => typeof value === "number" && !Number.isNaN(value);
493+
const isValidString = (value) => typeof value === "string";
494+
const isValidBoolean = (value) => typeof value === "boolean";
495+
const isValidBigint = (value) => typeof value === "bigint";
496+
const isValidDate = (value) => value instanceof Date && !isNaN(value);
497+
const isValidBuffer = (value) => value instanceof ArrayBuffer;
498+
const isValidArray = (value) => Array.isArray(value);
499+
const isValidObject = (value) => typeof value === "object" && value !== null;
500+
const isValidOther = (value) => value != null;
501+
502+
// Function to get the correct validity checking function based on type
503+
export function getTypeValidator(colType) {
504+
switch (colType) {
505+
case "string":
506+
return isValidString;
507+
case "bigint":
508+
return isValidBigint;
509+
case "boolean":
510+
return isValidBoolean;
511+
case "number":
512+
return isValidNumber;
513+
case "date":
514+
return isValidDate;
515+
case "buffer":
516+
return isValidBuffer;
517+
case "array":
518+
return isValidArray;
519+
case "object":
520+
return isValidObject;
521+
case "other":
522+
default:
523+
return isValidOther;
524+
}
525+
}
526+
483527
// This function applies table cell operations to an in-memory table (array of
484528
// objects); it should be equivalent to the corresponding SQL query. TODO Use
485529
// DuckDBClient for data arrays, too, and then we wouldn’t need our own __table
@@ -493,6 +537,20 @@ export function __table(source, operations) {
493537
const [{value: column}] = operands;
494538
const values = operands.slice(1).map(({value}) => value);
495539
switch (type) {
540+
// valid (matches the column type)
541+
case "v": {
542+
const [colType] = values;
543+
const isValid = getTypeValidator(colType);
544+
source = source.filter(d => isValid(d[column]));
545+
break;
546+
}
547+
// not valid (doesn't match the column type)
548+
case "nv": {
549+
const [colType] = values;
550+
const isValid = getTypeValidator(colType);
551+
source = source.filter(d => !isValid(d[column]));
552+
break;
553+
}
496554
case "eq": {
497555
const [value] = values;
498556
if (value instanceof Date) {

test/table-test.js

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {makeQueryTemplate, __table} from "../src/table.js";
1+
import {getTypeValidator, makeQueryTemplate, __table} from "../src/table.js";
22
import assert from "assert";
33

44
export const EMPTY_TABLE_DATA = {
@@ -542,3 +542,85 @@ describe("__table", () => {
542542
);
543543
});
544544
});
545+
546+
describe("getTypeValidator filters accurately", () => {
547+
let source = [
548+
{label: "string", value: "string"},
549+
{label: "object", value: {}},
550+
{label: "buffer", value: new ArrayBuffer()},
551+
{label: "boolean", value: true},
552+
{label: "array", value: [1, 2, 3]},
553+
{label: "number", value: 10},
554+
{label: "date", value: new Date(1)},
555+
// eslint-disable-next-line no-undef
556+
{label: "bigint", value: BigInt(10)},
557+
{label: "null", value: null},
558+
{label: "NaN", value: NaN},
559+
{label: "undefined"}
560+
];
561+
562+
it("filters strings", () => {
563+
const isValid = getTypeValidator("string");
564+
assert.deepStrictEqual(source.filter(d => isValid(d.value)), [{label: "string", value: "string"}]);
565+
});
566+
567+
it("filters buffers", () => {
568+
const isValid = getTypeValidator("buffer");
569+
assert.deepStrictEqual(source.filter(d => isValid(d.value)), [{label: "buffer", value: new ArrayBuffer()}]);
570+
});
571+
572+
it("filters numbers", () => {
573+
const isValid = getTypeValidator("number");
574+
assert.deepStrictEqual(source.filter(d => isValid(d.value)), [{label: "number", value: 10}]);
575+
});
576+
577+
it("filters booleans", () => {
578+
const isValid = getTypeValidator("boolean");
579+
assert.deepStrictEqual(source.filter(d => isValid(d.value)), [{label: "boolean", value: true}]);
580+
});
581+
582+
it("filters arrays", () => {
583+
const isValid = getTypeValidator("array");
584+
assert.deepStrictEqual(source.filter(d => isValid(d.value)), [{label: "array", value: [1, 2, 3]}]);
585+
});
586+
587+
it("filters dates", () => {
588+
const isValid = getTypeValidator("date");
589+
assert.deepStrictEqual(source.filter(d => isValid(d.value)), [{label: "date", value: new Date(1)}]);
590+
});
591+
592+
it("filters BigInts", () => {
593+
const isValid = getTypeValidator("bigint");
594+
// eslint-disable-next-line no-undef
595+
assert.deepStrictEqual(source.filter(d => isValid(d.value)), [{label: "bigint", value: BigInt(10)}]);
596+
});
597+
598+
it("filters objects", () => {
599+
const isValid = getTypeValidator("object");
600+
assert.deepStrictEqual(source.filter(d => isValid(d.value)),
601+
[
602+
{label: "object", value: {}},
603+
{label: "buffer", value: new ArrayBuffer()},
604+
{label: "array", value: [1, 2, 3]},
605+
{label: "date", value: new Date(1)}]
606+
);
607+
});
608+
609+
it("filters other", () => {
610+
const isValid = getTypeValidator("other");
611+
assert.deepStrictEqual(source.filter(d => isValid(d.value)),
612+
[
613+
{label: "string", value: "string"},
614+
{label: "object", value: {}},
615+
{label: "buffer", value: new ArrayBuffer()},
616+
{label: "boolean", value: true},
617+
{label: "array", value: [1, 2, 3]},
618+
{label: "number", value: 10},
619+
{label: "date", value: new Date(1)},
620+
// eslint-disable-next-line no-undef
621+
{label: "bigint", value: BigInt(10)},
622+
{label: "NaN", value: NaN}
623+
]
624+
);
625+
});
626+
});

0 commit comments

Comments
 (0)