Files
2026-07-13 12:25:07 +08:00

1473 lines
52 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 expressions_common from "./common.js";
/**
* Tests the functionality of `View`-based expressions, specifically that
* existing column/view semantics (pivots, aggregates, columns, sorts, filters)
* continue to be functional on expressions.
*/
((perspective) => {
const validate_delta = async (colname, delta, expected) => {
const t = await perspective.table(delta.slice());
const v = await t.view();
const data = await v.to_columns();
expect(data[colname]).toEqual(expected);
await v.delete();
await t.delete();
};
test.describe("Expressions with multiple views", () => {
test.describe("Schema then update", () => {
test("Appends", async () => {
const table = await perspective.table({
x: "integer",
y: "string",
z: "boolean",
});
const v1 = await table.view({
expressions: { column: `"x" + 10` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result).toEqual({ column: [], x: [], y: [], z: [] });
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
await table.update(expressions_common.data);
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Append in sequence", async () => {
const table = await perspective.table({
x: "integer",
y: "string",
z: "boolean",
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
});
const v2 = await table.view({
expressions: { column: `upper(concat("y", 'bcd'))` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result).toEqual({ column: [], x: [], y: [], z: [] });
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
const expected = [];
const expected2 = [];
for (let i = 1; i < 10; i++) {
await table.update({
x: [i],
y: [`${i}`],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expected.push(i * 2);
expected2.push(`${i}BCD`);
expect(result["column"]).toEqual(expected);
expect(result2["column"]).toEqual(expected2);
}
await v2.delete();
await v1.delete();
await table.delete();
});
test("Append in sequence, filtered", async () => {
const table = await perspective.table({
x: "integer",
y: "string",
z: "boolean",
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
filter: [["column", ">", 5]],
});
const v2 = await table.view({
expressions: { column: `upper(concat("y", 'bcd'))` },
filter: [["column", "contains", "A"]],
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result).toEqual({ column: [], x: [], y: [], z: [] });
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
const expected = [];
const expected2 = [];
for (let i = 1; i < 10; i++) {
await table.update({
x: [i],
y: [`A${i}`],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
if (i * 2 > 5) {
expected.push(i * 2);
expect(result["column"]).toEqual(expected);
} else {
expect(result).toEqual({
column: [],
x: [],
y: [],
z: [],
});
}
expected2.push(`A${i}BCD`);
expect(result2["column"]).toEqual(expected2);
}
await v2.delete();
await v1.delete();
await table.delete();
});
test("Append in sequence, sorted", async () => {
const table = await perspective.table({
x: "integer",
y: "string",
z: "boolean",
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
sort: [["column", "desc"]],
});
const v2 = await table.view({
expressions: { column: `upper(concat("y", 'bcd'))` },
sort: [["column", "asc"]],
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result).toEqual({ column: [], x: [], y: [], z: [] });
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
const expected = [];
const expected2 = [];
let idx = 0;
for (let i = 5; i < 9; i++) {
await table.update({
x: [i],
y: [`${i}`],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expected.unshift(i * 2);
expected2.splice(idx, 0, `${i}BCD`);
expect(result["column"]).toEqual(expected);
expect(result2["column"]).toEqual(expected2);
idx++;
}
await v2.delete();
await v1.delete();
await table.delete();
});
test("Partial update", async () => {
const table = await perspective.table(
{
x: "integer",
y: "string",
z: "boolean",
},
{ index: "x" },
);
const v1 = await table.view({
expressions: { column: `"x" * 2` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result).toEqual({ column: [], x: [], y: [], z: [] });
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([null, 4, 6, 8, 20]);
expect(result2["column"]).toEqual([
"DEF",
"X",
"Z",
"Y",
"ABC",
]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Partial update, filtered", async () => {
const table = await perspective.table(
{
x: "integer",
y: "string",
z: "boolean",
},
{ index: "x" },
);
const v1 = await table.view({
expressions: { column: `"x" * 2` },
filter: [["column", "==", 8]],
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
filter: [["column", "==", "Z"]],
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result).toEqual({ column: [], x: [], y: [], z: [] });
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([8]);
expect(result2["column"]).toEqual(["Z"]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Partial update, sorted", async () => {
const table = await perspective.table(
{
x: "integer",
y: "string",
z: "boolean",
},
{ index: "x" },
);
const v1 = await table.view({
expressions: { column: `"x" * 2` },
sort: [["column", "desc"]],
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
sort: [["column", "desc"]],
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result).toEqual({ column: [], x: [], y: [], z: [] });
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([20, 8, 6, 4, null]);
expect(result2["column"]).toEqual([
"Z",
"Y",
"X",
"DEF",
"ABC",
]);
await table.update({
x: [2, 10],
y: ["XYZ", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([20, 8, 6, 4, null]);
expect(result2["x"]).toEqual([3, 4, 2, null, 10]);
expect(result2["column"]).toEqual([
"Z",
"Y",
"XYZ",
"DEF",
"DEF",
]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Limit", async () => {
const table = await perspective.table(
{
x: "integer",
y: "string",
z: "boolean",
},
{ limit: 2 },
);
const v1 = await table.view({
expressions: { column: `"x" * 2` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result).toEqual({ column: [], x: [], y: [], z: [] });
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([20, null]);
expect(result2["column"]).toEqual(["ABC", "DEF"]);
expect(await table.size()).toEqual(2);
await v2.delete();
await v1.delete();
await table.delete();
});
});
test.describe("Data then update", () => {
test("Appends", async () => {
const table = await perspective.table(expressions_common.data);
const v1 = await table.view({
expressions: { column: `"x" + 10` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
await table.update(expressions_common.data);
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([
11, 12, 13, 14, 11, 12, 13, 14,
]);
expect(result2["column"]).toEqual([
"A",
"B",
"C",
"D",
"A",
"B",
"C",
"D",
]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Append in sequence", async () => {
const table = await perspective.table(expressions_common.data);
const v1 = await table.view({
expressions: { column: `"x" * 2` },
});
const v2 = await table.view({
expressions: { column: `upper(concat("y", 'bcd'))` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([2, 4, 6, 8]);
expect(result2["column"]).toEqual([
"ABCD",
"BBCD",
"CBCD",
"DBCD",
]);
const expected = [2, 4, 6, 8];
const expected2 = ["ABCD", "BBCD", "CBCD", "DBCD"];
for (let i = 0; i < 10; i++) {
await table.update({
x: [i + 4],
y: [`${i + 4}`],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expected.push((i + 4) * 2);
expected2.push(`${i + 4}BCD`);
expect(result["column"]).toEqual(expected);
expect(result2["column"]).toEqual(expected2);
}
await v2.delete();
await v1.delete();
await table.delete();
});
test("Append in sequence, filtered", async () => {
const table = await perspective.table(expressions_common.data);
const v1 = await table.view({
expressions: { column: `"x" * 2` },
filter: [["column", ">", 5]],
});
const v2 = await table.view({
expressions: { column: `upper(concat("y", 'bcd'))` },
filter: [["column", "contains", "A"]],
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([6, 8]);
expect(result2["column"]).toEqual(["ABCD"]);
const expected = [6, 8];
for (let i = 0; i < 10; i++) {
await table.update({
x: [i + 4],
y: [`${i + 4}`],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expected.push((i + 4) * 2);
expect(result["column"]).toEqual(expected);
expect(result2["column"]).toEqual(["ABCD"]);
}
await v2.delete();
await v1.delete();
await table.delete();
});
test("Append in sequence, sorted", async () => {
const table = await perspective.table(expressions_common.data);
const v1 = await table.view({
expressions: { column: `"x" * 2` },
sort: [["column", "desc"]],
});
const v2 = await table.view({
expressions: { column: `upper(concat("y", 'bcd'))` },
sort: [["column", "asc"]],
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
const expected = [8, 6, 4, 2];
const expected2 = ["ABCD", "BBCD", "CBCD", "DBCD"];
expect(result["column"]).toEqual(expected);
expect(result2["column"]).toEqual(expected2);
let idx = 0;
for (let i = 5; i < 9; i++) {
await table.update({
x: [i],
y: [`${i}`],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expected.unshift(i * 2);
expected2.splice(idx, 0, `${i}BCD`);
expect(result["column"]).toEqual(expected);
expect(result2["column"]).toEqual(expected2);
idx++;
}
await v2.delete();
await v1.delete();
await table.delete();
});
test("Partial update", async () => {
const table = await perspective.table(expressions_common.data, {
index: "x",
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([2, 4, 6, 8]);
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([null, 2, 4, 6, 8, 20]);
expect(result2["column"]).toEqual([
"DEF",
"A",
"X",
"Z",
"Y",
"ABC",
]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Partial update, filtered", async () => {
const table = await perspective.table(expressions_common.data, {
index: "x",
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
filter: [["column", "==", 8]],
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
filter: [["column", "==", "B"]],
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([8]);
expect(result2["column"]).toEqual(["B"]);
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
console.log(result, result2);
expect(result["column"]).toEqual([8]);
expect(result2).toEqual({ column: [], x: [], y: [], z: [] });
await v2.delete();
await v1.delete();
await table.delete();
});
test("Partial update, sorted", async () => {
const table = await perspective.table(expressions_common.data, {
index: "x",
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
sort: [["column", "desc"]],
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
sort: [["column", "desc"]],
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([8, 6, 4, 2]);
expect(result2["column"]).toEqual(["D", "C", "B", "A"]);
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([20, 8, 6, 4, 2, null]);
expect(result2["column"]).toEqual([
"Z",
"Y",
"X",
"DEF",
"ABC",
"A",
]);
await table.update({
x: [2, 10],
y: ["XYZ", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([20, 8, 6, 4, 2, null]);
expect(result2["x"]).toEqual([3, 4, 2, null, 10, 1]);
expect(result2["column"]).toEqual([
"Z",
"Y",
"XYZ",
"DEF",
"DEF",
"A",
]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Limit", async () => {
const table = await perspective.table(expressions_common.data, {
limit: 2,
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([6, 8]);
expect(result2["column"]).toEqual(["C", "D"]);
expect(result["y"]).toEqual(["c", "d"]);
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([20, null]);
expect(result2["column"]).toEqual(["ABC", "DEF"]);
await v2.delete();
await v1.delete();
await table.delete();
});
});
test.describe("Deltas", () => {
test("Appends delta", async () => {
expect.assertions(6);
const table = await perspective.table(expressions_common.data);
const v1 = await table.view({
expressions: { column: `"x" + 10` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
const result = await v1.to_columns();
const result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
v1.on_update(
async (updated) => {
await validate_delta(
"column",
updated.delta,
[11, 12, 13, 14],
);
const result = await v1.to_columns();
expect(result["column"]).toEqual([
11, 12, 13, 14, 11, 12, 13, 14,
]);
},
{ mode: "row" },
);
let done;
let result3 = new Promise((x) => {
done = x;
});
v2.on_update(
async (updated) => {
await validate_delta("column", updated.delta, [
"A",
"B",
"C",
"D",
]);
const result2 = await v2.to_columns();
expect(result2["column"]).toEqual([
"A",
"B",
"C",
"D",
"A",
"B",
"C",
"D",
]);
await v2.delete();
await v1.delete();
await table.delete();
done();
},
{ mode: "row" },
);
await table.update(expressions_common.data);
await result3;
});
test("Partial update delta", async () => {
expect.assertions(6);
const table = await perspective.table(expressions_common.data, {
index: "x",
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
const result = await v1.to_columns();
const result2 = await v2.to_columns();
expect(result["column"]).toEqual([2, 4, 6, 8]);
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
v1.on_update(
async (updated) => {
await validate_delta("column", updated.delta, [
null,
4,
6,
8,
20,
]);
const result = await v1.to_columns();
expect(result["column"]).toEqual([
null,
2,
4,
6,
8,
20,
]);
},
{ mode: "row" },
);
let done;
let result3 = new Promise((x) => {
done = x;
});
v2.on_update(
async (updated) => {
await validate_delta("column", updated.delta, [
"DEF",
"X",
"Z",
"Y",
"ABC",
]);
const result = await v2.to_columns();
expect(result["column"]).toEqual([
"DEF",
"A",
"X",
"Z",
"Y",
"ABC",
]);
await v2.delete();
await v1.delete();
await table.delete();
done();
},
{ mode: "row" },
);
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
await result3;
});
test("Partial update, sorted delta", async () => {
expect.assertions(6);
const table = await perspective.table(expressions_common.data, {
index: "x",
});
const v1 = await table.view({
expressions: { column: `"x" * 2` },
sort: [["column", "desc"]],
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
sort: [["column", "desc"]],
});
const result = await v1.to_columns();
const result2 = await v2.to_columns();
expect(result["column"]).toEqual([8, 6, 4, 2]);
expect(result2["column"]).toEqual(["D", "C", "B", "A"]);
v1.on_update(
async (updated) => {
await validate_delta("column", updated.delta, [
20,
8,
6,
4,
null,
]);
const result = await v1.to_columns();
expect(result["column"]).toEqual([
20,
8,
6,
4,
2,
null,
]);
},
{ mode: "row" },
);
let done;
let result3 = new Promise((x) => {
done = x;
});
v2.on_update(
async (updated) => {
await validate_delta("column", updated.delta, [
"Z",
"Y",
"X",
"DEF",
"ABC",
]);
const result2 = await v2.to_columns();
expect(result2["column"]).toEqual([
"Z",
"Y",
"X",
"DEF",
"ABC",
"A",
]);
await v2.delete();
await v1.delete();
await table.delete();
done();
},
{ mode: "row" },
);
await table.update({
x: [2, 4, 3, 10, null],
y: ["X", "Y", "Z", "ABC", "DEF"],
});
await result3;
});
});
test.describe("Clear/Replace", () => {
test("Rows should clear() when called", async () => {
const now = new Date();
const bucketed = new Date(now.getUTCFullYear(), 0, 1).getTime();
const table = await perspective.table({
x: [1, 2, 3, 4],
y: ["a", "b", "c", "d"],
z: [now, now, now, now],
});
const v1 = await table.view({
columns: ["column", "column2"],
expressions: {
[`column`]: `"x" + 10`,
[`column2`]: `concat('a', 'b', 'c')`,
},
});
const v2 = await table.view({
columns: ["column2", "column"],
expressions: {
[`column`]: `upper("y")`,
[`column2`]: `bucket("z", 'Y')`,
},
});
expect(await v1.expression_schema()).toEqual({
column: "float",
column2: "string",
});
expect(await v2.expression_schema()).toEqual({
column: "string",
column2: "date",
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result["column2"]).toEqual(Array(4).fill("abc"));
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
expect(result2["column2"]).toEqual(Array(4).fill(bucketed));
await table.clear();
expect(await v1.num_rows()).toEqual(0);
expect(await v2.num_rows()).toEqual(0);
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result).toEqual({ column: [], column2: [] });
expect(result2).toEqual({ column: [], column2: [] });
await v2.delete();
await v1.delete();
await table.delete();
});
test("Rows should replace() when called", async () => {
const now = new Date();
const bucketed = new Date(now.getUTCFullYear(), 0, 1).getTime();
const table = await perspective.table({
x: [1, 2, 3, 4],
y: ["a", "b", "c", "d"],
z: [now, now, now, now],
});
const v1 = await table.view({
columns: ["column", "column2"],
expressions: {
[`column`]: `"x" + 10`,
[`column2`]: `concat('a', 'b', 'c')`,
},
});
const v2 = await table.view({
columns: ["column2", "column"],
expressions: {
[`column`]: `upper("y")`,
[`column2`]: `bucket("z", 'Y')`,
},
});
expect(await v1.expression_schema()).toEqual({
column: "float",
column2: "string",
});
expect(await v2.expression_schema()).toEqual({
column: "string",
column2: "date",
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result["column2"]).toEqual(Array(4).fill("abc"));
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
expect(result2["column2"]).toEqual(Array(4).fill(bucketed));
await table.replace({
x: [100, 300],
y: ["x", "y"],
z: [now, now],
});
expect(await v1.num_rows()).toEqual(2);
expect(await v2.num_rows()).toEqual(2);
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([110, 310]);
expect(result["column2"]).toEqual(["abc", "abc"]);
expect(result2["column"]).toEqual(["X", "Y"]);
expect(result2["column2"]).toEqual([bucketed, bucketed]);
await v2.delete();
await v1.delete();
await table.delete();
});
});
test.describe("Remove", () => {
test("Rows should remove() when called", async () => {
const now = new Date();
const bucketed = new Date(now.getUTCFullYear(), 0, 1).getTime();
const table = await perspective.table(
{
x: [1, 2, 3, 4],
y: ["a", "b", "c", "d"],
z: [now, now, now, now],
},
{ index: "x" },
);
const v1 = await table.view({
columns: ["column", "column2"],
expressions: {
[`column`]: `"x" + 10`,
[`column2`]: `concat('a', 'b', 'c')`,
},
});
const v2 = await table.view({
columns: ["column2", "column"],
expressions: {
[`column`]: `upper("y")`,
[`column2`]: `bucket("z", 'Y')`,
},
});
expect(await v1.expression_schema()).toEqual({
column: "float",
column2: "string",
});
expect(await v2.expression_schema()).toEqual({
column: "string",
column2: "date",
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result["column2"]).toEqual(Array(4).fill("abc"));
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
expect(result2["column2"]).toEqual(Array(4).fill(bucketed));
await table.remove([2, 3]);
// FIXME: size() and num_rows() do not respond correctly to
// remove() calls.
// expect(await v1.num_rows()).toEqual(2);
// expect(await v2.num_rows()).toEqual(2);
result = await v1.to_columns();
result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 14]);
expect(result["column2"]).toEqual(["abc", "abc"]);
expect(result2["column"]).toEqual(["A", "D"]);
expect(result2["column2"]).toEqual([bucketed, bucketed]);
await v2.delete();
await v1.delete();
await table.delete();
});
});
test("Multiple views with the same expression alias should not conflict", async () => {
const now = new Date();
const bucketed = new Date(now.getUTCFullYear(), 0, 1).getTime();
const table = await perspective.table({
x: [1, 2, 3, 4],
y: ["a", "b", "c", "d"],
z: [now, now, now, now],
});
const v1 = await table.view({
columns: ["column", "column2"],
expressions: {
[`column`]: `"x" + 10`,
[`column2`]: `concat('a', 'b', 'c')`,
},
});
const v2 = await table.view({
columns: ["column2", "column"],
expressions: {
[`column`]: `upper("y")`,
[`column2`]: `bucket("z", 'Y')`,
},
});
expect(await v1.expression_schema()).toEqual({
column: "float",
column2: "string",
});
expect(await v2.expression_schema()).toEqual({
column: "string",
column2: "date",
});
const result = await v1.to_columns();
const result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result["column2"]).toEqual(Array(4).fill("abc"));
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
expect(result2["column2"]).toEqual(Array(4).fill(bucketed));
await v2.delete();
await v1.delete();
await table.delete();
});
test("Multiple views with the same expression alias should not conflict, to_arrow", async () => {
const table = await perspective.table(expressions_common.data);
const v1 = await table.view({
expressions: { column: `"x" + 10` },
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
});
let result = await v1.to_columns();
let result2 = await v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
const t1 = await perspective.table(await v1.to_arrow());
const t2 = await perspective.table(await v2.to_arrow());
const new_v1 = await t1.view();
const new_v2 = await t2.view();
result = await new_v1.to_columns();
result2 = await new_v2.to_columns();
expect(result["column"]).toEqual([11, 12, 13, 14]);
expect(result2["column"]).toEqual(["A", "B", "C", "D"]);
await new_v2.delete();
await new_v1.delete();
await t2.delete();
await t1.delete();
await v2.delete();
await v1.delete();
await table.delete();
});
test("Multiple views with the same expression alias should not conflict, filtered", async () => {
const table = await perspective.table(expressions_common.data);
const v1 = await table.view({
expressions: { column: `"x" + 10` },
filter: [["column", "==", 12]],
});
const v2 = await table.view({
expressions: { column: `upper("y")` },
filter: [["column", "==", "D"]],
});
const result = await v1.to_columns();
const result2 = await v2.to_columns();
expect(result["column"]).toEqual([12]);
expect(result2["column"]).toEqual(["D"]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Multiple pivoted views with the same expression alias should not conflict", async () => {
const table = await perspective.table(expressions_common.data);
const v1 = await table.view({
group_by: ["y"],
split_by: ["x"],
expressions: { column: `"x" + 10` },
});
const v2 = await table.view({
group_by: ["x"],
split_by: ["y"],
expressions: { column: `upper("y")` },
aggregates: {
column: "last",
},
});
const result = await v1.to_columns();
const result2 = await v2.to_columns();
expect(result["1|column"]).toEqual([11, 11, null, null, null]);
expect(result["2|column"]).toEqual([12, null, 12, null, null]);
expect(result["3|column"]).toEqual([13, null, null, 13, null]);
expect(result["4|column"]).toEqual([14, null, null, null, 14]);
expect(result2["a|column"]).toEqual(["A", "A", null, null, null]);
expect(result2["b|column"]).toEqual(["B", null, "B", null, null]);
expect(result2["c|column"]).toEqual(["C", null, null, "C", null]);
expect(result2["d|column"]).toEqual(["D", null, null, null, "D"]);
await v2.delete();
await v1.delete();
await table.delete();
});
test("Multiple pivoted views with the same expression alias and different aggregates should not conflict", async () => {
const table = await perspective.table({
x: [10, 10, 20, 20],
y: ["A", "B", "C", "D"],
z: [1.5, 2.5, 3.5, 4.5],
});
const v1 = await table.view({
group_by: ["x"],
expressions: { column: `"z" + 10` },
aggregates: {
column: "avg",
},
});
const v2 = await table.view({
group_by: ["x"],
expressions: { column: `upper("y")` },
aggregates: {
column: "last",
},
});
const v3 = await table.view({
group_by: ["x"],
expressions: { column: `2 * "z"` },
aggregates: {
column: ["weighted mean", ["z"]],
},
});
const result = await v1.to_columns();
const result2 = await v2.to_columns();
const result3 = await v3.to_columns();
expect(result["column"]).toEqual([13, 12, 14]);
expect(result2["column"]).toEqual(["D", "B", "D"]);
expect(result3["column"]).toEqual([6.833333333333333, 4.25, 8.125]);
await v3.delete();
await v2.delete();
await v1.delete();
await table.delete();
});
test("Multiple 2-sided pivoted views with the same expression alias and different aggregates should not conflict", async () => {
const table = await perspective.table({
x: [10, 10, 20, 20],
y: ["A", "B", "C", "D"],
z: [1.5, 2.5, 3.5, 4.5],
});
const v1 = await table.view({
group_by: ["x"],
split_by: ["y"],
expressions: { column: `"z" + 10` },
aggregates: {
column: "avg",
},
});
const v2 = await table.view({
group_by: ["x"],
split_by: ["y"],
expressions: { column: `upper("y")` },
aggregates: {
column: "last",
},
});
const v3 = await table.view({
group_by: ["x"],
split_by: ["y"],
expressions: { column: `2 * "z"` },
aggregates: {
column: ["weighted mean", ["z"]],
},
});
const result = await v1.to_columns();
const result2 = await v2.to_columns();
const result3 = await v3.to_columns();
expect(result).toEqual({
__ROW_PATH__: [[], [10], [20]],
"A|x": [10, 10, null],
"A|y": [1, 1, null],
"A|z": [1.5, 1.5, null],
"A|column": [11.5, 11.5, null],
"B|x": [10, 10, null],
"B|y": [1, 1, null],
"B|z": [2.5, 2.5, null],
"B|column": [12.5, 12.5, null],
"C|x": [20, null, 20],
"C|y": [1, null, 1],
"C|z": [3.5, null, 3.5],
"C|column": [13.5, null, 13.5],
"D|x": [20, null, 20],
"D|y": [1, null, 1],
"D|z": [4.5, null, 4.5],
"D|column": [14.5, null, 14.5],
});
expect(result2).toEqual({
__ROW_PATH__: [[], [10], [20]],
"A|x": [10, 10, null],
"A|y": [1, 1, null],
"A|z": [1.5, 1.5, null],
"A|column": ["A", "A", null],
"B|x": [10, 10, null],
"B|y": [1, 1, null],
"B|z": [2.5, 2.5, null],
"B|column": ["B", "B", null],
"C|x": [20, null, 20],
"C|y": [1, null, 1],
"C|z": [3.5, null, 3.5],
"C|column": ["C", null, "C"],
"D|x": [20, null, 20],
"D|y": [1, null, 1],
"D|z": [4.5, null, 4.5],
"D|column": ["D", null, "D"],
});
expect(result3).toEqual({
__ROW_PATH__: [[], [10], [20]],
"A|x": [10, 10, null],
"A|y": [1, 1, null],
"A|z": [1.5, 1.5, null],
"A|column": [3, 3, null],
"B|x": [10, 10, null],
"B|y": [1, 1, null],
"B|z": [2.5, 2.5, null],
"B|column": [5, 5, null],
"C|x": [20, null, 20],
"C|y": [1, null, 1],
"C|z": [3.5, null, 3.5],
"C|column": [7, null, 7],
"D|x": [20, null, 20],
"D|y": [1, null, 1],
"D|z": [4.5, null, 4.5],
"D|column": [9, null, 9],
});
await v3.delete();
await v2.delete();
await v1.delete();
await table.delete();
});
});
})(perspective);