// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ // ┃ 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);