1471 lines
53 KiB
JavaScript
1471 lines
53 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 data = [
|
|
{ x: 1, y: 2 },
|
|
{ x: 2, y: 4 },
|
|
{ x: 3, y: 6 },
|
|
{ x: 4, y: 8 },
|
|
];
|
|
|
|
const pivot_data = [
|
|
{ int: 1, float: 2.25 },
|
|
{ int: 2, float: 3.5 },
|
|
{ int: 3, float: 4.75 },
|
|
{ int: 4, float: 5.25 },
|
|
];
|
|
|
|
/**
|
|
* Tests the correctness of updates on Tables with expression columns created
|
|
* through `View`, including partial updates, appends, and removes.
|
|
*/
|
|
((perspective) => {
|
|
test.describe("Expression updates", function () {
|
|
test("Scalar-only expressions should respond to appends", async function () {
|
|
const table = await perspective.table({
|
|
x: [1.5, 2.5, 3.5, 4.5],
|
|
y: ["A", "B", "C", "D"],
|
|
});
|
|
const view = await table.view({
|
|
expressions: [
|
|
"10 + 20",
|
|
"lower('ABC')",
|
|
"concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const before = await view.to_columns();
|
|
expect(before["10 + 20"]).toEqual([30, 30, 30, 30]);
|
|
expect(before["lower('ABC')"]).toEqual([
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
]);
|
|
expect(
|
|
before[
|
|
"concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"
|
|
],
|
|
).toEqual([
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
]);
|
|
await table.update({ x: [5, 6, 7] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after["10 + 20"]).toEqual([30, 30, 30, 30, 30, 30, 30]);
|
|
expect(after["lower('ABC')"]).toEqual([
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
]);
|
|
expect(
|
|
after[
|
|
"concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"
|
|
],
|
|
).toEqual([
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Conditional expressions should respond to appends", async function () {
|
|
const table = await perspective.table({
|
|
x: [1.5, 2.5, 3.5, 4.5],
|
|
y: ["A", "B", "C", "D"],
|
|
});
|
|
const view = await table.view({
|
|
// conditional here must be float, as int-float comparisons
|
|
// don't work as of yet.
|
|
expressions: [
|
|
'if ("x" > 4) 10; else 100',
|
|
`"y" == 'A' ? true : false`,
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const before = await view.to_columns();
|
|
expect(before['if ("x" > 4) 10; else 100']).toEqual([
|
|
100, 100, 100, 10,
|
|
]);
|
|
expect(before[`"y" == 'A' ? true : false`]).toEqual([
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
|
|
await table.update({ x: [5, 6, 7], y: ["A", "A", "B"] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['if ("x" > 4) 10; else 100']).toEqual([
|
|
100, 100, 100, 10, 10, 10, 10,
|
|
]);
|
|
expect(after[`"y" == 'A' ? true : false`]).toEqual([
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
]);
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Scalar-only expressions should respond to partial updates", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
x: [1.5, 2.5, 3.5, 4.5],
|
|
y: ["A", "B", "C", "D"],
|
|
},
|
|
{ index: "y" },
|
|
);
|
|
const view = await table.view({
|
|
expressions: [
|
|
"10 + 20",
|
|
"lower('ABC')",
|
|
"concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')",
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
expect(before["10 + 20"]).toEqual([30, 30, 30, 30]);
|
|
expect(before["lower('ABC')"]).toEqual([
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
"abc",
|
|
]);
|
|
expect(
|
|
before[
|
|
"concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"
|
|
],
|
|
).toEqual([
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
]);
|
|
|
|
await table.update({ x: [5, 6, 7], y: ["A", "B", "D"] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after["10 + 20"]).toEqual([30, 30, 30, 30]);
|
|
expect(after["lower('ABC')"]).toEqual(["abc", "abc", "abc", "abc"]);
|
|
expect(
|
|
after[
|
|
"concat('hello', ' ', 'world', ', ', 'here is a long, long, long string with lots of characters')"
|
|
],
|
|
).toEqual([
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
"hello world, here is a long, long, long string with lots of characters",
|
|
]);
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Expressions should respond to multiple streaming updates", async function () {
|
|
const table = await perspective.table({
|
|
x: [1.5, 2.5, 3.5, 4.5],
|
|
y: ["A", "B", "C", "D"],
|
|
});
|
|
const view = await table.view({
|
|
expressions: {
|
|
123: "123",
|
|
c0: '10 + "x"',
|
|
c1: 'lower("y")',
|
|
c2: `concat("y", ' ', 'abcd')`,
|
|
},
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
expect(before["123"]).toEqual(Array(4).fill(123));
|
|
expect(before["c0"]).toEqual([11.5, 12.5, 13.5, 14.5]);
|
|
expect(before["c1"]).toEqual(["a", "b", "c", "d"]);
|
|
expect(before["c2"]).toEqual([
|
|
"A abcd",
|
|
"B abcd",
|
|
"C abcd",
|
|
"D abcd",
|
|
]);
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
await table.update({
|
|
x: [1.5, 2.5, 3.5, 4.5],
|
|
y: ["A", "B", "C", "D"],
|
|
});
|
|
}
|
|
|
|
const after = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(24);
|
|
console.log(after);
|
|
expect(after["123"]).toEqual(Array(24).fill(123));
|
|
expect(after["c0"]).toEqual([
|
|
11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5, 11.5, 12.5,
|
|
13.5, 14.5, 11.5, 12.5, 13.5, 14.5, 11.5, 12.5, 13.5, 14.5,
|
|
11.5, 12.5, 13.5, 14.5,
|
|
]);
|
|
expect(after["c1"]).toEqual([
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
]);
|
|
expect(after["c2"]).toEqual([
|
|
"A abcd",
|
|
"B abcd",
|
|
"C abcd",
|
|
"D abcd",
|
|
"A abcd",
|
|
"B abcd",
|
|
"C abcd",
|
|
"D abcd",
|
|
"A abcd",
|
|
"B abcd",
|
|
"C abcd",
|
|
"D abcd",
|
|
"A abcd",
|
|
"B abcd",
|
|
"C abcd",
|
|
"D abcd",
|
|
"A abcd",
|
|
"B abcd",
|
|
"C abcd",
|
|
"D abcd",
|
|
"A abcd",
|
|
"B abcd",
|
|
"C abcd",
|
|
"D abcd",
|
|
]);
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Conditional expressions should respond to partial updates", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
x: [1.5, 2.5, 3.5, 4.5],
|
|
y: ["A", "B", "C", "D"],
|
|
z: ["a", "b", "c", "d"],
|
|
},
|
|
{ index: "y" },
|
|
);
|
|
const view = await table.view({
|
|
expressions: [
|
|
'if ("x" > 4) 10; else 100',
|
|
`"z" == 'a' ? true : false`,
|
|
].reduce((x, y) => Object.assign(x, { [y]: y }), {}),
|
|
});
|
|
const before = await view.to_columns();
|
|
expect(before['if ("x" > 4) 10; else 100']).toEqual([
|
|
100, 100, 100, 10,
|
|
]);
|
|
expect(before[`"z" == 'a' ? true : false`]).toEqual([
|
|
true,
|
|
false,
|
|
false,
|
|
false,
|
|
]);
|
|
await table.update({
|
|
x: [5, 6, 7],
|
|
y: ["A", "C", "D"],
|
|
z: ["a", "a", "a"],
|
|
});
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['if ("x" > 4) 10; else 100']).toEqual([
|
|
10, 100, 10, 10,
|
|
]);
|
|
expect(after[`"z" == 'a' ? true : false`]).toEqual([
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Appends should notify expression column", async function () {
|
|
const table = await perspective.table({
|
|
x: [1, 2, 3, 4],
|
|
y: ["A", "B", "C", "D"],
|
|
});
|
|
const view = await table.view({
|
|
expressions: { 'lower("y")': 'lower("y")' },
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
expect(before['lower("y")']).toEqual(["a", "b", "c", "d"]);
|
|
await table.update({ y: ["HELLO", "WORLD"] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['lower("y")']).toEqual([
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"hello",
|
|
"world",
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Multiple appends should notify expression column", async function () {
|
|
const table = await perspective.table({
|
|
x: [1, 2, 3, 4],
|
|
y: ["A", "B", "C", "D"],
|
|
});
|
|
const view = await table.view({
|
|
expressions: { 'lower("y")': 'lower("y")' },
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
expect(before['lower("y")']).toEqual(["a", "b", "c", "d"]);
|
|
await table.update({ y: ["HELLO", "WORLD"] });
|
|
|
|
const result = await view.to_columns();
|
|
expect(result['lower("y")']).toEqual([
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"hello",
|
|
"world",
|
|
]);
|
|
|
|
await table.update({ x: [12, 34], y: ["XYZ", "ABCdEFg"] });
|
|
|
|
const result2 = await view.to_columns();
|
|
expect(result2["x"]).toEqual([1, 2, 3, 4, null, null, 12, 34]);
|
|
expect(result2['lower("y")']).toEqual([
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"hello",
|
|
"world",
|
|
"xyz",
|
|
"abcdefg",
|
|
]);
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Appends should notify expression column with multiple columns", async function () {
|
|
const table = await perspective.table({
|
|
x: [1, 2, 3, 4],
|
|
y: [1.5, 2.5, 3.5, 4.5],
|
|
z: [2, 4, 6, 8],
|
|
});
|
|
const view = await table.view({
|
|
expressions: {
|
|
'"x" + ("y" + 5.5) / "z"': '"x" + ("y" + 5.5) / "z"',
|
|
},
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
expect(before['"x" + ("y" + 5.5) / "z"']).toEqual([
|
|
4.5, 4, 4.5, 5.25,
|
|
]);
|
|
|
|
await table.update({
|
|
x: [5, 6, 7, 8],
|
|
y: [5.5, 6.5, 7.5, 8.5],
|
|
z: [10, 10, 10, 10],
|
|
});
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['"x" + ("y" + 5.5) / "z"']).toEqual([
|
|
4.5, 4, 4.5, 5.25, 6.1, 7.2, 8.3, 9.4,
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Partial updates should notify expression column", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
x: [1, 2, 3, 4],
|
|
y: ["A", "B", "C", "D"],
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
const view = await table.view({
|
|
expressions: { 'lower("y")': 'lower("y")' },
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
expect(before['lower("y")']).toEqual(["a", "b", "c", "d"]);
|
|
await table.update({ x: [1, 3], y: ["HELLO", "WORLD"] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['lower("y")']).toEqual(["hello", "b", "world", "d"]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column appends with missing columns should notify expression columns", async function () {
|
|
const table = await perspective.table({
|
|
x: [1, 2, 3, 4],
|
|
y: [1.5, 2.5, 3.5, 4.5],
|
|
z: ["a", "b", "c", "d"],
|
|
});
|
|
const view = await table.view({
|
|
expressions: { 'upper("z")': 'upper("z")' },
|
|
});
|
|
const before = await view.to_columns();
|
|
expect(before['upper("z")']).toEqual(["A", "B", "C", "D"]);
|
|
|
|
// `z` is missing in the update, so it should render as null
|
|
await table.update({ x: [2, 4], y: [10.5, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after).toEqual({
|
|
x: [1, 2, 3, 4, 2, 4],
|
|
y: [1.5, 2.5, 3.5, 4.5, 10.5, 12.5],
|
|
z: ["a", "b", "c", "d", null, null],
|
|
'upper("z")': ["A", "B", "C", "D", null, null],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column update appends should notify expression columns, pivoted arity 1", async function () {
|
|
const table = await perspective.table({
|
|
x: [1, 2, 3, 4],
|
|
y: ["A", "B", "C", "C"],
|
|
});
|
|
const view = await table.view({
|
|
group_by: ['lower("y")'],
|
|
expressions: { 'lower("y")': 'lower("y")' },
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
expect(before).toEqual({
|
|
__ROW_PATH__: [[], ["a"], ["b"], ["c"]],
|
|
'lower("y")': [4, 1, 1, 2],
|
|
x: [10, 1, 2, 7],
|
|
y: [4, 1, 1, 2],
|
|
});
|
|
|
|
await table.update({ y: ["HELLO", "WORLD"] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after).toEqual({
|
|
__ROW_PATH__: [[], ["a"], ["b"], ["c"], ["hello"], ["world"]],
|
|
'lower("y")': [6, 1, 1, 2, 1, 1],
|
|
x: [10, 1, 2, 7, 0, 0],
|
|
y: [6, 1, 1, 2, 1, 1],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column appends should notify expression columns, arity 2", async function () {
|
|
const table = await perspective.table(common.int_float_data);
|
|
const view = await table.view({
|
|
expressions: { '"w" + "x"': '"w" + "x"' },
|
|
});
|
|
const before = await view.to_columns();
|
|
expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
|
|
await table.update({ x: [2, 4], w: [10.5, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['"w" + "x"']).toEqual([
|
|
2.5, 4.5, 6.5, 8.5, 12.5, 16.5,
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column updates on all column updates should notify expression columns, arity 2", async function () {
|
|
const table = await perspective.table(common.int_float_data, {
|
|
index: "x",
|
|
});
|
|
const view = await table.view({
|
|
expressions: {
|
|
'"w" + "x"': '"w" + "x"',
|
|
'upper("y")': 'upper("y")',
|
|
},
|
|
});
|
|
let before = await view.to_columns();
|
|
expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before['upper("y")']).toEqual(["A", "B", "C", "D"]);
|
|
|
|
await table.update({ x: [2, 4], w: [10.5, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['"w" + "x"']).toEqual([2.5, 12.5, 6.5, 16.5]);
|
|
|
|
await table.update({ x: [1, 3], y: ["hello", "world"] });
|
|
|
|
const after2 = await view.to_columns();
|
|
expect(after2['upper("y")']).toEqual(["HELLO", "B", "WORLD", "D"]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column appends should notify expression columns on different views, arity 2", async function () {
|
|
const table = await perspective.table(common.int_float_data);
|
|
const view = await table.view({
|
|
expressions: { '"w" + "x"': '"w" + "x"' },
|
|
});
|
|
|
|
const view2 = await table.view({
|
|
expressions: { '"w" - "x"': '"w" - "x"' },
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
const before2 = await view2.to_columns();
|
|
expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5]);
|
|
|
|
await table.update({ x: [2, 4], w: [10.5, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
const after2 = await view2.to_columns();
|
|
expect(after['"w" + "x"']).toEqual([
|
|
2.5, 4.5, 6.5, 8.5, 12.5, 16.5,
|
|
]);
|
|
expect(after2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5, 8.5, 8.5]);
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column appends should notify equivalent expression columns on different views, arity 2", async function () {
|
|
const table = await perspective.table(common.int_float_data);
|
|
const view = await table.view({
|
|
expressions: { '"w" + "x"': '"w" + "x"' },
|
|
});
|
|
|
|
const view2 = await table.view({
|
|
expressions: {
|
|
'"w" + "x"': '"w" + "x"',
|
|
'"w" - "x"': '"w" - "x"',
|
|
},
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
const before2 = await view2.to_columns();
|
|
expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5]);
|
|
|
|
await table.update({ x: [2, 4], w: [10.5, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
const after2 = await view2.to_columns();
|
|
expect(after['"w" + "x"']).toEqual([
|
|
2.5, 4.5, 6.5, 8.5, 12.5, 16.5,
|
|
]);
|
|
expect(after2['"w" + "x"']).toEqual([
|
|
2.5, 4.5, 6.5, 8.5, 12.5, 16.5,
|
|
]);
|
|
expect(after2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5, 8.5, 8.5]);
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column updates should notify expression columns on different views, arity 2.", async function () {
|
|
const table = await perspective.table(common.int_float_data, {
|
|
index: "x",
|
|
});
|
|
const view = await table.view({
|
|
expressions: { '"w" + "x"': '"w" + "x"' },
|
|
});
|
|
|
|
const view2 = await table.view({
|
|
expressions: { '"w" - "x"': '"w" - "x"' },
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
const before2 = await view2.to_columns();
|
|
expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5]);
|
|
|
|
await table.update({ x: [2, 4], w: [10.5, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
const after2 = await view2.to_columns();
|
|
expect(after['"w" + "x"']).toEqual([2.5, 12.5, 6.5, 16.5]);
|
|
expect(after2['"w" - "x"']).toEqual([0.5, 8.5, 0.5, 8.5]);
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column updates should notify equivalent expression columns on different views, arity 2.", async function () {
|
|
const table = await perspective.table(common.int_float_data, {
|
|
index: "x",
|
|
});
|
|
const view = await table.view({
|
|
expressions: { '"w" + "x"': '"w" + "x"' },
|
|
});
|
|
|
|
const view2 = await table.view({
|
|
expressions: {
|
|
'"w" + "x"': '"w" + "x"',
|
|
'"w" - "x"': '"w" - "x"',
|
|
},
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
const before2 = await view2.to_columns();
|
|
expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5]);
|
|
|
|
await table.update({ x: [2, 4], w: [10.5, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
const after2 = await view2.to_columns();
|
|
expect(after['"w" + "x"']).toEqual([2.5, 12.5, 6.5, 16.5]);
|
|
expect(after2['"w" + "x"']).toEqual([2.5, 12.5, 6.5, 16.5]);
|
|
expect(after2['"w" - "x"']).toEqual([0.5, 8.5, 0.5, 8.5]);
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column update with `null` should notify expression columns on different views, arity 2.", async function () {
|
|
const table = await perspective.table(common.int_float_data, {
|
|
index: "x",
|
|
});
|
|
const view = await table.view({
|
|
expressions: { '"w" + "x"': '"w" + "x"' },
|
|
});
|
|
|
|
const view2 = await table.view({
|
|
expressions: { '"w" - "x"': '"w" - "x"' },
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
const before2 = await view2.to_columns();
|
|
expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5]);
|
|
|
|
await table.update({ x: [2, 4], w: [null, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
const after2 = await view2.to_columns();
|
|
expect(after['"w" + "x"']).toEqual([2.5, null, 6.5, 16.5]);
|
|
expect(after2['"w" - "x"']).toEqual([0.5, null, 0.5, 8.5]);
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Dependent column update with `null` should notify equivalent expression columns on different views, arity 2.", async function () {
|
|
const table = await perspective.table(common.int_float_data, {
|
|
index: "x",
|
|
});
|
|
const view = await table.view({
|
|
expressions: { '"w" + "x"': '"w" + "x"' },
|
|
});
|
|
|
|
const view2 = await table.view({
|
|
expressions: {
|
|
'"w" + "x"': '"w" + "x"',
|
|
'"w" - "x"': '"w" - "x"',
|
|
},
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
const before2 = await view2.to_columns();
|
|
expect(before['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" + "x"']).toEqual([2.5, 4.5, 6.5, 8.5]);
|
|
expect(before2['"w" - "x"']).toEqual([0.5, 0.5, 0.5, 0.5]);
|
|
|
|
await table.update({ x: [2, 4], w: [null, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
const after2 = await view2.to_columns();
|
|
expect(after['"w" + "x"']).toEqual([2.5, null, 6.5, 16.5]);
|
|
expect(after2['"w" + "x"']).toEqual([2.5, null, 6.5, 16.5]);
|
|
expect(after2['"w" - "x"']).toEqual([0.5, null, 0.5, 8.5]);
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Updating with `null` should clear the output expression column.", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
w: [1.5, 2.5, 3.5, 4.5],
|
|
x: [1, 2, 3, 4],
|
|
y: [5, 6, 7, 8],
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
|
|
const view = await table.view({
|
|
expressions: { '"w" + "y"': '"w" + "y"' },
|
|
});
|
|
|
|
let before = await view.to_columns();
|
|
expect(before['"w" + "y"']).toEqual([6.5, 8.5, 10.5, 12.5]);
|
|
await table.update({ x: [2, 4], w: [null, 12.5] });
|
|
const after = await view.to_columns();
|
|
expect(after['"w" + "y"']).toEqual([6.5, null, 10.5, 20.5]);
|
|
await table.update({ x: [2, 3], w: [20.5, null] });
|
|
const after2 = await view.to_columns();
|
|
expect(after2['"w" + "y"']).toEqual([6.5, 26.5, null, 20.5]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("OG - Updating with `undefined` should not clear the output expression column.", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
w: [1.5, 2.5, 3.5, 4.5],
|
|
x: [1, 2, 3, 4],
|
|
y: [5, 6, 7, 8],
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
const view = await table.view({
|
|
expressions: { '"w" + "y"': '"w" + "y"' },
|
|
});
|
|
let before = await view.to_columns();
|
|
expect(before['"w" + "y"']).toEqual([6.5, 8.5, 10.5, 12.5]);
|
|
|
|
await table.update({ x: [2, 4], w: [undefined, 12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['"w" + "y"']).toEqual([6.5, 8.5, 10.5, 20.5]);
|
|
|
|
await table.update({ x: [2, 3], w: [20.5, undefined] });
|
|
|
|
const after2 = await view.to_columns();
|
|
expect(after2['"w" + "y"']).toEqual([6.5, 26.5, 10.5, 20.5]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Updating with `undefined` should not clear the output expression column.", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
w: [1.5, 2.5, 3.5, 4.5],
|
|
x: [1, 2, 3, 4],
|
|
y: [5, 6, 7, 8],
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
const view = await table.view({
|
|
expressions: { '"w" + "y"': '"w" + "y"' },
|
|
});
|
|
let before = await view.to_columns();
|
|
expect(before['"w" + "y"']).toEqual([6.5, 8.5, 10.5, 12.5]);
|
|
|
|
await table.update({ x: [2] });
|
|
await table.update({ x: [4], w: [12.5] });
|
|
|
|
const after = await view.to_columns();
|
|
expect(after['"w" + "y"']).toEqual([6.5, 8.5, 10.5, 20.5]);
|
|
|
|
await table.update({ x: [2], w: [20.5] });
|
|
await table.update({ x: [3] });
|
|
|
|
const after2 = await view.to_columns();
|
|
expect(after2['"w" + "y"']).toEqual([6.5, 26.5, 10.5, 20.5]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Updates on non-dependent columns should not change expression columns.", async function () {
|
|
var meta = {
|
|
w: "float",
|
|
x: "float",
|
|
y: "string",
|
|
z: "boolean",
|
|
};
|
|
const table = await perspective.table(meta, { index: "y" });
|
|
const view = await table.view({
|
|
columns: ["y", '"w" / "x"'],
|
|
expressions: { '"w" / "x"': '"w" / "x"' },
|
|
});
|
|
|
|
await table.update(common.int_float_data);
|
|
|
|
let delta_upd = [
|
|
{ y: "a", z: false },
|
|
{ y: "b", z: true },
|
|
{ y: "c", z: false },
|
|
{ y: "d", z: true },
|
|
];
|
|
await table.update(delta_upd);
|
|
let result = await view.to_json();
|
|
let expected = [
|
|
{ y: "a", '"w" / "x"': 1.5 },
|
|
{ y: "b", '"w" / "x"': 1.25 },
|
|
{ y: "c", '"w" / "x"': 1.1666666666666667 },
|
|
{ y: "d", '"w" / "x"': 1.125 },
|
|
];
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Should recompute after partial update using `__INDEX__`", async function () {
|
|
const table = await perspective.table({
|
|
x: "integer",
|
|
y: "integer",
|
|
});
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
|
|
await table.update(data);
|
|
await table.update([
|
|
{ __INDEX__: 0, x: 10 },
|
|
{ __INDEX__: 2, x: 10 },
|
|
]);
|
|
|
|
const json = await view.to_json();
|
|
expect(json).toEqual([
|
|
{ x: 10, y: 2, '"x" * "y"': 20 },
|
|
{ x: 2, y: 4, '"x" * "y"': 8 },
|
|
{ x: 10, y: 6, '"x" * "y"': 60 },
|
|
{ x: 4, y: 8, '"x" * "y"': 32 },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Partial update without a new value shouldn't change computed output", async function () {
|
|
const table = await perspective.table(data);
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
|
|
const json = await view.to_json();
|
|
expect(json).toEqual([
|
|
{ x: 1, y: 2, '"x" * "y"': 2 },
|
|
{ x: 2, y: 4, '"x" * "y"': 8 },
|
|
{ x: 3, y: 6, '"x" * "y"': 18 },
|
|
{ x: 4, y: 8, '"x" * "y"': 32 },
|
|
]);
|
|
|
|
await table.update([{ __INDEX__: 0, x: 1 }]);
|
|
const json2 = await view.to_json();
|
|
expect(json2).toEqual(json);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("partial update on single computed source column", async function () {
|
|
const table = await perspective.table(data);
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
|
|
await table.update([{ __INDEX__: 0, x: 10 }]);
|
|
const json = await view.to_json();
|
|
expect(json).toEqual([
|
|
{ x: 10, y: 2, '"x" * "y"': 20 },
|
|
{ x: 2, y: 4, '"x" * "y"': 8 },
|
|
{ x: 3, y: 6, '"x" * "y"': 18 },
|
|
{ x: 4, y: 8, '"x" * "y"': 32 },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("partial update on non-contiguous computed source columns", async function () {
|
|
const table = await perspective.table(data);
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
await table.update([
|
|
{ __INDEX__: 0, x: 1, y: 10 },
|
|
{ __INDEX__: 2, x: 3, y: 20 },
|
|
]);
|
|
let json = await view.to_json();
|
|
expect(json).toEqual([
|
|
{ x: 1, y: 10, '"x" * "y"': 10 },
|
|
{ x: 2, y: 4, '"x" * "y"': 8 },
|
|
{ x: 3, y: 20, '"x" * "y"': 60 },
|
|
{ x: 4, y: 8, '"x" * "y"': 32 },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("partial update on non-contiguous computed source columns, indexed table", async function () {
|
|
const table = await perspective.table(data, { index: "x" });
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
await table.update([
|
|
{ x: 1, y: 10 },
|
|
{ x: 3, y: 20 },
|
|
]);
|
|
let json = await view.to_json();
|
|
expect(json).toEqual([
|
|
{ x: 1, y: 10, '"x" * "y"': 10 },
|
|
{ x: 2, y: 4, '"x" * "y"': 8 },
|
|
{ x: 3, y: 20, '"x" * "y"': 60 },
|
|
{ x: 4, y: 8, '"x" * "y"': 32 },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("multiple updates change validity", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
a: "float",
|
|
b: "float",
|
|
c: "string",
|
|
index: "integer",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
|
|
await table.update({
|
|
a: [null, null, 1.5, null],
|
|
b: [null, 2.5, null, null],
|
|
c: ["a", "b", "b", "a"],
|
|
index: [1, 2, 3, 4],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: '"a" + "b"' },
|
|
});
|
|
|
|
let result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([null, null, null, null]);
|
|
|
|
await table.update({
|
|
index: [4],
|
|
a: [100],
|
|
});
|
|
|
|
result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([null, null, null, null]);
|
|
|
|
await table.update({
|
|
index: [4],
|
|
b: [100],
|
|
});
|
|
|
|
result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([null, null, null, 200]);
|
|
|
|
await table.update({
|
|
index: [3],
|
|
b: [100],
|
|
});
|
|
|
|
result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([null, null, 101.5, 200]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("multiple partial update on single computed source column", async function () {
|
|
const table = await perspective.table(data);
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
|
|
await table.update([
|
|
{ __INDEX__: 0, x: 10 },
|
|
{ __INDEX__: 2, x: 10 },
|
|
]);
|
|
await table.update([
|
|
{ __INDEX__: 0, x: 20 },
|
|
{ __INDEX__: 2, x: 20 },
|
|
]);
|
|
await table.update([
|
|
{ __INDEX__: 0, x: 30 },
|
|
{ __INDEX__: 2, x: 30 },
|
|
]);
|
|
|
|
let json = await view.to_json();
|
|
expect(json).toEqual([
|
|
{ x: 30, y: 2, '"x" * "y"': 60 },
|
|
{ x: 2, y: 4, '"x" * "y"': 8 },
|
|
{ x: 30, y: 6, '"x" * "y"': 180 },
|
|
{ x: 4, y: 8, '"x" * "y"': 32 },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("multiple expression columns with updates on source columns", async function () {
|
|
const table = await perspective.table(data);
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
const view2 = await table.view({
|
|
expressions: { '"x" + "y"': '"x" + "y"' },
|
|
});
|
|
|
|
await table.update([
|
|
{ __INDEX__: 0, x: 5 },
|
|
{ __INDEX__: 2, x: 10 },
|
|
]);
|
|
|
|
let result = await view.to_columns();
|
|
let result2 = await view2.to_columns();
|
|
|
|
expect(result).toEqual({
|
|
x: [5, 2, 10, 4],
|
|
y: [2, 4, 6, 8],
|
|
'"x" * "y"': [10, 8, 60, 32],
|
|
});
|
|
|
|
expect(result2).toEqual({
|
|
x: [5, 2, 10, 4],
|
|
y: [2, 4, 6, 8],
|
|
'"x" + "y"': [7, 6, 16, 12],
|
|
});
|
|
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("propagate updates to all expression columns", async function () {
|
|
const table = await perspective.table(data, { index: "x" });
|
|
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
|
|
const view2 = await table.view({
|
|
expressions: { '"x" + "y"': '"x" + "y"' },
|
|
});
|
|
|
|
const view3 = await table.view({
|
|
expressions: { '"x" - "y"': '"x" - "y"' },
|
|
});
|
|
|
|
await table.update({ x: [1, 2, 3, 4], y: [1, 2, 3, 4] });
|
|
|
|
let result = await view.to_columns();
|
|
let result2 = await view2.to_columns();
|
|
let result3 = await view3.to_columns();
|
|
|
|
expect(result).toEqual({
|
|
x: [1, 2, 3, 4],
|
|
y: [1, 2, 3, 4],
|
|
'"x" * "y"': [1, 4, 9, 16],
|
|
});
|
|
|
|
expect(result2).toEqual({
|
|
x: [1, 2, 3, 4],
|
|
y: [1, 2, 3, 4],
|
|
'"x" + "y"': [2, 4, 6, 8],
|
|
});
|
|
|
|
expect(result3).toEqual({
|
|
x: [1, 2, 3, 4],
|
|
y: [1, 2, 3, 4],
|
|
'"x" - "y"': [0, 0, 0, 0],
|
|
});
|
|
|
|
view3.delete();
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("propagate appends to all expression columns", async function () {
|
|
const table = await perspective.table(data);
|
|
|
|
const view = await table.view({
|
|
expressions: { '"x" * "y"': '"x" * "y"' },
|
|
});
|
|
|
|
const view2 = await table.view({
|
|
expressions: { '"x" + "y"': '"x" + "y"' },
|
|
});
|
|
|
|
const view3 = await table.view({
|
|
expressions: { '"x" - "y"': '"x" - "y"' },
|
|
});
|
|
|
|
await table.update({ x: [1, 2, 3, 4], y: [1, 2, 3, 4] });
|
|
|
|
let result = await view.to_columns();
|
|
let result2 = await view2.to_columns();
|
|
let result3 = await view3.to_columns();
|
|
|
|
expect(result).toEqual({
|
|
x: [1, 2, 3, 4, 1, 2, 3, 4],
|
|
y: [2, 4, 6, 8, 1, 2, 3, 4],
|
|
'"x" * "y"': [2, 8, 18, 32, 1, 4, 9, 16],
|
|
});
|
|
|
|
expect(result2).toEqual({
|
|
x: [1, 2, 3, 4, 1, 2, 3, 4],
|
|
y: [2, 4, 6, 8, 1, 2, 3, 4],
|
|
'"x" + "y"': [3, 6, 9, 12, 2, 4, 6, 8],
|
|
});
|
|
|
|
expect(result3).toEqual({
|
|
x: [1, 2, 3, 4, 1, 2, 3, 4],
|
|
y: [2, 4, 6, 8, 1, 2, 3, 4],
|
|
'"x" - "y"': [-1, -2, -3, -4, 0, 0, 0, 0],
|
|
});
|
|
|
|
view3.delete();
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Computed updates with group by", function () {
|
|
test("should update on dependent columns, add", async function () {
|
|
const table = await perspective.table(pivot_data);
|
|
const view = await table.view({
|
|
columns: ['"int" + "float"', "int"],
|
|
aggregates: {
|
|
'"int" + "float"': "sum",
|
|
},
|
|
group_by: ['"int" + "float"'],
|
|
expressions: { '"int" + "float"': '"int" + "float"' },
|
|
});
|
|
|
|
await table.update({ int: [4], __INDEX__: [0] });
|
|
|
|
let results = await view.to_columns({
|
|
index: true,
|
|
});
|
|
|
|
expect(results).toEqual({
|
|
__ROW_PATH__: [[], [5.5], [6.25], [7.75], [9.25]],
|
|
int: [13, 2, 4, 3, 4],
|
|
'"int" + "float"': [28.75, 5.5, 6.25, 7.75, 9.25],
|
|
__INDEX__: [[0, 3, 2, 1], [1], [0], [2], [3]],
|
|
});
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should update on dependent columns, subtract", async function () {
|
|
const table = await perspective.table(pivot_data);
|
|
const view = await table.view({
|
|
columns: ['"int" - "float"', "int"],
|
|
group_by: ['"int" - "float"'],
|
|
expressions: { '"int" - "float"': '"int" - "float"' },
|
|
});
|
|
|
|
await table.update([{ int: 4, __INDEX__: 0 }]);
|
|
|
|
let json = await view.to_json({
|
|
index: true,
|
|
});
|
|
|
|
expect(json).toEqual([
|
|
{
|
|
__ROW_PATH__: [],
|
|
int: 13,
|
|
'"int" - "float"': -2.75,
|
|
__INDEX__: [0, 3, 1, 2],
|
|
},
|
|
{
|
|
__ROW_PATH__: [-1.75],
|
|
int: 3,
|
|
'"int" - "float"': -1.75,
|
|
__INDEX__: [2],
|
|
},
|
|
{
|
|
__ROW_PATH__: [-1.5],
|
|
int: 2,
|
|
'"int" - "float"': -1.5,
|
|
__INDEX__: [1],
|
|
},
|
|
{
|
|
__ROW_PATH__: [-1.25],
|
|
int: 4,
|
|
'"int" - "float"': -1.25,
|
|
__INDEX__: [3],
|
|
},
|
|
{
|
|
__ROW_PATH__: [1.75],
|
|
int: 4,
|
|
'"int" - "float"': 1.75,
|
|
__INDEX__: [0],
|
|
},
|
|
]);
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should update on dependent columns, multiply", async function () {
|
|
const table = await perspective.table(pivot_data);
|
|
|
|
const view = await table.view({
|
|
columns: ['"int" * "float"', "int"],
|
|
group_by: ['"int" * "float"'],
|
|
expressions: { '"int" * "float"': '"int" * "float"' },
|
|
});
|
|
|
|
await table.update([{ int: 4, __INDEX__: 0 }]);
|
|
|
|
let json = await view.to_json({
|
|
index: true,
|
|
});
|
|
|
|
expect(json).toEqual([
|
|
{
|
|
__ROW_PATH__: [],
|
|
int: 13,
|
|
'"int" * "float"': 51.25,
|
|
__INDEX__: [0, 3, 2, 1],
|
|
},
|
|
{
|
|
__ROW_PATH__: [7],
|
|
int: 2,
|
|
'"int" * "float"': 7,
|
|
__INDEX__: [1],
|
|
},
|
|
{
|
|
__ROW_PATH__: [9],
|
|
int: 4,
|
|
'"int" * "float"': 9,
|
|
__INDEX__: [0],
|
|
},
|
|
{
|
|
__ROW_PATH__: [14.25],
|
|
int: 3,
|
|
'"int" * "float"': 14.25,
|
|
__INDEX__: [2],
|
|
},
|
|
{
|
|
__ROW_PATH__: [21],
|
|
int: 4,
|
|
'"int" * "float"': 21,
|
|
__INDEX__: [3],
|
|
},
|
|
]);
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should update on dependent columns, divide", async function () {
|
|
const table = await perspective.table(pivot_data);
|
|
|
|
const view = await table.view({
|
|
columns: ['"int" / "float"', "int"],
|
|
group_by: ['"int" / "float"'],
|
|
expressions: { '"int" / "float"': '"int" / "float"' },
|
|
});
|
|
|
|
await table.update([{ int: 4, __INDEX__: 0 }]);
|
|
|
|
let json = await view.to_json({
|
|
index: true,
|
|
});
|
|
|
|
expect(json).toEqual([
|
|
{
|
|
__ROW_PATH__: [],
|
|
int: 13,
|
|
'"int" / "float"': 3.742690058479532,
|
|
__INDEX__: [0, 3, 2, 1],
|
|
},
|
|
{
|
|
__ROW_PATH__: [0.5714285714285714],
|
|
int: 2,
|
|
'"int" / "float"': 0.5714285714285714,
|
|
__INDEX__: [1],
|
|
},
|
|
{
|
|
__ROW_PATH__: [0.631578947368421],
|
|
int: 3,
|
|
'"int" / "float"': 0.631578947368421,
|
|
__INDEX__: [2],
|
|
},
|
|
{
|
|
__ROW_PATH__: [0.7619047619047619],
|
|
int: 4,
|
|
'"int" / "float"': 0.7619047619047619,
|
|
__INDEX__: [3],
|
|
},
|
|
{
|
|
__ROW_PATH__: [1.7777777777777777],
|
|
int: 4,
|
|
'"int" / "float"': 1.7777777777777777,
|
|
__INDEX__: [0],
|
|
},
|
|
]);
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Partial update with null", function () {
|
|
test("Update over null should recalculate", async function () {
|
|
const table = await perspective.table(pivot_data, { index: "int" });
|
|
const view = await table.view({
|
|
columns: ['"int" + "float"', "int", "float"],
|
|
expressions: { '"int" + "float"': '"int" + "float"' },
|
|
});
|
|
|
|
await table.update([{ int: 2, float: 3.5 }]);
|
|
|
|
let result = await view.to_columns();
|
|
|
|
expect(result).toEqual({
|
|
'"int" + "float"': [3.25, 5.5, 7.75, 9.25],
|
|
int: [1, 2, 3, 4],
|
|
float: [2.25, 3.5, 4.75, 5.25],
|
|
});
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Update with null should unset", async function () {
|
|
const table = await perspective.table(pivot_data, { index: "int" });
|
|
|
|
const view = await table.view({
|
|
columns: ['"int" + "float"', "int", "float"],
|
|
expressions: { '"int" + "float"': '"int" + "float"' },
|
|
});
|
|
|
|
await table.update([{ int: 2, float: null }]);
|
|
|
|
let result = await view.to_columns();
|
|
|
|
expect(result).toEqual({
|
|
'"int" + "float"': [3.25, null, 7.75, 9.25],
|
|
int: [1, 2, 3, 4],
|
|
float: [2.25, null, 4.75, 5.25],
|
|
});
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Undefined should be a no-op on expression columns", async function () {
|
|
const table = await perspective.table(
|
|
[
|
|
{ int: 1, float: 2.25, string: "a", datetime: new Date() },
|
|
{ int: 2, float: 3.5, string: "b", datetime: new Date() },
|
|
{ int: 3, float: 4.75, string: "c", datetime: new Date() },
|
|
{ int: 4, float: 5.25, string: "d", datetime: new Date() },
|
|
],
|
|
{ index: "int" },
|
|
);
|
|
const view = await table.view({
|
|
columns: ['"int" + "float"', "int", "float"],
|
|
expressions: { '"int" + "float"': '"int" + "float"' },
|
|
});
|
|
|
|
await table.update([{ int: 2, float: undefined }]);
|
|
|
|
let result = await view.to_columns();
|
|
|
|
expect(result).toEqual({
|
|
'"int" + "float"': [3.25, 5.5, 7.75, 9.25],
|
|
int: [1, 2, 3, 4],
|
|
float: [2.25, 3.5, 4.75, 5.25],
|
|
});
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Removing expression columns", () => {
|
|
test("Removes should remove rows in expression columns", async () => {
|
|
const table = await perspective.table(
|
|
{
|
|
x: [1, 2, 3, 4],
|
|
y: ["A", "B", "C", "D"],
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
|
|
const view = await table.view({
|
|
expressions: { 'lower("y")': 'lower("y")' },
|
|
});
|
|
|
|
const before = await view.to_columns();
|
|
expect(before['lower("y")']).toEqual(["a", "b", "c", "d"]);
|
|
await table.update({ y: ["HELLO", "WORLD"] });
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["x"]).toEqual([null, 1, 2, 3, 4]);
|
|
expect(result['lower("y")']).toEqual(["world", "a", "b", "c", "d"]);
|
|
|
|
await table.remove([4, null, 1]);
|
|
|
|
const result2 = await view.to_columns();
|
|
expect(result2["x"]).toEqual([2, 3]);
|
|
expect(result2['lower("y")']).toEqual(["b", "c"]);
|
|
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
})(perspective);
|