1473 lines
52 KiB
JavaScript
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);
|