1863 lines
68 KiB
JavaScript
1863 lines
68 KiB
JavaScript
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
|
|
import { test, expect } from "@perspective-dev/test";
|
|
import perspective from "../perspective_client";
|
|
import * as common from "./common.js";
|
|
|
|
const NUMERIC_TYPES = [
|
|
"i8",
|
|
"ui8",
|
|
"i16",
|
|
"ui16",
|
|
"i32",
|
|
"ui32",
|
|
"i64",
|
|
"ui64",
|
|
"f32",
|
|
"f64",
|
|
];
|
|
|
|
/**
|
|
* Return all possible permutations of unary operators for all numeric types.
|
|
*/
|
|
function generate_unary_operations(operator) {
|
|
const expressions = [];
|
|
for (const expr of NUMERIC_TYPES) {
|
|
expressions.push(`${operator}"${expr}"`);
|
|
}
|
|
return expressions;
|
|
}
|
|
|
|
/**
|
|
* Return all possible permutations of binary operators for all numeric types.
|
|
*/
|
|
function generate_binary_operations(operator) {
|
|
const expressions = [];
|
|
for (const expr of NUMERIC_TYPES) {
|
|
for (let i = 0; i < NUMERIC_TYPES.length; i++) {
|
|
expressions.push(`"${expr}" ${operator} "${NUMERIC_TYPES[i]}"`);
|
|
}
|
|
}
|
|
return expressions;
|
|
}
|
|
|
|
function calc_unary(operator, value) {
|
|
switch (operator) {
|
|
case "+":
|
|
return +value;
|
|
case "-":
|
|
return -value;
|
|
default:
|
|
throw new Error("Unknown operator");
|
|
}
|
|
}
|
|
|
|
function calc_binary(operator, left, right) {
|
|
switch (operator) {
|
|
case "+":
|
|
return left + right;
|
|
case "-":
|
|
return left - right;
|
|
case "*":
|
|
return left * right;
|
|
case "/":
|
|
return left / right;
|
|
case "%":
|
|
return left % right;
|
|
case "^":
|
|
return Math.pow(left, right);
|
|
case "==":
|
|
return left == right ? 1 : 0;
|
|
case "!=":
|
|
return left != right ? 1 : 0;
|
|
case ">":
|
|
return left > right ? 1 : 0;
|
|
case "<":
|
|
return left < right ? 1 : 0;
|
|
case ">=":
|
|
return left >= right ? 1 : 0;
|
|
case "<=":
|
|
return left <= right ? 1 : 0;
|
|
default:
|
|
throw new Error("Unknown operator");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate the results of operations against all numeric types.
|
|
*
|
|
* @param {*} operator
|
|
*/
|
|
function validate_unary_operations(output, expressions, operator) {
|
|
for (const expr of expressions) {
|
|
const output_col = output[expr];
|
|
const input = expr.substr(2, expr.length - 3);
|
|
expect({
|
|
expr,
|
|
result: output_col,
|
|
}).toEqual({
|
|
expr,
|
|
result: output[input].map((v) => calc_unary(operator, v)),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate the results of operations against all numeric types.
|
|
*
|
|
* @param {*} operator
|
|
*/
|
|
function validate_binary_operations(output, expressions, operator) {
|
|
for (const expr of expressions) {
|
|
const output_col = output[expr];
|
|
const inputs = expr.split(` ${operator} `);
|
|
const left = inputs[0].substr(1, inputs[0].length - 2);
|
|
const right = inputs[1].substr(1, inputs[1].length - 2);
|
|
expect({
|
|
expr,
|
|
result: output_col,
|
|
}).toEqual({
|
|
expr,
|
|
result: output[left].map((v, idx) =>
|
|
calc_binary(operator, v, output[right][idx]),
|
|
),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests the correctness of expressions allowed by exprtk, using as many
|
|
* of the expression structures as we can, such as conditionals, loops, etc.
|
|
*/
|
|
((perspective) => {
|
|
test.describe("Numeric operations", function () {
|
|
test.describe("All numeric types", function () {
|
|
test.describe("aggregates", function () {
|
|
test(
|
|
"integer",
|
|
async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { '"a"': '"a"' },
|
|
});
|
|
|
|
await table.update({
|
|
a: [10, 15, 20, 30],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['"a"']).toEqual([10, 15, 20, 30]);
|
|
expect(result["a"]).toEqual([10, 15, 20, 30]);
|
|
await view.delete();
|
|
await table.delete();
|
|
},
|
|
|
|
test("expression integer column sum", async function () {
|
|
const table = await perspective.table({
|
|
a: [10, 15, 20, 30],
|
|
b: [1, 2, 3, 4],
|
|
});
|
|
|
|
const view = await table.view({
|
|
group_by: ["b"],
|
|
aggregates: {
|
|
a: "sum",
|
|
b: "sum",
|
|
},
|
|
expressions: { '"a"': '"a"' },
|
|
});
|
|
|
|
await table.update({
|
|
a: [40, 50, 60, 70],
|
|
});
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
__ROW_PATH__: [[], [null], [1], [2], [3], [4]],
|
|
a: [295, 220, 10, 15, 20, 30],
|
|
b: [10, 0, 1, 2, 3, 4],
|
|
'"a"': [295, 220, 10, 15, 20, 30],
|
|
});
|
|
await view.delete();
|
|
await table.delete();
|
|
}),
|
|
);
|
|
});
|
|
|
|
test.describe("unary", function () {
|
|
test("negative", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_arrow.slice(),
|
|
);
|
|
const expressions = generate_unary_operations("+");
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
validate_unary_operations(result, expressions, "+");
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("binary", function () {
|
|
test("add", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_arrow.slice(),
|
|
);
|
|
const expressions = generate_binary_operations("+");
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
validate_binary_operations(result, expressions, "+");
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("subtract", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_arrow.slice(),
|
|
);
|
|
const expressions = generate_binary_operations("-");
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
validate_binary_operations(result, expressions, "-");
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("multiply", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_arrow.slice(),
|
|
);
|
|
const expressions = generate_binary_operations("*");
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
validate_binary_operations(result, expressions, "*");
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("divide", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_arrow.slice(),
|
|
);
|
|
const expressions = generate_binary_operations("/");
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
validate_binary_operations(result, expressions, "/");
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("modulus", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_arrow.slice(),
|
|
);
|
|
const expressions = generate_binary_operations("%");
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
validate_binary_operations(result, expressions, "%");
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("power", async function () {
|
|
const table = await perspective.table(common.arrow.slice());
|
|
const expressions = generate_binary_operations("^");
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
validate_binary_operations(result, expressions, "^");
|
|
});
|
|
|
|
test("==", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_multi_arrow.slice(),
|
|
);
|
|
const expressions = [];
|
|
|
|
// comparisons only work when the two types are the same
|
|
for (const expr of NUMERIC_TYPES) {
|
|
expressions.push(`"${expr}" == "${expr} 2"`);
|
|
}
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
|
|
for (const expr of expressions) {
|
|
const output_col = result[expr];
|
|
const inputs = expr.split(` == `);
|
|
const left = inputs[0].substr(1, inputs[0].length - 2);
|
|
const right = inputs[1].substr(1, inputs[1].length - 2);
|
|
expect(output_col).toEqual(
|
|
result[left].map(
|
|
(v, idx) => v == result[right][idx],
|
|
),
|
|
);
|
|
}
|
|
});
|
|
|
|
test("!=", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_multi_arrow.slice(),
|
|
);
|
|
const expressions = [];
|
|
|
|
// comparisons only work when the two types are the same
|
|
for (const expr of NUMERIC_TYPES) {
|
|
expressions.push(`"${expr}" != "${expr} 2"`);
|
|
}
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
|
|
for (const expr of expressions) {
|
|
const output_col = result[expr];
|
|
const inputs = expr.split(` != `);
|
|
const left = inputs[0].substr(1, inputs[0].length - 2);
|
|
const right = inputs[1].substr(1, inputs[1].length - 2);
|
|
expect(output_col).toEqual(
|
|
result[left].map(
|
|
(v, idx) => v != result[right][idx],
|
|
),
|
|
);
|
|
}
|
|
});
|
|
|
|
test(">", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_multi_arrow.slice(),
|
|
);
|
|
const expressions = [];
|
|
|
|
// comparisons only work when the two types are the same
|
|
for (const expr of NUMERIC_TYPES) {
|
|
expressions.push(`"${expr}" > "${expr} 2"`);
|
|
}
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
|
|
for (const expr of expressions) {
|
|
const output_col = result[expr];
|
|
const inputs = expr.split(` > `);
|
|
const left = inputs[0].substr(1, inputs[0].length - 2);
|
|
const right = inputs[1].substr(1, inputs[1].length - 2);
|
|
expect(output_col).toEqual(
|
|
result[left].map(
|
|
(v, idx) => v > result[right][idx],
|
|
),
|
|
);
|
|
}
|
|
});
|
|
|
|
test("<", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_multi_arrow.slice(),
|
|
);
|
|
const expressions = [];
|
|
|
|
// comparisons only work when the two types are the same
|
|
for (const expr of NUMERIC_TYPES) {
|
|
expressions.push(`"${expr}" < "${expr} 2"`);
|
|
}
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
|
|
for (const expr of expressions) {
|
|
const output_col = result[expr];
|
|
const inputs = expr.split(` < `);
|
|
const left = inputs[0].substr(1, inputs[0].length - 2);
|
|
const right = inputs[1].substr(1, inputs[1].length - 2);
|
|
expect(output_col).toEqual(
|
|
result[left].map(
|
|
(v, idx) => v < result[right][idx],
|
|
),
|
|
);
|
|
}
|
|
});
|
|
|
|
test(">=", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_multi_arrow.slice(),
|
|
);
|
|
const expressions = [];
|
|
|
|
// comparisons only work when the two types are the same
|
|
for (const expr of NUMERIC_TYPES) {
|
|
expressions.push(`"${expr}" >= "${expr} 2"`);
|
|
}
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
|
|
for (const expr of expressions) {
|
|
const output_col = result[expr];
|
|
const inputs = expr.split(` >= `);
|
|
const left = inputs[0].substr(1, inputs[0].length - 2);
|
|
const right = inputs[1].substr(1, inputs[1].length - 2);
|
|
expect(output_col).toEqual(
|
|
result[left].map(
|
|
(v, idx) => v >= result[right][idx],
|
|
),
|
|
);
|
|
}
|
|
});
|
|
|
|
test("<=", async function () {
|
|
const table = await perspective.table(
|
|
common.all_types_multi_arrow.slice(),
|
|
);
|
|
const expressions = [];
|
|
|
|
// comparisons only work when the two types are the same
|
|
for (const expr of NUMERIC_TYPES) {
|
|
expressions.push(`"${expr}" <= "${expr} 2"`);
|
|
}
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
const col_names = await view.column_paths();
|
|
|
|
for (const expr of expressions) {
|
|
expect(col_names.includes(expr)).toBe(true);
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
|
|
for (const expr of expressions) {
|
|
const output_col = result[expr];
|
|
const inputs = expr.split(` <= `);
|
|
const left = inputs[0].substr(1, inputs[0].length - 2);
|
|
const right = inputs[1].substr(1, inputs[1].length - 2);
|
|
expect(output_col).toEqual(
|
|
result[left].map(
|
|
(v, idx) => v <= result[right][idx],
|
|
),
|
|
);
|
|
}
|
|
});
|
|
|
|
test("Numeric comparisons should be false for different types", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
c: "integer",
|
|
d: "float",
|
|
});
|
|
const view = await table.view({
|
|
expressions: {
|
|
'"a" == "b"': '"a" == "b"',
|
|
'"a" != "b"': '"a" != "b"',
|
|
},
|
|
});
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.0, 2.0, 3.0, 4.0],
|
|
c: [1, 0, 1, 0],
|
|
d: [1.0, 0, 3.0, 0.0],
|
|
});
|
|
const result = await view.to_columns();
|
|
expect(result['"a" == "b"']).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result['"a" != "b"']).toEqual([
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Numeric comparisons should be true for same types", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
c: "integer",
|
|
d: "float",
|
|
});
|
|
const view = await table.view({
|
|
expressions: [
|
|
'"a" == "c"',
|
|
'"a" != "c"',
|
|
'"b" == "d"',
|
|
'"b" != "d"',
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.0, 2.0, 3.0, 4.0],
|
|
c: [1, 0, 1, 0],
|
|
d: [1.0, 0, 3.0, 0.0],
|
|
});
|
|
const result = await view.to_columns();
|
|
expect(result['"a" == "c"']).toEqual([
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result['"a" != "c"']).toEqual([
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
]);
|
|
expect(result['"b" == "d"']).toEqual([
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
]);
|
|
expect(result['"b" != "d"']).toEqual([
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should not divide by 0", async function () {
|
|
const table = await perspective.table({
|
|
a: [1, 2, 3, 4],
|
|
b: [0, 0, 0, 0],
|
|
c: [1.5, 2.123, 3.125, 4.123809],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'"a" / "b"': '"a" / "b"',
|
|
'"c" / "b"': '"c" / "b"',
|
|
},
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['"a" / "b"']).toEqual([
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
]);
|
|
expect(result['"c" / "b"']).toEqual([
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should not modulo by 0", async function () {
|
|
const table = await perspective.table({
|
|
a: [1, 2, 3, 4],
|
|
b: [0, 0, 0, 0],
|
|
c: [1.5, 2.123, 3.125, 4.123809],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'"a" % "b"': '"a" % "b"',
|
|
'"c" % "b"': '"c" % "b"',
|
|
},
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['"a" % "b"']).toEqual([
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
]);
|
|
expect(result['"c" % "b"']).toEqual([
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe("Integers", function () {
|
|
test("Integer outputs with negative values dont overflow", async function () {
|
|
var data = [
|
|
{ x: -1, y: "a", z: true },
|
|
{ x: -2, y: "b", z: false },
|
|
{ x: -3, y: "c", z: true },
|
|
{ x: -4, y: "d", z: false },
|
|
];
|
|
|
|
var table = await perspective.table({
|
|
x: "integer",
|
|
y: "string",
|
|
z: "boolean",
|
|
});
|
|
|
|
await table.update(data);
|
|
|
|
var view = await table.view({
|
|
group_by: ["z"],
|
|
columns: ["x", "w"],
|
|
expressions: {
|
|
w: 'integer("x")',
|
|
},
|
|
});
|
|
|
|
var answer = [
|
|
{ __ROW_PATH__: [], x: -10, w: -10 },
|
|
{ __ROW_PATH__: [false], x: -6, w: -6 },
|
|
{ __ROW_PATH__: [true], x: -4, w: -4 },
|
|
];
|
|
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(answer);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Functions", function () {
|
|
test("min", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const expressions = [
|
|
"min(1)",
|
|
'min("a")',
|
|
'min("a", -10, -10.001)',
|
|
'min("b")',
|
|
'min("b", 0.00000000001, -10, -100, -100.1)',
|
|
'min("a", "b")',
|
|
];
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.5, 2.5, 3.5, 4.5],
|
|
});
|
|
|
|
const schema = await view.expression_schema();
|
|
for (const expr of expressions) {
|
|
expect(schema[expr]).toEqual("float");
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["min(1)"]).toEqual([1, 1, 1, 1]);
|
|
expect(result['min("a")']).toEqual([1, 2, 3, 4]);
|
|
expect(result['min("a", -10, -10.001)']).toEqual([
|
|
-10.001, -10.001, -10.001, -10.001,
|
|
]);
|
|
expect(result['min("b")']).toEqual([1.5, 2.5, 3.5, 4.5]);
|
|
expect(
|
|
result['min("b", 0.00000000001, -10, -100, -100.1)'],
|
|
).toEqual([-100.1, -100.1, -100.1, -100.1]);
|
|
expect(result['min("a", "b")']).toEqual([1, 2, 3, 4]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("max", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const expressions = [
|
|
"max(1)",
|
|
"max(2000000000000.11, 2000000000000.1)",
|
|
'max("a")',
|
|
'max("a", -1.00001)',
|
|
'max("a", "b")',
|
|
'max("b")',
|
|
'max("a", 10, 20, 0.1, 0.00000001)',
|
|
'max("b", -1, -100, 100)',
|
|
];
|
|
|
|
const view = await table.view({
|
|
expressions,
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.5, 2.5, 3.5, 4.5],
|
|
});
|
|
|
|
const schema = await view.expression_schema();
|
|
for (const expr of expressions) {
|
|
expect(schema[expr]).toEqual("float");
|
|
}
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["max(1)"]).toEqual([1, 1, 1, 1]);
|
|
expect(
|
|
result["max(2000000000000.11, 2000000000000.1)"],
|
|
).toEqual([
|
|
2000000000000.11, 2000000000000.11, 2000000000000.11,
|
|
2000000000000.11,
|
|
]);
|
|
expect(result['max("a")']).toEqual([1, 2, 3, 4]);
|
|
expect(result['max("a", "b")']).toEqual([1.5, 2.5, 3.5, 4.5]);
|
|
expect(result['max("b")']).toEqual([1.5, 2.5, 3.5, 4.5]);
|
|
expect(result['max("a", 10, 20, 0.1, 0.00000001)']).toEqual([
|
|
20, 20, 20, 20,
|
|
]);
|
|
expect(result['max("b", -1, -100, 100)']).toEqual([
|
|
100, 100, 100, 100,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("inrange", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
// works for floats only
|
|
const view = await table.view({
|
|
expressions: {
|
|
'inrange(9, "b", 20)': 'inrange(9, "b", 20)',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [10, 15, 20, 30],
|
|
b: [10.5, 15.5, 20.5, 30.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['inrange(9, "b", 20)']).toEqual([
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("iclamp", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'iclamp(10, "a", 20)': 'iclamp(10, "a", 20)',
|
|
'iclamp(10, "b", 20)': 'iclamp(10, "b", 20)',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [10, 15, 20, 30],
|
|
b: [10.5, 15.5, 20.5, 30.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['iclamp(10, "a", 20)']).toEqual([10, 15, 20, 30]);
|
|
expect(result['iclamp(10, "b", 20)']).toEqual([
|
|
10, 20, 20.5, 30.5,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("pow", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'pow("a", 1)': 'pow("a", 1)',
|
|
'pow("b", 3)': 'pow("b", 3)',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.5, 2.5, 3.5, 4.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['pow("a", 1)']).toEqual([1, 2, 3, 4]);
|
|
expect(result['pow("b", 3)']).toEqual(
|
|
[1.5, 2.5, 3.5, 4.5].map((x) => Math.pow(x, 3)),
|
|
);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test.skip("logn", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'logn("a", 5)': 'logn("a", 5)',
|
|
'logn("b", 3)': 'logn("b", 3)',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [100, 200, 300, 400],
|
|
b: [100.5, 200.5, 300.5, 400.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['logn("a", 5)']).toEqual(
|
|
[100, 200, 300, 400].map((x) => Math.log(x) / Math.log(5)),
|
|
);
|
|
expect(result['logn("b", 3)']).toEqual(
|
|
[100.5, 200.5, 300.5, 400.5].map(
|
|
(x) => Math.log(x) / Math.log(3),
|
|
),
|
|
);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("root", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'root("a", 5)': 'root("a", 5)',
|
|
'root("b", 3)': 'root("b", 3)',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [100, 200, 300, 400],
|
|
b: [100.5, 200.5, 300.5, 400.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(
|
|
result['root("a", 5)'].map((x) => Number(x.toFixed(5))),
|
|
).toEqual(
|
|
[100, 200, 300, 400].map((x) =>
|
|
Number(Math.pow(x, 1 / 5).toFixed(5)),
|
|
),
|
|
);
|
|
expect(
|
|
result['root("b", 3)'].map((x) => Number(x.toFixed(5))),
|
|
).toEqual(
|
|
[100.5, 200.5, 300.5, 400.5].map((x) =>
|
|
Number(Math.pow(x, 1 / 3).toFixed(5)),
|
|
),
|
|
);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("avg", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: [
|
|
'avg("a", 10, 20, 30, 40, "a")',
|
|
'avg("b", 3, 4, 5, "b")',
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.5, 2.5, 3.5, 4.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['avg("a", 10, 20, 30, 40, "a")']).toEqual(
|
|
[1, 2, 3, 4].map((x) => (x + x + 10 + 20 + 30 + 40) / 6),
|
|
);
|
|
expect(result['avg("b", 3, 4, 5, "b")']).toEqual(
|
|
[1.5, 2.5, 3.5, 4.5].map((x) => (x + x + 3 + 4 + 5) / 5),
|
|
);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("sum", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: [
|
|
'sum("a", 10, 20, 30, 40, "a")',
|
|
'sum("b", 3, 4, 5, "b")',
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.5, 2.5, 3.5, 4.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['sum("a", 10, 20, 30, 40, "a")']).toEqual(
|
|
[1, 2, 3, 4].map((x) => x + x + 10 + 20 + 30 + 40),
|
|
);
|
|
expect(result['sum("b", 3, 4, 5, "b")']).toEqual(
|
|
[1.5, 2.5, 3.5, 4.5].map((x) => x + x + 3 + 4 + 5),
|
|
);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("trunc", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'trunc("a")': 'trunc("a")',
|
|
'trunc("b")': 'trunc("b")',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.5, 2.5, 3.5, 4.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['trunc("a")']).toEqual([1, 2, 3, 4]);
|
|
expect(result['trunc("b")']).toEqual([1, 2, 3, 4]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("d2r", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'deg2rad("a")': 'deg2rad("a")',
|
|
'deg2rad("b")': 'deg2rad("b")',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [30, 60, 90, 120],
|
|
b: [25.5, 45.5, 88.721282, 91.12983],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['deg2rad("a")']).toEqual(
|
|
[30, 60, 90, 120].map((x) => x * (Math.PI / 180)),
|
|
);
|
|
expect(result['deg2rad("b")']).toEqual(
|
|
[25.5, 45.5, 88.721282, 91.12983].map(
|
|
(x) => x * (Math.PI / 180),
|
|
),
|
|
);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test.skip("OG - r2d", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'rad2deg("a")': 'rad2deg("a")',
|
|
'rad2deg("b")': 'rad2deg("b")',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [25.5, 45.5, 88.721282, 91.12983].map(
|
|
(x) => x * (Math.PI / 180),
|
|
),
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['rad2deg("a")']).toEqual(
|
|
[1, 2, 3, 4].map((x) => x * (180 / Math.PI)),
|
|
);
|
|
expect(result['rad2deg("b")']).toEqual([
|
|
25.5, 45.5, 88.721282, 91.12983,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("r2d", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
'rad2deg("a")': 'rad2deg("a")',
|
|
'rad2deg("b")': 'rad2deg("b")',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, 2, 3, 4],
|
|
b: [25.5, 45.5, 88.721282, 91.12983].map(
|
|
(x) => x * (Math.PI / 180),
|
|
),
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['rad2deg("a")']).toEqual(
|
|
[1, 2, 3, 4].map((x) => x * (180 / Math.PI)),
|
|
);
|
|
const expected = [25.5, 45.5, 88.721282, 91.12983];
|
|
expect(result['rad2deg("b")'].length).toEqual(expected.length);
|
|
result['rad2deg("b")'].forEach((x, idx) =>
|
|
expect(x).toBeCloseTo(expected[idx], 5),
|
|
);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("is_null", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: [
|
|
'is_null("a")',
|
|
'is_null("b")',
|
|
'if(is_null("a")) 100; else 0;',
|
|
'if(is_null("b")) 100; else 0;',
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, null, null, 4],
|
|
b: [null, 2.5, null, 4.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['is_null("a")']).toEqual([
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
]);
|
|
expect(result['is_null("b")']).toEqual([
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
]);
|
|
expect(result['if(is_null("a")) 100; else 0;']).toEqual([
|
|
0, 100, 100, 0,
|
|
]);
|
|
expect(result['if(is_null("b")) 100; else 0;']).toEqual([
|
|
100, 0, 100, 0,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("is_not_null", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: [
|
|
'is_not_null("a")',
|
|
'is_not_null("b")',
|
|
'if(is_not_null("a")) 100; else 0;',
|
|
'if(is_not_null("b")) 100; else 0;',
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, null, null, 4],
|
|
b: [null, 2.5, null, 4.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['is_not_null("a")']).toEqual([
|
|
true,
|
|
false,
|
|
false,
|
|
true,
|
|
]);
|
|
expect(result['is_not_null("b")']).toEqual([
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
]);
|
|
expect(result['if(is_not_null("a")) 100; else 0;']).toEqual([
|
|
100, 0, 0, 100,
|
|
]);
|
|
expect(result['if(is_not_null("b")) 100; else 0;']).toEqual([
|
|
0, 100, 0, 100,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("coalesce returns first non-null arg", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "integer",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
coalesce_ab: 'coalesce("a", "b")',
|
|
coalesce_ab_default: 'coalesce("a", "b", 99)',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, null, null, 4],
|
|
b: [10, 20, null, 40],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["coalesce_ab"]).toEqual([1, 20, null, 4]);
|
|
expect(result["coalesce_ab_default"]).toEqual([1, 20, 99, 4]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("coalesce promotes mixed numeric inputs to float", async function () {
|
|
const table = await perspective.table({
|
|
i: "integer",
|
|
f: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
coalesce_if: 'coalesce("i", "f")',
|
|
coalesce_if_default: 'coalesce("i", "f", 0.5)',
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
i: [1, null, null, 4],
|
|
f: [null, 2.5, null, 4.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
const schema = await view.expression_schema();
|
|
expect(schema["coalesce_if"]).toEqual("float");
|
|
expect(schema["coalesce_if_default"]).toEqual("float");
|
|
expect(result["coalesce_if"]).toEqual([1, 2.5, null, 4]);
|
|
expect(result["coalesce_if_default"]).toEqual([1, 2.5, 0.5, 4]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("coalesce with all-null inputs returns null", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "integer",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { coalesce_nulls: 'coalesce("a", "b")' },
|
|
});
|
|
|
|
await table.update({
|
|
a: [null, null, null],
|
|
b: [null, null, null],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["coalesce_nulls"]).toEqual([null, null, null]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("coalesce fails validation for incompatible types", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "string",
|
|
});
|
|
|
|
const validated = await table.validate_expressions([
|
|
'coalesce("a", "b")',
|
|
"coalesce(\"a\", 'fallback')",
|
|
]);
|
|
|
|
expect(validated.expression_schema).toEqual({});
|
|
expect(validated.errors['coalesce("a", "b")']).toEqual({
|
|
column: 0,
|
|
line: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
});
|
|
|
|
expect(validated.errors["coalesce(\"a\", 'fallback')"]).toEqual(
|
|
{
|
|
column: 0,
|
|
line: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
},
|
|
);
|
|
|
|
await table.delete();
|
|
});
|
|
|
|
test("null", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: [
|
|
"float(null)",
|
|
"string(null)",
|
|
"integer(null)",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
|
|
await table.update({
|
|
a: [1, null, null, 4],
|
|
b: [null, 2.5, null, 4.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["float(null)"]).toEqual([null, null, null, null]);
|
|
expect(result["string(null)"]).toEqual([
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
]);
|
|
|
|
expect(result["integer(null)"]).toEqual([
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("percent_of", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: [
|
|
'percent_of("a", 500)',
|
|
'percent_of("a", "b")',
|
|
"percent_of(1, 3)",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
|
|
await table.update({
|
|
a: [100, 200, 300, 400],
|
|
b: [100.5, 200.5, 300.5, 400.5],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['percent_of("a", 500)']).toEqual(
|
|
[100, 200, 300, 400].map((x) => (x / 500) * 100),
|
|
);
|
|
expect(result['percent_of("a", "b")']).toEqual(
|
|
[100, 200, 300, 400].map(
|
|
(x, idx) => (x / result["b"][idx]) * 100,
|
|
),
|
|
);
|
|
expect(result["percent_of(1, 3)"]).toEqual([
|
|
33.33333333333333, 33.33333333333333, 33.33333333333333,
|
|
33.33333333333333,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("bucket", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: [
|
|
'bucket("a", 5)',
|
|
'bucket("b", 2.5)',
|
|
`bucket("b", 10)`,
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
|
|
await table.update({
|
|
a: [15, 15, 35, 40, 1250, 1255],
|
|
b: [2.25, 2, 3.5, 16.5, 28, 8],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['bucket("a", 5)']).toEqual([
|
|
15, 15, 35, 40, 1250, 1255,
|
|
]);
|
|
expect(result['bucket("b", 2.5)']).toEqual([
|
|
0, 0, 2.5, 15, 27.5, 7.5,
|
|
]);
|
|
expect(result['bucket("b", 10)']).toEqual([0, 0, 0, 10, 20, 0]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("random", async function () {
|
|
const table = await perspective.table({
|
|
x: "float",
|
|
});
|
|
|
|
const data = [];
|
|
for (let i = 0; i < 1000; i++) {
|
|
data.push(i);
|
|
}
|
|
await table.update({ x: data });
|
|
const view = await table.view({
|
|
expressions: { "random()": "random()" },
|
|
});
|
|
const schema = await view.expression_schema();
|
|
expect(schema).toEqual({ "random()": "float" });
|
|
const result = await view.to_columns();
|
|
|
|
for (let i = 0; i < 1000; i++) {
|
|
const res = result["random()"][i];
|
|
expect(res >= 0 && res <= 1).toBe(true);
|
|
}
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Booleans", function () {
|
|
test("AND", async function () {
|
|
const table = await perspective.table(common.comparison_data);
|
|
const view = await table.view({
|
|
expressions: [
|
|
'"u" and "u"',
|
|
'"u" and "z"',
|
|
'"z" and "z"',
|
|
"0 and 0",
|
|
"1 and 1",
|
|
"1 and 100",
|
|
"true and false",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const result = await view.to_columns();
|
|
expect(result['"u" and "u"']).toEqual([
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
]);
|
|
expect(result['"u" and "z"']).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result['"u" and "z"']).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result["0 and 0"]).toEqual([false, false, false, false]);
|
|
expect(result["1 and 1"]).toEqual([true, true, true, true]);
|
|
expect(result["1 and 100"]).toEqual([true, true, true, true]);
|
|
expect(result["true and false"]).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("mand", async function () {
|
|
const table = await perspective.table(common.comparison_data);
|
|
const view = await table.view({
|
|
expressions: [
|
|
'mand("u" and "u", "u" and "z", "z" and "z")',
|
|
"mand(true, true, true, true)",
|
|
"mand(is_null(null), is_not_null(null))",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const result = await view.to_columns();
|
|
|
|
expect(
|
|
result['mand("u" and "u", "u" and "z", "z" and "z")'],
|
|
).toEqual(Array(4).fill(false));
|
|
|
|
expect(
|
|
result["mand(is_null(null), is_not_null(null))"],
|
|
).toEqual(Array(4).fill(false));
|
|
|
|
expect(result["mand(true, true, true, true)"]).toEqual(
|
|
Array(4).fill(true),
|
|
);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("mand should fail validation for non boolean inputs", async function () {
|
|
const table = await perspective.table(common.comparison_data);
|
|
const validated = await table.validate_expressions([
|
|
'mand("w", "x", "y")',
|
|
"mand(100, 200, 300)",
|
|
"mand('a', 'b', 'cdef')",
|
|
]);
|
|
|
|
expect(validated.expression_schema).toEqual({});
|
|
expect(validated.errors['mand("w", "x", "y")']).toEqual({
|
|
column: 0,
|
|
line: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
});
|
|
expect(validated.errors["mand(100, 200, 300)"]).toEqual({
|
|
column: 0,
|
|
line: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
});
|
|
expect(validated.errors["mand('a', 'b', 'cdef')"]).toEqual({
|
|
column: 0,
|
|
line: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
});
|
|
|
|
await table.delete();
|
|
});
|
|
|
|
test("OR", async function () {
|
|
const table = await perspective.table({
|
|
a: [0, 1, 0, 1],
|
|
b: [0, 1, 0, 1],
|
|
c: [0, 0, 0, 0],
|
|
d: [1, 1, 1, 1],
|
|
e: [false, false, false, false],
|
|
f: [true, true, true, true],
|
|
});
|
|
const view = await table.view({
|
|
expressions: {
|
|
'"a" or "b"': '"a" or "b"',
|
|
'"c" or "d"': '"c" or "d"',
|
|
'"e" or "f"': '"e" or "f"',
|
|
"0 or 1": "0 or 1",
|
|
"true or false": "true or false",
|
|
"false or false": "false or false",
|
|
filtered: '"a" > 0.5 or "d" < 0.5',
|
|
},
|
|
});
|
|
const result = await view.to_columns();
|
|
expect(result['"a" or "b"']).toEqual([
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
]);
|
|
expect(result['"c" or "d"']).toEqual([true, true, true, true]);
|
|
expect(result['"e" or "f"']).toEqual([true, true, true, true]);
|
|
expect(result["0 or 1"]).toEqual([true, true, true, true]);
|
|
expect(result["true or false"]).toEqual([
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
]);
|
|
expect(result["false or false"]).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result["filtered"]).toEqual([true, true, true, true]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("mor", async function () {
|
|
const table = await perspective.table(common.comparison_data);
|
|
const view = await table.view({
|
|
expressions: [
|
|
'mor("u" and "u", "u" and "z", "z" and "z")',
|
|
"mor(false, false, false)",
|
|
"mor(false, true, false)",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const result = await view.to_columns();
|
|
expect(
|
|
result['mor("u" and "u", "u" and "z", "z" and "z")'],
|
|
).toEqual([true, true, true, true]);
|
|
|
|
// The boolean false is a false
|
|
expect(result["mor(false, false, false)"]).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
|
|
expect(result["mor(false, true, false)"]).toEqual([
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("mor should fail validation for non boolean inputs", async function () {
|
|
const table = await perspective.table(common.comparison_data);
|
|
const validated = await table.validate_expressions([
|
|
'mor("w", "x", "y")',
|
|
"mor(100, 200, 300)",
|
|
"mor('a', 'b', 'cdef')",
|
|
]);
|
|
|
|
expect(validated.expression_schema).toEqual({});
|
|
expect(validated.errors['mor("w", "x", "y")']).toEqual({
|
|
column: 0,
|
|
line: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
});
|
|
expect(validated.errors["mor(100, 200, 300)"]).toEqual({
|
|
column: 0,
|
|
line: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
});
|
|
expect(validated.errors["mor('a', 'b', 'cdef')"]).toEqual({
|
|
column: 0,
|
|
line: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
});
|
|
|
|
await table.delete();
|
|
});
|
|
|
|
test("NAND", async function () {
|
|
const table = await perspective.table(common.comparison_data);
|
|
const view = await table.view({
|
|
expressions: [
|
|
'"u" nand "u"',
|
|
'"u" nand "z"',
|
|
'"z" nand "z"',
|
|
"0 nand 0",
|
|
"1 nand 1",
|
|
"1 nand 100",
|
|
"true nand true",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const result = await view.to_columns();
|
|
expect(result['"u" nand "u"']).toEqual([
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
]);
|
|
expect(result['"u" nand "z"']).toEqual([
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
]);
|
|
expect(result['"u" nand "z"']).toEqual([
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
]);
|
|
expect(result["0 nand 0"]).toEqual([true, true, true, true]);
|
|
expect(result["1 nand 1"]).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result["1 nand 100"]).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result["true nand true"]).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("NOR", async function () {
|
|
const table = await perspective.table({
|
|
a: [0, 1, 0, 1],
|
|
b: [0, 1, 0, 1],
|
|
c: [0, 0, 0, 0],
|
|
d: [1, 1, 1, 1],
|
|
e: [false, false, false, false],
|
|
f: [true, true, true, true],
|
|
});
|
|
const view = await table.view({
|
|
expressions: [
|
|
'"a" nor "b"',
|
|
'"c" nor "d"',
|
|
'"e" nor "f"',
|
|
"0 nor 1",
|
|
"false nor false",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const result = await view.to_columns();
|
|
expect(result['"a" nor "b"']).toEqual([
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
]);
|
|
expect(result['"c" nor "d"']).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result['"e" nor "f"']).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result["0 nor 1"]).toEqual([false, false, false, false]);
|
|
expect(result["false nor false"]).toEqual([
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("XOR", async function () {
|
|
const table = await perspective.table({
|
|
a: [0, 1, 0, 1],
|
|
b: [0, 1, 0, 1],
|
|
c: [0, 0, 0, 0],
|
|
d: [1, 1, 1, 1],
|
|
e: [false, false, false, false],
|
|
f: [true, true, true, true],
|
|
});
|
|
const view = await table.view({
|
|
expressions: [
|
|
'"a" xor "b"',
|
|
'"c" xor "d"',
|
|
'"e" xor "f"',
|
|
"0 xor 1",
|
|
"false xor false",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const result = await view.to_columns();
|
|
expect(result['"a" xor "b"']).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(result['"c" xor "d"']).toEqual([true, true, true, true]);
|
|
expect(result['"e" xor "f"']).toEqual([true, true, true, true]);
|
|
expect(result["0 xor 1"]).toEqual([true, true, true, true]);
|
|
expect(result["false xor false"]).toEqual([
|
|
false,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
});
|
|
})(perspective);
|