3532 lines
116 KiB
JavaScript
3532 lines
116 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 * as _ from "lodash";
|
|
import * as arrows from "./test_arrows.js";
|
|
|
|
import { test, expect } from "@perspective-dev/test";
|
|
import perspective from "./perspective_client";
|
|
|
|
function it_old_behavior(name, capture, opts = { skip: false }) {
|
|
let test_fn = test;
|
|
if (opts.skip) {
|
|
test_fn = test.skip;
|
|
}
|
|
test_fn(name, async function () {
|
|
let done;
|
|
let result = new Promise((x) => {
|
|
done = x;
|
|
});
|
|
|
|
await capture(done);
|
|
await result;
|
|
});
|
|
}
|
|
|
|
var data = [
|
|
{ x: 1, y: "a", z: true },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
];
|
|
|
|
var col_data = {
|
|
x: [1, 2, 3, 4],
|
|
y: ["a", "b", "c", "d"],
|
|
z: [true, false, true, false],
|
|
};
|
|
|
|
var meta = {
|
|
x: "integer",
|
|
y: "string",
|
|
z: "boolean",
|
|
};
|
|
|
|
var data_2 = [
|
|
{ x: 3, y: "c", z: false },
|
|
{ x: 4, y: "d", z: true },
|
|
{ x: 5, y: "g", z: false },
|
|
{ x: 6, y: "h", z: true },
|
|
];
|
|
|
|
const arrow_result = [
|
|
{
|
|
f32: 1.5,
|
|
f64: 1.5,
|
|
i64: 1,
|
|
i32: 1,
|
|
i16: 1,
|
|
i8: 1,
|
|
bool: true,
|
|
char: "a",
|
|
dict: "a",
|
|
datetime: +new Date("2018-01-25"),
|
|
},
|
|
{
|
|
f32: 2.5,
|
|
f64: 2.5,
|
|
i64: 2,
|
|
i32: 2,
|
|
i16: 2,
|
|
i8: 2,
|
|
bool: false,
|
|
char: "b",
|
|
dict: "b",
|
|
datetime: +new Date("2018-01-26"),
|
|
},
|
|
{
|
|
f32: 3.5,
|
|
f64: 3.5,
|
|
i64: 3,
|
|
i32: 3,
|
|
i16: 3,
|
|
i8: 3,
|
|
bool: true,
|
|
char: "c",
|
|
dict: "c",
|
|
datetime: +new Date("2018-01-27"),
|
|
},
|
|
{
|
|
f32: 4.5,
|
|
f64: 4.5,
|
|
i64: 4,
|
|
i32: 4,
|
|
i16: 4,
|
|
i8: 4,
|
|
bool: false,
|
|
char: "d",
|
|
dict: "d",
|
|
datetime: +new Date("2018-01-28"),
|
|
},
|
|
{
|
|
f32: 5.5,
|
|
f64: 5.5,
|
|
i64: 5,
|
|
i32: 5,
|
|
i16: 5,
|
|
i8: 5,
|
|
bool: true,
|
|
char: "d",
|
|
dict: "d",
|
|
datetime: +new Date("2018-01-29"),
|
|
},
|
|
];
|
|
|
|
const arrow_partial_result = [
|
|
{
|
|
f32: 1.5,
|
|
f64: 1.5,
|
|
i64: 1,
|
|
i32: 1,
|
|
i16: 1,
|
|
i8: 1,
|
|
bool: false,
|
|
char: "a",
|
|
dict: "a",
|
|
datetime: +new Date("2018-01-25"),
|
|
},
|
|
{
|
|
f32: 2.5,
|
|
f64: 2.5,
|
|
i64: 2,
|
|
i32: 2,
|
|
i16: 2,
|
|
i8: 2,
|
|
bool: true,
|
|
char: "b",
|
|
dict: "b",
|
|
datetime: +new Date("2018-01-26"),
|
|
},
|
|
{
|
|
f32: 3.5,
|
|
f64: 3.5,
|
|
i64: 3,
|
|
i32: 3,
|
|
i16: 3,
|
|
i8: 3,
|
|
bool: false,
|
|
char: "c",
|
|
dict: "c",
|
|
datetime: +new Date("2018-01-27"),
|
|
},
|
|
{
|
|
f32: 4.5,
|
|
f64: 4.5,
|
|
i64: 4,
|
|
i32: 4,
|
|
i16: 4,
|
|
i8: 4,
|
|
bool: true,
|
|
char: "d",
|
|
dict: "d",
|
|
datetime: +new Date("2018-01-28"),
|
|
},
|
|
{
|
|
f32: 5.5,
|
|
f64: 5.5,
|
|
i64: 5,
|
|
i32: 5,
|
|
i16: 5,
|
|
i8: 5,
|
|
bool: false,
|
|
char: "d",
|
|
dict: "d",
|
|
datetime: +new Date("2018-01-29"),
|
|
},
|
|
];
|
|
|
|
const arrow_partial_missing_result = [
|
|
{
|
|
f32: 1.5,
|
|
f64: 1.5,
|
|
i64: 1,
|
|
i32: 1,
|
|
i16: 1,
|
|
i8: 1,
|
|
bool: false,
|
|
char: "a",
|
|
dict: "a",
|
|
datetime: +new Date("2018-01-25"),
|
|
},
|
|
{
|
|
f32: 2.5,
|
|
f64: 2.5,
|
|
i64: 2,
|
|
i32: 2,
|
|
i16: 2,
|
|
i8: 2,
|
|
bool: false,
|
|
char: "b",
|
|
dict: "b",
|
|
datetime: +new Date("2018-01-26"),
|
|
},
|
|
{
|
|
f32: 3.5,
|
|
f64: 3.5,
|
|
i64: 3,
|
|
i32: 3,
|
|
i16: 3,
|
|
i8: 3,
|
|
bool: false,
|
|
char: "c",
|
|
dict: "c",
|
|
datetime: +new Date("2018-01-27"),
|
|
},
|
|
{
|
|
f32: 4.5,
|
|
f64: 4.5,
|
|
i64: 4,
|
|
i32: 4,
|
|
i16: 4,
|
|
i8: 4,
|
|
bool: false,
|
|
char: "d",
|
|
dict: "d",
|
|
datetime: +new Date("2018-01-28"),
|
|
},
|
|
{
|
|
f32: 5.5,
|
|
f64: 5.5,
|
|
i64: 5,
|
|
i32: 5,
|
|
i16: 5,
|
|
i8: 5,
|
|
bool: false,
|
|
char: "d",
|
|
dict: "d",
|
|
datetime: +new Date("2018-01-29"),
|
|
},
|
|
];
|
|
|
|
const arrow_indexed_result = [
|
|
{
|
|
f32: 1.5,
|
|
f64: 1.5,
|
|
i64: 1,
|
|
i32: 1,
|
|
i16: 1,
|
|
i8: 1,
|
|
bool: true,
|
|
char: "a",
|
|
dict: "a",
|
|
datetime: +new Date("2018-01-25"),
|
|
},
|
|
{
|
|
f32: 2.5,
|
|
f64: 2.5,
|
|
i64: 2,
|
|
i32: 2,
|
|
i16: 2,
|
|
i8: 2,
|
|
bool: false,
|
|
char: "b",
|
|
dict: "b",
|
|
datetime: +new Date("2018-01-26"),
|
|
},
|
|
{
|
|
f32: 3.5,
|
|
f64: 3.5,
|
|
i64: 3,
|
|
i32: 3,
|
|
i16: 3,
|
|
i8: 3,
|
|
bool: true,
|
|
char: "c",
|
|
dict: "c",
|
|
datetime: +new Date("2018-01-27"),
|
|
},
|
|
{
|
|
f32: 5.5,
|
|
f64: 5.5,
|
|
i64: 5,
|
|
i32: 5,
|
|
i16: 5,
|
|
i8: 5,
|
|
bool: true,
|
|
char: "d",
|
|
dict: "d",
|
|
datetime: +new Date("2018-01-29"),
|
|
},
|
|
];
|
|
|
|
async function match_delta(perspective, delta, expected) {
|
|
let table = await perspective.table(delta);
|
|
let view = await table.view();
|
|
let json = await view.to_json();
|
|
expect(json).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
}
|
|
|
|
((perspective) => {
|
|
test.describe("Removes", function () {
|
|
test("should not remove without explicit index", async function () {
|
|
const table = await perspective.table(meta);
|
|
await table.update(data);
|
|
const view = await table.view();
|
|
let threw = false;
|
|
try {
|
|
// In 3.0, this throws an exception
|
|
await table.remove([0, 1]);
|
|
} catch (e) {
|
|
threw = true;
|
|
}
|
|
|
|
expect(threw).toBeTruthy();
|
|
const result = await view.to_json();
|
|
expect(await view.num_rows()).toEqual(4);
|
|
expect(result.length).toEqual(4);
|
|
expect(result).toEqual(data);
|
|
expect(await table.size()).toEqual(4);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("after an `update()`", async function () {
|
|
const table = await perspective.table(meta, { index: "x" });
|
|
await table.update(data);
|
|
const view = await table.view();
|
|
await table.remove([1, 2]);
|
|
const result = await view.to_json();
|
|
expect(await view.num_rows()).toEqual(2);
|
|
expect(result.length).toEqual(2);
|
|
expect(result).toEqual(data.slice(2, 4));
|
|
expect(await table.size()).toEqual(2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("after a regular data load", async function () {
|
|
const table = await perspective.table(data, { index: "x" });
|
|
const view = await table.view();
|
|
await table.remove([1, 2]);
|
|
const result = await view.to_json();
|
|
expect(await view.num_rows()).toEqual(2);
|
|
expect(result.length).toEqual(2);
|
|
expect(result).toEqual(data.slice(2, 4));
|
|
expect(await table.size()).toEqual(2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("after an `update()`, string pkey", async function () {
|
|
const table = await perspective.table(meta, { index: "y" });
|
|
await table.update(data);
|
|
const view = await table.view();
|
|
await table.remove(["a", "b"]);
|
|
const result = await view.to_json();
|
|
expect(await view.num_rows()).toEqual(2);
|
|
expect(result.length).toEqual(2);
|
|
expect(result).toEqual(data.slice(2, 4));
|
|
expect(await table.size()).toEqual(2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("after a regular data load, string pkey", async function () {
|
|
const table = await perspective.table(data, { index: "y" });
|
|
const view = await table.view();
|
|
await table.remove(["a", "b"]);
|
|
const result = await view.to_json();
|
|
expect(await view.num_rows()).toEqual(2);
|
|
expect(result.length).toEqual(2);
|
|
expect(result).toEqual(data.slice(2, 4));
|
|
expect(await table.size()).toEqual(2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("after an update, date pkey", async function () {
|
|
const datetimes = [
|
|
new Date(2020, 0, 15),
|
|
new Date(2020, 1, 15),
|
|
new Date(2020, 2, 15),
|
|
new Date(2020, 3, 15),
|
|
];
|
|
const table = await perspective.table(
|
|
{
|
|
x: "integer",
|
|
y: "date",
|
|
z: "float",
|
|
},
|
|
{ index: "y" },
|
|
);
|
|
await table.update({
|
|
x: [1, 2, 3, 4],
|
|
y: datetimes,
|
|
z: [1.5, 2.5, 3.5, 4.5],
|
|
});
|
|
const view = await table.view();
|
|
await table.remove(datetimes.slice(0, 2));
|
|
const result = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(2);
|
|
expect(result).toEqual({
|
|
x: [3, 4],
|
|
y: [1584230400000, 1586908800000],
|
|
z: [3.5, 4.5],
|
|
});
|
|
// expect(await table.size()).toEqual(2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("after an update, datetime pkey", async function () {
|
|
const datetimes = [
|
|
new Date(2020, 0, 15),
|
|
new Date(2020, 1, 15),
|
|
new Date(2020, 2, 15),
|
|
new Date(2020, 3, 15),
|
|
];
|
|
const table = await perspective.table(
|
|
{
|
|
x: "integer",
|
|
y: "datetime",
|
|
z: "float",
|
|
},
|
|
{ index: "y" },
|
|
);
|
|
await table.update({
|
|
x: [1, 2, 3, 4],
|
|
y: datetimes,
|
|
z: [1.5, 2.5, 3.5, 4.5],
|
|
});
|
|
const view = await table.view();
|
|
await table.remove(datetimes.slice(0, 2));
|
|
const result = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(2);
|
|
expect(result).toEqual({
|
|
x: [3, 4],
|
|
y: [1584230400000, 1586908800000],
|
|
z: [3.5, 4.5],
|
|
});
|
|
expect(await table.size()).toEqual(2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("after a regular data load, datetime pkey", async function () {
|
|
const datetimes = [
|
|
new Date(2020, 0, 15),
|
|
new Date(2020, 1, 15),
|
|
new Date(2020, 2, 15),
|
|
new Date(2020, 3, 15),
|
|
];
|
|
const table = await perspective.table(
|
|
{
|
|
x: [1, 2, 3, 4],
|
|
y: datetimes,
|
|
z: [1.5, 2.5, 3.5, 4.5],
|
|
},
|
|
{ index: "y" },
|
|
);
|
|
const view = await table.view();
|
|
await table.remove(datetimes.slice(0, 2));
|
|
const result = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(2);
|
|
expect(result).toEqual({
|
|
x: [3, 4],
|
|
y: [1584230400000, 1586908800000],
|
|
z: [3.5, 4.5],
|
|
});
|
|
expect(await table.size()).toEqual(2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("multiple single element removes", async function () {
|
|
const table = await perspective.table(meta, { index: "x" });
|
|
for (let i = 0; i < 100; i++) {
|
|
await table.update([{ x: i, y: "test", z: false }]);
|
|
}
|
|
for (let i = 1; i < 100; i++) {
|
|
await table.remove([i]);
|
|
}
|
|
const view = await table.view();
|
|
const result = await view.to_json();
|
|
expect(result).toEqual([{ x: 0, y: "test", z: false }]);
|
|
expect(result.length).toEqual(1);
|
|
expect(await table.size()).toEqual(1);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("remove while pivoted, last and count", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
x: ["a"],
|
|
y: ["A"],
|
|
idx: [1],
|
|
},
|
|
{ index: "idx" },
|
|
);
|
|
|
|
const view = await table.view({
|
|
aggregates: {
|
|
x: "last",
|
|
y: "count",
|
|
},
|
|
group_by: ["y"],
|
|
});
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
__ROW_PATH__: [[], ["A"]],
|
|
x: ["a", "a"],
|
|
y: [1, 1],
|
|
idx: [1, 1],
|
|
});
|
|
|
|
await table.update({
|
|
x: ["b"],
|
|
y: ["B"],
|
|
idx: [2],
|
|
});
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
__ROW_PATH__: [[], ["A"], ["B"]],
|
|
x: ["b", "a", "b"],
|
|
y: [2, 1, 1],
|
|
idx: [3, 1, 2],
|
|
});
|
|
|
|
await table.remove([1]);
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
__ROW_PATH__: [[], ["B"]],
|
|
x: ["b", "b"],
|
|
y: [1, 1],
|
|
idx: [2, 2],
|
|
});
|
|
|
|
await table.remove([2]);
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
__ROW_PATH__: [[]],
|
|
x: [""],
|
|
y: [0],
|
|
idx: [0],
|
|
});
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Removes out of order", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
x: "string",
|
|
y: "integer",
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
|
|
await table.update({
|
|
x: ["b", "1", "a", "c"],
|
|
y: [3, 1, 2, 4],
|
|
});
|
|
|
|
const view = await table.view();
|
|
expect(await view.to_columns()).toEqual({
|
|
x: ["1", "a", "b", "c"],
|
|
y: [1, 2, 3, 4],
|
|
});
|
|
|
|
await table.remove(["a", "c", "1", "b"]);
|
|
|
|
// num_rows should always reflect latest - we did not call_process
|
|
// in num_rows and I think the viewer covered that up because
|
|
// it would immediately serialize and thus flush the queue.
|
|
expect(await view.num_rows()).toEqual(0);
|
|
expect(await view.to_json()).toEqual([]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("No-op on pkeys not in the set", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
x: "string",
|
|
y: "integer",
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
|
|
await table.update({
|
|
x: ["b", "1", "a", "c"],
|
|
y: [3, 1, 2, 4],
|
|
});
|
|
|
|
const view = await table.view();
|
|
expect(await view.to_columns()).toEqual({
|
|
x: ["1", "a", "b", "c"],
|
|
y: [1, 2, 3, 4],
|
|
});
|
|
|
|
await table.remove(["z", "ff", "2312", "b"]);
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
x: ["1", "a", "c"],
|
|
y: [1, 2, 4],
|
|
});
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("conflation order is consistent", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
x: "string",
|
|
y: "integer",
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
|
|
await table.update({
|
|
x: ["b", "1", "a", "c"],
|
|
y: [3, 1, 2, 4],
|
|
});
|
|
await table.remove(["1", "c"]);
|
|
|
|
// removes applied after update
|
|
const view = await table.view();
|
|
expect(await view.to_columns()).toEqual({
|
|
x: ["a", "b"],
|
|
y: [2, 3],
|
|
});
|
|
|
|
await table.update({
|
|
x: ["b"],
|
|
y: [103],
|
|
});
|
|
await table.remove(["b"]);
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
x: ["a"],
|
|
y: [2],
|
|
});
|
|
|
|
await table.update({
|
|
x: ["b"],
|
|
y: [100],
|
|
});
|
|
|
|
await table.remove(["a"]);
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
x: ["b"],
|
|
y: [100],
|
|
});
|
|
|
|
// remove applied after update
|
|
for (let i = 0; i < 100; i++) {
|
|
await table.update({
|
|
x: ["c", "a"],
|
|
y: [i + 1, i + 2],
|
|
});
|
|
await table.remove(["c", "a"]);
|
|
}
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
x: ["b"],
|
|
y: [100],
|
|
});
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("correct size", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
x: "integer",
|
|
y: "integer",
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
await table.update([{ x: i, y: i }]);
|
|
}
|
|
|
|
expect(await table.size()).toEqual(100);
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
await table.remove([i]);
|
|
await table.update([{ x: i, y: i }]);
|
|
}
|
|
|
|
expect(await table.size()).toEqual(100);
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
await table.update([{ x: i, y: i }]);
|
|
}
|
|
|
|
expect(await table.size()).toEqual(100);
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
await table.remove([i]);
|
|
}
|
|
|
|
expect(await table.size()).toEqual(0);
|
|
|
|
await table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Schema", function () {
|
|
test("updates with columns not in schema", async function () {
|
|
var table = await perspective.table({ x: "integer", y: "string" });
|
|
await table.update(data);
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual([
|
|
{ x: 1, y: "a" },
|
|
{ x: 2, y: "b" },
|
|
{ x: 3, y: "c" },
|
|
{ x: 4, y: "d" },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("coerces to string", async function () {
|
|
var table = await perspective.table({
|
|
x: "string",
|
|
y: "string",
|
|
z: "string",
|
|
});
|
|
await table.update(data);
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual([
|
|
{ x: "1", y: "a", z: "true" },
|
|
{ x: "2", y: "b", z: "false" },
|
|
{ x: "3", y: "c", z: "true" },
|
|
{ x: "4", y: "d", z: "false" },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Updates", function () {
|
|
test("Meta constructor then `update()`", async function () {
|
|
var table = await perspective.table(meta);
|
|
await table.update(data);
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Meta constructor then column oriented `update()`", async function () {
|
|
var table = await perspective.table(meta);
|
|
await table.update(col_data);
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Column oriented `update()` with columns in different order to schema", async function () {
|
|
var table = await perspective.table(meta);
|
|
|
|
var reordered_col_data = {
|
|
y: ["a", "b", "c", "d"],
|
|
z: [true, false, true, false],
|
|
x: [1, 2, 3, 4],
|
|
};
|
|
|
|
await table.update(reordered_col_data);
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Column data constructor then column oriented `update()`", async function () {
|
|
var colUpdate = {
|
|
x: [3, 4, 5],
|
|
y: ["h", "i", "j"],
|
|
z: [false, true, false],
|
|
};
|
|
|
|
var expected = [
|
|
{ x: 1, y: "a", z: true },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "h", z: false },
|
|
{ x: 4, y: "i", z: true },
|
|
{ x: 5, y: "j", z: false },
|
|
];
|
|
|
|
var table = await perspective.table(col_data, { index: "x" });
|
|
await table.update(colUpdate);
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("`update()` should increment `table.size()` without a view created", async function () {
|
|
const table = await perspective.table(data);
|
|
expect(await table.size()).toEqual(4);
|
|
await table.update(data);
|
|
expect(await table.size()).toEqual(8);
|
|
await table.update(data);
|
|
expect(await table.size()).toEqual(12);
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("`update()` unbound to table", async function () {
|
|
var table = await perspective.table(meta);
|
|
var updater = await table.update;
|
|
console.log(updater);
|
|
updater(data);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("`update([])` does not error", async function () {
|
|
let table = await perspective.table(meta);
|
|
let view = await table.view();
|
|
await table.update([]);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual([]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Multiple `update()`s", async function () {
|
|
var table = await perspective.table(meta);
|
|
await table.update(data);
|
|
await table.update(data);
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data.concat(data));
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("`update()` called after `view()`", async function () {
|
|
var table = await perspective.table(meta);
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Arrow Updates", function () {
|
|
test("arrow contructor then arrow `update()`", async function () {
|
|
const arrow = arrows.test_arrow;
|
|
var table = await perspective.table(arrow.slice());
|
|
await table.update(arrow.slice());
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(arrow_result.concat(arrow_result));
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("non-arrow constructor then arrow `update()`", async function () {
|
|
let table = await perspective.table(arrow_result);
|
|
let view = await table.view();
|
|
let generated_arrow = await view.to_arrow();
|
|
await table.update(generated_arrow);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(arrow_result.concat(arrow_result));
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("arrow dict contructor then arrow dict `update()`", async function () {
|
|
var table = await perspective.table(arrows.dict_arrow.slice());
|
|
await table.update(arrows.dict_update_arrow.slice());
|
|
var view = await table.view();
|
|
let result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [
|
|
"abc",
|
|
"def",
|
|
"def",
|
|
null,
|
|
"abc",
|
|
null,
|
|
"update1",
|
|
"update2",
|
|
],
|
|
b: [
|
|
"klm",
|
|
"hij",
|
|
null,
|
|
"hij",
|
|
"klm",
|
|
"update3",
|
|
null,
|
|
"update4",
|
|
],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("non-arrow constructor then arrow dict `update()`", async function () {
|
|
let table = await perspective.table({
|
|
a: ["a", "b", "c"],
|
|
b: ["d", "e", "f"],
|
|
});
|
|
let view = await table.view();
|
|
await table.update(arrows.dict_update_arrow.slice());
|
|
let result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: ["a", "b", "c", null, "update1", "update2"],
|
|
b: ["d", "e", "f", "update3", null, "update4"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("arrow dict contructor then arrow dict `update()` subset of columns", async function () {
|
|
var table = await perspective.table(arrows.dict_arrow.slice());
|
|
await table.update(arrows.dict_update_arrow.slice());
|
|
var view = await table.view({
|
|
columns: ["a"],
|
|
});
|
|
let result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [
|
|
"abc",
|
|
"def",
|
|
"def",
|
|
null,
|
|
"abc",
|
|
null,
|
|
"update1",
|
|
"update2",
|
|
],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("non-arrow constructor then arrow dict `update()`, subset of columns", async function () {
|
|
let table = await perspective.table({
|
|
a: ["a", "b", "c"],
|
|
b: ["d", "e", "f"],
|
|
});
|
|
let view = await table.view({
|
|
columns: ["a"],
|
|
});
|
|
await table.update(arrows.dict_update_arrow.slice());
|
|
let result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: ["a", "b", "c", null, "update1", "update2"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("arrow partial `update()` a single column", async function () {
|
|
let table = await perspective.table(arrows.test_arrow.slice(), {
|
|
index: "i64",
|
|
});
|
|
await table.update(arrows.partial_arrow.slice());
|
|
const view = await table.view();
|
|
const result = await view.to_json();
|
|
expect(result).toEqual(arrow_partial_result);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("arrow partial `update()` a single column with missing rows", async function () {
|
|
let table = await perspective.table(arrows.test_arrow.slice(), {
|
|
index: "i64",
|
|
});
|
|
await table.update(arrows.partial_missing_rows_arrow.slice());
|
|
const view = await table.view();
|
|
const result = await view.to_json();
|
|
expect(result).toEqual(arrow_partial_missing_result);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("schema constructor, then arrow `update()`", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
c: "string",
|
|
});
|
|
await table.update(arrows.int_float_str_arrow.slice());
|
|
const view = await table.view();
|
|
const size = await table.size();
|
|
expect(size).toEqual(4);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [1, 2, 3, 4],
|
|
b: [1.5, 2.5, 3.5, 4.5],
|
|
c: ["a", "b", "c", "d"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("schema constructor, then arrow dictionary `update()`", async function () {
|
|
const table = await perspective.table({
|
|
a: "string",
|
|
b: "string",
|
|
});
|
|
await table.update(arrows.dict_arrow.slice());
|
|
const view = await table.view();
|
|
const size = await table.size();
|
|
expect(size).toEqual(5);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: ["abc", "def", "def", null, "abc"],
|
|
b: ["klm", "hij", null, "hij", "klm"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("schema constructor, then indexed arrow dictionary `update()`", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
a: "string",
|
|
b: "string",
|
|
},
|
|
{ index: "a" },
|
|
);
|
|
await table.update(arrows.dict_arrow.slice());
|
|
const view = await table.view();
|
|
const size = await table.size();
|
|
expect(size).toEqual(3);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [null, "abc", "def"],
|
|
b: ["hij", "klm", "hij"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("schema constructor, then indexed arrow dictionary `update()` with more columns than in schema", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
a: "string",
|
|
},
|
|
{ index: "a" },
|
|
);
|
|
await table.update(arrows.dict_arrow.slice());
|
|
const view = await table.view();
|
|
const size = await table.size();
|
|
console.log(await view.to_columns());
|
|
expect(size).toEqual(3);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [null, "abc", "def"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("schema constructor, then indexed arrow dictionary `update()` with less columns than in schema", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
a: "string",
|
|
b: "string",
|
|
x: "integer",
|
|
},
|
|
{ index: "a" },
|
|
);
|
|
await table.update(arrows.dict_arrow.slice());
|
|
const view = await table.view();
|
|
const size = await table.size();
|
|
console.log(await view.to_columns());
|
|
expect(size).toEqual(3);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [null, "abc", "def"],
|
|
b: ["hij", "klm", "hij"],
|
|
x: [null, null, null],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("schema constructor, then indexed arrow `update()`", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
a: "integer",
|
|
b: "float",
|
|
c: "string",
|
|
},
|
|
{ index: "a" },
|
|
);
|
|
|
|
const view = await table.view();
|
|
await table.update(arrows.int_float_str_arrow.slice());
|
|
await table.update(arrows.int_float_str_update_arrow.slice());
|
|
expect(await table.size()).toBe(4);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [1, 2, 3, 4],
|
|
b: [100.5, 2.5, 3.5, 400.5],
|
|
c: ["x", "b", "c", "y"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("schema constructor, then arrow update() with more columns than in the Table", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
});
|
|
const view = await table.view();
|
|
await table.update(arrows.int_float_str_arrow.slice());
|
|
expect(await table.size()).toBe(4);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [1, 2, 3, 4],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("Table.get_num_views", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
});
|
|
const view1 = await table.view();
|
|
expect(await table.get_num_views()).toBe(1);
|
|
const view2 = await table.view();
|
|
const view3 = await table.view();
|
|
const view4 = await table.view();
|
|
const view5 = await table.view();
|
|
expect(await table.get_num_views()).toBe(5);
|
|
await view1.delete();
|
|
await view2.delete();
|
|
await view3.delete();
|
|
await view4.delete();
|
|
await view5.delete();
|
|
expect(await table.get_num_views()).toBe(0);
|
|
table.delete();
|
|
});
|
|
|
|
test("schema constructor indexed, then arrow update() with more columns than in the Table", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
a: "integer",
|
|
b: "float",
|
|
},
|
|
{
|
|
index: "a",
|
|
},
|
|
);
|
|
const view = await table.view();
|
|
await table.update(arrows.int_float_str_arrow.slice());
|
|
await table.update(arrows.int_float_str_update_arrow.slice());
|
|
expect(await table.size()).toBe(4);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [1, 2, 3, 4],
|
|
b: [100.5, 2.5, 3.5, 400.5],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("schema constructor, then arrow update() with less columns than in the Table", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
x: "float",
|
|
y: "string",
|
|
});
|
|
const view = await table.view();
|
|
await table.update(arrows.int_float_str_arrow.slice());
|
|
expect(await table.size()).toBe(4);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [1, 2, 3, 4],
|
|
x: [null, null, null, null],
|
|
y: [null, null, null, null],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("schema constructor indexed, then arrow update() with less columns than in the Table", async function () {
|
|
const table = await perspective.table(
|
|
{
|
|
a: "integer",
|
|
x: "float",
|
|
y: "string",
|
|
},
|
|
{
|
|
index: "a",
|
|
},
|
|
);
|
|
const view = await table.view();
|
|
await table.update(arrows.int_float_str_arrow.slice());
|
|
await table.update(arrows.int_float_str_update_arrow.slice());
|
|
expect(await table.size()).toBe(4);
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [1, 2, 3, 4],
|
|
x: [null, null, null, null],
|
|
y: [null, null, null, null],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("schema constructor, then arrow stream and arrow file `update()`", async function () {
|
|
const table = await perspective.table({
|
|
a: "integer",
|
|
b: "float",
|
|
c: "string",
|
|
});
|
|
|
|
await table.update(arrows.int_float_str_arrow.slice());
|
|
await table.update(arrows.int_float_str_file_arrow.slice());
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
a: [1, 2, 3, 4, 1, 2, 3, 4],
|
|
b: [1.5, 2.5, 3.5, 4.5, 1.5, 2.5, 3.5, 4.5],
|
|
c: ["a", "b", "c", "d", "a", "b", "c", "d"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Notifications", function () {
|
|
it_old_behavior("`on_update()`", async function (done) {
|
|
var table = await perspective.table(meta);
|
|
var view = await table.view();
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(perspective, updated.delta, data);
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table.update(data);
|
|
});
|
|
|
|
it_old_behavior(
|
|
"`on_update` before and after `update()`",
|
|
async function (done) {
|
|
var table = await perspective.table(meta);
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
var ran = false;
|
|
view.on_update(
|
|
async function (updated) {
|
|
if (!ran) {
|
|
await match_delta(perspective, updated.delta, data);
|
|
ran = true;
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
}
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table.update(data);
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"`on_update(await table.update) !`",
|
|
async function (done) {
|
|
var table1 = await perspective.table(meta);
|
|
var table2 = await perspective.table(meta);
|
|
var view1 = await table1.view();
|
|
var view2 = await table2.view();
|
|
view1.on_update(
|
|
async function (updated) {
|
|
await table2.update(updated.delta);
|
|
let result = await view2.to_json();
|
|
expect(result).toEqual(data);
|
|
view1.delete();
|
|
view2.delete();
|
|
table1.delete();
|
|
table2.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table1.update(data);
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"`on_update( table.update)` before and after `update()`",
|
|
async function (done) {
|
|
var table1 = await perspective.table(meta);
|
|
var table2 = await perspective.table(meta);
|
|
var view1 = await table1.view();
|
|
var view2 = await table2.view();
|
|
|
|
await table1.update(data);
|
|
await table2.update(data);
|
|
view1.on_update(
|
|
async function (updated) {
|
|
await table2.update(updated.delta);
|
|
let result = await view2.to_json();
|
|
expect(result).toEqual(data.concat(data));
|
|
view1.delete();
|
|
view2.delete();
|
|
table1.delete();
|
|
table2.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table1.update(data);
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"OG - properly removes a failed update callback on a table",
|
|
async function (done) {
|
|
const table = await perspective.table({ x: "integer" });
|
|
const view = await table.view();
|
|
let size = await table.size();
|
|
let counter = 0;
|
|
|
|
// when a callback throws, it should delete that callback
|
|
view.on_update(() => {
|
|
counter++;
|
|
throw new Error("something went wrong!");
|
|
});
|
|
|
|
view.on_update(async () => {
|
|
// failed callback gets removed; non-failing callback gets called
|
|
let sz = await table.size();
|
|
expect(counter).toEqual(1);
|
|
expect(sz).toEqual(size++);
|
|
});
|
|
|
|
await table.update([{ x: 1 }]);
|
|
await table.update([{ x: 2 }]);
|
|
await table.update([{ x: 3 }]);
|
|
|
|
const view2 = await table.view(); // create a new view to make sure we process every update transacation.
|
|
const final_size = await table.size();
|
|
expect(final_size).toEqual(3);
|
|
|
|
view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ skip: true },
|
|
);
|
|
|
|
it_old_behavior(
|
|
"properly removes a failed update callback on a table",
|
|
async function (done) {
|
|
const table = await perspective.table({ x: "integer" });
|
|
const view = await table.view();
|
|
let size = await table.size();
|
|
|
|
view.on_update(async () => {
|
|
// failed callback gets removed; non-failing callback gets called
|
|
let sz = await table.size();
|
|
expect(sz).toEqual(++size);
|
|
});
|
|
|
|
await table.update([{ x: 1 }]);
|
|
await table.size();
|
|
await table.update([{ x: 2 }]);
|
|
await table.size();
|
|
await table.update([{ x: 3 }]);
|
|
await table.size();
|
|
|
|
const final_size = await table.size();
|
|
expect(final_size).toEqual(3);
|
|
|
|
// view2.delete();
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"`on_update()` that calls operations on the table should not recurse",
|
|
async function (done) {
|
|
var table = await perspective.table(meta);
|
|
var view = await table.view();
|
|
view.on_update(async function (updated) {
|
|
expect(updated.port_id).toEqual(0);
|
|
const json = await view.to_json();
|
|
// Not checking for correctness, literally just to assert
|
|
// that the `process()` call triggered by `to_json` will not
|
|
// infinitely recurse.
|
|
expect(json).toEqual(await view.to_json());
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
});
|
|
await table.update(data);
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"`on_update()` should be triggered in sequence",
|
|
async function (done) {
|
|
var table = await perspective.table(meta);
|
|
var view = await table.view();
|
|
|
|
let order = [];
|
|
|
|
const finish = function () {
|
|
if (order.length === 3) {
|
|
expect(order).toEqual([0, 1, 2]);
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
}
|
|
};
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
view.on_update(() => {
|
|
order.push(i);
|
|
finish();
|
|
});
|
|
}
|
|
|
|
await table.update(data);
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"`on_update()` should be triggered in sequence across multiple views",
|
|
async function (done) {
|
|
var table = await perspective.table(meta);
|
|
const views = [
|
|
await table.view(),
|
|
await table.view(),
|
|
await table.view(),
|
|
];
|
|
|
|
let order = [];
|
|
|
|
const finish = function () {
|
|
if (order.length === 3) {
|
|
expect(order).toEqual([0, 1, 2]);
|
|
for (const view of views) {
|
|
view.delete();
|
|
}
|
|
table.delete();
|
|
done();
|
|
}
|
|
};
|
|
|
|
for (let i = 0; i < views.length; i++) {
|
|
views[i].on_update(() => {
|
|
order.push(i);
|
|
finish();
|
|
});
|
|
}
|
|
|
|
await table.update(data);
|
|
},
|
|
);
|
|
});
|
|
|
|
test.describe("Limit", function () {
|
|
test("{limit: 2} with table of size 4", async function () {
|
|
var table = await perspective.table(data, { limit: 2 });
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data.slice(2));
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{limit: 5} with 2 updates of size 4", async function () {
|
|
var table = await perspective.table(data, { limit: 5 });
|
|
await table.update(data);
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
let expected = data.concat(data).slice(-5);
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{limit: 1} with arrow update", async function () {
|
|
const arrow = arrows.test_arrow.slice();
|
|
var table = await perspective.table(arrow, { limit: 1 });
|
|
await table.update(arrow.slice());
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual([arrow_result[arrow_result.length - 1]]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Indexed", function () {
|
|
test("{index: 'x'} (int)", async function () {
|
|
var table = await perspective.table(data, { index: "x" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{index: 'y'} (string)", async function () {
|
|
var table = await perspective.table(data, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{index: 'x'} (int) with null and 0", async function () {
|
|
const data = {
|
|
x: [0, 1, null, 2, 3],
|
|
y: ["a", "b", "c", "d", "e"],
|
|
};
|
|
const table = await perspective.table(data, { index: "x" });
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
x: [null, 0, 1, 2, 3], // null before 0
|
|
y: ["c", "a", "b", "d", "e"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{index: 'y'} (str) with null and empty string", async function () {
|
|
const data = {
|
|
x: [0, 1, 2, 3, 4],
|
|
y: ["", "a", "b", "c", null],
|
|
};
|
|
const table = await perspective.table(data, { index: "y" });
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
x: [4, 0, 1, 2, 3],
|
|
y: [null, "", "a", "b", "c"], // null before empties
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{index: 'x'} (int) with null and 0, update", async function () {
|
|
const data = {
|
|
x: [0, 1, null, 2, 3],
|
|
y: ["a", "b", "c", "d", "e"],
|
|
};
|
|
const table = await perspective.table(data, { index: "x" });
|
|
await table.update({
|
|
x: [null, 0],
|
|
y: ["x", "y"],
|
|
});
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
x: [null, 0, 1, 2, 3], // null before 0
|
|
y: ["x", "y", "b", "d", "e"],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{index: 'y'} (str) with null and empty string, update", async function () {
|
|
const data = {
|
|
x: [0, 1, 2, 3, 4],
|
|
y: ["", "a", "b", "c", null],
|
|
};
|
|
const table = await perspective.table(data, { index: "y" });
|
|
await table.update({
|
|
x: [5, 6],
|
|
y: ["", null],
|
|
});
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
x: [6, 5, 1, 2, 3],
|
|
y: [null, "", "a", "b", "c"], // null before empties
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{index: 'x'} (date) with null", async function () {
|
|
const data = {
|
|
x: ["10/30/2016", "11/1/2016", null, "1/1/2000"],
|
|
y: [1, 2, 3, 4],
|
|
};
|
|
const table = await perspective.table(
|
|
{
|
|
x: "date",
|
|
y: "integer",
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
await table.update(data);
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
x: [
|
|
null,
|
|
new Date("2000-1-1").getTime(),
|
|
new Date("2016-10-30").getTime(),
|
|
new Date("2016-11-1").getTime(),
|
|
],
|
|
y: [3, 4, 1, 2],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("{index: 'y'} (datetime) with datetime and null", async function () {
|
|
const data = {
|
|
x: [
|
|
"2016-11-01 11:00:00",
|
|
"2016-11-01 11:10:00",
|
|
null,
|
|
"2016-11-01 11:20:00",
|
|
],
|
|
y: [1, 2, 3, 4],
|
|
};
|
|
const table = await perspective.table(
|
|
{
|
|
x: "datetime",
|
|
y: "integer",
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
await table.update(data);
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
expect(result).toEqual({
|
|
x: [
|
|
null,
|
|
new Date("2016-11-1 11:00:00").getTime(),
|
|
new Date("2016-11-1 11:10:00").getTime(),
|
|
new Date("2016-11-1 11:20:00").getTime(),
|
|
],
|
|
y: [3, 1, 2, 4],
|
|
});
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Arrow with {index: 'i64'} (int)", async function () {
|
|
var table = await perspective.table(arrows.test_arrow.slice(), {
|
|
index: "i64",
|
|
});
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(arrow_result);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Arrow with {index: 'char'} (char)", async function () {
|
|
var table = await perspective.table(arrows.test_arrow.slice(), {
|
|
index: "char",
|
|
});
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(arrow_indexed_result);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("Arrow with {index: 'dict'} (dict)", async function () {
|
|
var table = await perspective.table(arrows.test_arrow.slice(), {
|
|
index: "dict",
|
|
});
|
|
var view = await table.view();
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(arrow_indexed_result);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("multiple updates on int {index: 'x'}", async function () {
|
|
var table = await perspective.table(data, { index: "x" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
await table.update(data);
|
|
await table.update(data);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("multiple updates on str {index: 'y'}", async function () {
|
|
var table = await perspective.table(data, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
await table.update(data);
|
|
await table.update(data);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("multiple updates on str {index: 'y'} with new, old, null pkey", async function () {
|
|
var table = await perspective.table(data, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update([{ x: 1, y: "a", z: true }]);
|
|
await table.update([{ x: 100, y: null, z: true }]);
|
|
await table.update([{ x: 123, y: "abc", z: true }]);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual([
|
|
{ x: 100, y: null, z: true },
|
|
{ x: 1, y: "a", z: true },
|
|
{ x: 123, y: "abc", z: true },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("multiple updates on str {index: 'y'} with new, old, null in dataset", async function () {
|
|
// FIXME: this is an engine bug
|
|
const table = await perspective.table(
|
|
{
|
|
x: [1, 2, 3],
|
|
y: ["a", null, "c"],
|
|
},
|
|
{ index: "y" },
|
|
);
|
|
|
|
const view = await table.view();
|
|
|
|
await table.update([{ x: 12345, y: "a" }]);
|
|
await table.update([{ x: 100, y: null }]);
|
|
await table.update([{ x: 123, y: "abc" }]);
|
|
|
|
const result = await view.to_json();
|
|
expect(result).toEqual([
|
|
{ x: 100, y: null },
|
|
{ x: 12345, y: "a" },
|
|
{ x: 123, y: "abc" },
|
|
{ x: 3, y: "c" },
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("{index: 'x'} with overlap", async function () {
|
|
var table = await perspective.table(data, { index: "x" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
await table.update(data_2);
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data.slice(0, 2).concat(data_2));
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
it_old_behavior("update and index (int)", async function (done) {
|
|
var table = await perspective.table(meta, { index: "x" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(perspective, updated.delta, data_2);
|
|
let json = await view.to_json();
|
|
expect(json).toEqual(data.slice(0, 2).concat(data_2));
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table.update(data_2);
|
|
});
|
|
|
|
it_old_behavior("update and index (string)", async function (done) {
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(perspective, updated.delta, data_2);
|
|
let json = await view.to_json();
|
|
expect(json).toEqual(data.slice(0, 2).concat(data_2));
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table.update(data_2);
|
|
});
|
|
|
|
test("update with depth expansion", async function () {
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view({
|
|
group_by: ["z", "y"],
|
|
columns: [],
|
|
});
|
|
await table.update(data);
|
|
|
|
let result = await view.to_json();
|
|
|
|
let expected = [
|
|
{ __ROW_PATH__: [] },
|
|
{ __ROW_PATH__: [false] },
|
|
{ __ROW_PATH__: [false, "b"] },
|
|
{ __ROW_PATH__: [false, "d"] },
|
|
{ __ROW_PATH__: [true] },
|
|
{ __ROW_PATH__: [true, "a"] },
|
|
{ __ROW_PATH__: [true, "c"] },
|
|
];
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
it_old_behavior("partial update", async function (done) {
|
|
var partial = [
|
|
{ x: 5, y: "a" },
|
|
{ y: "b", z: true },
|
|
];
|
|
var expected = [
|
|
{ x: 5, y: "a", z: true },
|
|
{ x: 2, y: "b", z: true },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
];
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(
|
|
perspective,
|
|
updated.delta,
|
|
expected.slice(0, 2),
|
|
);
|
|
let json = await view.to_json();
|
|
expect(json).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table.update(partial);
|
|
});
|
|
|
|
it_old_behavior(
|
|
"OG - partial column oriented update",
|
|
async function (done) {
|
|
var partial = {
|
|
x: [5, undefined],
|
|
y: ["a", "b"],
|
|
z: [undefined, true],
|
|
};
|
|
|
|
var expected = [
|
|
{ x: 5, y: "a", z: true },
|
|
{ x: 2, y: "b", z: true },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
];
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(col_data);
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(
|
|
perspective,
|
|
updated.delta,
|
|
expected.slice(0, 2),
|
|
);
|
|
let json = await view.to_json();
|
|
expect(json).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table.update(partial);
|
|
},
|
|
{ skip: true },
|
|
);
|
|
|
|
it_old_behavior(
|
|
"partial column oriented update",
|
|
async function (done) {
|
|
var batches = [
|
|
{
|
|
x: [5],
|
|
y: ["a"],
|
|
},
|
|
{
|
|
y: ["b"],
|
|
z: [true],
|
|
},
|
|
];
|
|
|
|
var expected = [
|
|
{ x: 5, y: "a", z: true },
|
|
{ x: 2, y: "b", z: true },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
];
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(col_data);
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(
|
|
perspective,
|
|
updated.delta,
|
|
expected.slice(0, 2),
|
|
);
|
|
let json = await view.to_json();
|
|
expect(json).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
for (const batch of batches) {
|
|
table.update(batch);
|
|
}
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"Partial column oriented update with new pkey",
|
|
async function (done) {
|
|
const data = {
|
|
x: [0, 1, null, 2, 3],
|
|
y: ["a", "b", "c", "d", "e"],
|
|
};
|
|
const table = await perspective.table(data, { index: "x" });
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
|
|
expect(result).toEqual({
|
|
x: [null, 0, 1, 2, 3], // null before 0
|
|
y: ["c", "a", "b", "d", "e"],
|
|
});
|
|
|
|
const expected = {
|
|
x: [null, 0, 1, 2, 3, 4],
|
|
y: ["c", "a", "b", "d", "e", "f"],
|
|
};
|
|
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(perspective, updated.delta, [
|
|
{
|
|
x: 4,
|
|
y: "f",
|
|
},
|
|
]);
|
|
const result2 = await view.to_columns();
|
|
expect(result2).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
|
|
await table.update({
|
|
x: [4],
|
|
y: ["f"],
|
|
});
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"Partial column oriented update with new pkey, missing columns",
|
|
async function (done) {
|
|
const data = {
|
|
x: [0, 1, null, 2, 3],
|
|
y: ["a", "b", "c", "d", "e"],
|
|
z: [true, false, true, false, true],
|
|
};
|
|
const table = await perspective.table(data, { index: "x" });
|
|
const view = await table.view();
|
|
const result = await view.to_columns();
|
|
|
|
expect(result).toEqual({
|
|
x: [null, 0, 1, 2, 3], // null before 0
|
|
y: ["c", "a", "b", "d", "e"],
|
|
z: [true, true, false, false, true],
|
|
});
|
|
|
|
const expected = {
|
|
x: [null, 0, 1, 2, 3, 4],
|
|
y: ["c", "a", "b", "d", "e", "f"],
|
|
z: [true, true, false, false, true, null],
|
|
};
|
|
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(perspective, updated.delta, [
|
|
{
|
|
x: 4,
|
|
y: "f",
|
|
z: null,
|
|
},
|
|
]);
|
|
const result2 = await view.to_columns();
|
|
expect(result2).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
|
|
await table.update({
|
|
x: [4],
|
|
y: ["f"],
|
|
});
|
|
},
|
|
);
|
|
|
|
it_old_behavior(
|
|
"partial column oriented update with entire columns missing",
|
|
async function (done) {
|
|
var partial = {
|
|
y: ["a", "b"],
|
|
z: [false, true],
|
|
};
|
|
|
|
var expected = [
|
|
{ x: 1, y: "a", z: false },
|
|
{ x: 2, y: "b", z: true },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
];
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(col_data);
|
|
view.on_update(
|
|
async function (updated) {
|
|
await match_delta(
|
|
perspective,
|
|
updated.delta,
|
|
expected.slice(0, 2),
|
|
);
|
|
let json = await view.to_json();
|
|
expect(json).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
},
|
|
{ mode: "row" },
|
|
);
|
|
await table.update(partial);
|
|
},
|
|
);
|
|
});
|
|
|
|
test.describe("null handling", function () {
|
|
test("recalculates sum aggregates when a null unsets a value", async function () {
|
|
var table = await perspective.table(
|
|
[
|
|
{ x: 1, y: 1 },
|
|
{ x: 2, y: 1 },
|
|
],
|
|
{ index: "x" },
|
|
);
|
|
await table.update([{ x: 2, y: null }]);
|
|
var view = await table.view({
|
|
group_by: ["x"],
|
|
columns: ["y"],
|
|
});
|
|
let json = await view.to_json();
|
|
expect(json).toEqual([
|
|
{ __ROW_PATH__: [], y: 1 },
|
|
{ __ROW_PATH__: [1], y: 1 },
|
|
{ __ROW_PATH__: [2], y: 0 },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("can be removed entirely", async function () {
|
|
var table = await perspective.table([{ x: 1, y: 1 }], {
|
|
index: "x",
|
|
});
|
|
await table.update([{ x: 1, y: null }]);
|
|
await table.update([{ x: 1, y: 1 }]);
|
|
var view = await table.view();
|
|
let json = await view.to_json();
|
|
expect(json).toEqual([{ x: 1, y: 1 }]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("partial update with null unsets value", async function () {
|
|
var partial = [{ x: null, y: "a", z: false }];
|
|
var expected = [
|
|
{ x: null, y: "a", z: false },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
];
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
await table.update(partial);
|
|
const json = await view.to_json();
|
|
expect(json).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("update by adding rows (new pkeys) with partials/nulls", async function () {
|
|
var update = [{ x: null, y: "e", z: null }];
|
|
var expected = [
|
|
{ x: 1, y: "a", z: true },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
{ x: null, y: "e", z: null },
|
|
];
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(data);
|
|
await table.update(update);
|
|
const json = await view.to_json();
|
|
expect(json).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("partial column oriented update with null unsets value", async function () {
|
|
var partial = {
|
|
x: [null],
|
|
y: ["a"],
|
|
};
|
|
|
|
var expected = [
|
|
{ x: null, y: "a", z: true },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "c", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
];
|
|
var table = await perspective.table(meta, { index: "y" });
|
|
var view = await table.view();
|
|
await table.update(col_data);
|
|
await table.update(partial);
|
|
const json = await view.to_json();
|
|
expect(json).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Viewport", function () {
|
|
test.skip("`height`", async function () {
|
|
var table = await perspective.table(data);
|
|
var view = await table.view({
|
|
viewport: {
|
|
height: 2,
|
|
},
|
|
});
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data.slice(0, 2));
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("`top`", async function () {
|
|
var table = await perspective.table(data);
|
|
var view = await table.view({
|
|
viewport: {
|
|
top: 2,
|
|
},
|
|
});
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(data.slice(2));
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("`width`", async function () {
|
|
var table = await perspective.table(data);
|
|
var view = await table.view({
|
|
viewport: {
|
|
width: 2,
|
|
},
|
|
});
|
|
var result2 = _.map(data, (x) => _.pick(x, "x", "y"));
|
|
let result = await view.to_json();
|
|
expect(result).toEqual(result2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("`left`", async function () {
|
|
var table = await perspective.table(data);
|
|
var view = await table.view({
|
|
viewport: {
|
|
left: 1,
|
|
},
|
|
});
|
|
var result = _.map(data, function (x) {
|
|
return _.pick(x, "y", "z");
|
|
});
|
|
let result2 = await view.to_json();
|
|
expect(result).toEqual(result2);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test.skip("All", async function () {
|
|
var table = await perspective.table(data);
|
|
var view = await table.view({
|
|
viewport: {
|
|
top: 1,
|
|
left: 1,
|
|
width: 1,
|
|
height: 2,
|
|
},
|
|
});
|
|
var result = _.map(data, function (x) {
|
|
return _.pick(x, "y");
|
|
});
|
|
let result2 = await view.to_json();
|
|
expect(result2).toEqual(result.slice(1, 3));
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("implicit index", function () {
|
|
test("should apply single partial update on unindexed table using row id from '__INDEX__'", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update([
|
|
{
|
|
__INDEX__: 2,
|
|
y: "new_string",
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[2]["y"] = "new_string";
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should partial update on unindexed table, column dataset", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update({
|
|
__INDEX__: [2],
|
|
y: ["new_string"],
|
|
});
|
|
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
// does not unset any values
|
|
expect(result).toEqual([
|
|
{ x: 1, y: "a", z: true },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "new_string", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should partial update and unset on unindexed table", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update([
|
|
{
|
|
__INDEX__: 2,
|
|
y: "new_string",
|
|
z: null,
|
|
},
|
|
]);
|
|
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
// does not unset any values
|
|
expect(result).toEqual([
|
|
{ x: 1, y: "a", z: true },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "new_string", z: null },
|
|
{ x: 4, y: "d", z: false },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
// ORIGINAL VERSION OF TEST BELOW
|
|
test.skip("OG - should partial update and unset on unindexed table, column dataset", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update({
|
|
__INDEX__: [0, 2],
|
|
y: [undefined, "new_string"],
|
|
z: [null, undefined],
|
|
});
|
|
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
// does not unset any values
|
|
expect(result).toEqual([
|
|
{ x: 1, y: "a", z: null },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "new_string", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
// Split the updates to avoid handling `undefined` values.
|
|
test("should partial update and unset on unindexed table, column dataset", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update({
|
|
__INDEX__: [0],
|
|
z: [null],
|
|
});
|
|
await table.update({
|
|
__INDEX__: [2],
|
|
y: ["new_string"],
|
|
});
|
|
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
// does not unset any values
|
|
expect(result).toEqual([
|
|
{ x: 1, y: "a", z: null },
|
|
{ x: 2, y: "b", z: false },
|
|
{ x: 3, y: "new_string", z: true },
|
|
{ x: 4, y: "d", z: false },
|
|
]);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should apply single multi-column partial update on unindexed table using row id from '__INDEX__'", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update([
|
|
{
|
|
__INDEX__: 2,
|
|
y: "new_string",
|
|
x: 100,
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[2]["x"] = 100;
|
|
expected[2]["y"] = "new_string";
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should apply updates using '__INDEX__' on a table with explicit index set", async function () {
|
|
let table = await perspective.table(data, { index: "x" });
|
|
await table.update([
|
|
{
|
|
__INDEX__: 2,
|
|
y: "new_string",
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[1]["y"] = "new_string";
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should apply multiple sequential updates using '__INDEX__' on a table with explicit index set", async function () {
|
|
let table = await perspective.table(data, { index: "x" });
|
|
await table.update([
|
|
{
|
|
__INDEX__: 2,
|
|
y: "new_string",
|
|
},
|
|
{
|
|
__INDEX__: 3,
|
|
y: "new_string",
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[1]["y"] = "new_string";
|
|
expected[2]["y"] = "new_string";
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should apply mulitple nonsequential updates using '__INDEX__' on a table with explicit index set", async function () {
|
|
let table = await perspective.table(data, { index: "x" });
|
|
await table.update([
|
|
{
|
|
__INDEX__: 2,
|
|
y: "new_string",
|
|
},
|
|
{
|
|
__INDEX__: 4,
|
|
y: "new_string",
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[1]["y"] = "new_string";
|
|
expected[3]["y"] = "new_string";
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should apply multiple sequential partial updates on unindexed table using '__INDEX__'", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update([
|
|
{
|
|
__INDEX__: 0,
|
|
y: "new_string1",
|
|
},
|
|
{
|
|
__INDEX__: 1,
|
|
y: "new_string2",
|
|
},
|
|
{
|
|
__INDEX__: 2,
|
|
y: "new_string3",
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[0]["y"] = "new_string1";
|
|
expected[1]["y"] = "new_string2";
|
|
expected[2]["y"] = "new_string3";
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should correctly apply multiple out-of-sequence partial updates on unindexed table using '__INDEX__'", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update([
|
|
{
|
|
__INDEX__: 0,
|
|
y: "new_string1",
|
|
},
|
|
{
|
|
__INDEX__: 2,
|
|
y: "new_string3",
|
|
},
|
|
{
|
|
__INDEX__: 3,
|
|
y: "new_string4",
|
|
},
|
|
{
|
|
__INDEX__: 1,
|
|
y: "new_string2",
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[0]["y"] = "new_string1";
|
|
expected[1]["y"] = "new_string2";
|
|
expected[2]["y"] = "new_string3";
|
|
expected[3]["y"] = "new_string4";
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should stack multiple partial updates on unindexed table using the same '__INDEX__'", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update([
|
|
{
|
|
__INDEX__: 0,
|
|
y: "new_string1",
|
|
},
|
|
{
|
|
__INDEX__: 0,
|
|
y: "new_string2",
|
|
},
|
|
{
|
|
__INDEX__: 0,
|
|
y: "new_string3",
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[0]["y"] = "new_string3";
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("updates without '__INDEX' should append", async function () {
|
|
let table = await perspective.table(data);
|
|
await table.update([
|
|
{
|
|
__INDEX__: 0,
|
|
y: "new_string",
|
|
},
|
|
]);
|
|
await table.update([
|
|
{
|
|
y: "new_string",
|
|
},
|
|
]);
|
|
let view = await table.view();
|
|
let result = await view.to_json();
|
|
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[0]["y"] = "new_string";
|
|
expected.push({ x: null, y: "new_string", z: null });
|
|
|
|
expect(result).toEqual(expected);
|
|
view.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should partial update on 1-sided views using implicit '__INDEX__'", async function () {
|
|
let table = await perspective.table(data);
|
|
let view = await table.view({
|
|
group_by: ["x"],
|
|
});
|
|
|
|
await table.update([
|
|
{
|
|
__INDEX__: 0,
|
|
x: 100,
|
|
},
|
|
]);
|
|
|
|
let result = await view.to_json();
|
|
// update should be applied properly
|
|
expect(result).toEqual([
|
|
{ __ROW_PATH__: [], x: 109, y: 4, z: 4 },
|
|
{ __ROW_PATH__: [2], x: 2, y: 1, z: 1 },
|
|
{ __ROW_PATH__: [3], x: 3, y: 1, z: 1 },
|
|
{ __ROW_PATH__: [4], x: 4, y: 1, z: 1 },
|
|
{ __ROW_PATH__: [100], x: 100, y: 1, z: 1 },
|
|
]);
|
|
|
|
// check that un-pivoted view reflects data correctly
|
|
let view2 = await table.view();
|
|
let result2 = await view2.to_json();
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[0]["x"] = 100;
|
|
|
|
expect(result2).toEqual(expected);
|
|
|
|
view.delete();
|
|
view2.delete();
|
|
table.delete();
|
|
});
|
|
|
|
test("should partial update on 2-sided views using implicit '__INDEX__'", async function () {
|
|
let table = await perspective.table(data);
|
|
let view = await table.view({
|
|
group_by: ["x"],
|
|
split_by: ["y"],
|
|
});
|
|
|
|
await table.update([
|
|
{
|
|
__INDEX__: 0,
|
|
x: 100,
|
|
},
|
|
]);
|
|
|
|
let result = await view.to_json();
|
|
// update should be applied properly
|
|
expect(result).toEqual([
|
|
{
|
|
__ROW_PATH__: [],
|
|
"a|x": 100,
|
|
"a|y": 1,
|
|
"a|z": 1,
|
|
"b|x": 2,
|
|
"b|y": 1,
|
|
"b|z": 1,
|
|
"c|x": 3,
|
|
"c|y": 1,
|
|
"c|z": 1,
|
|
"d|x": 4,
|
|
"d|y": 1,
|
|
"d|z": 1,
|
|
},
|
|
{
|
|
__ROW_PATH__: [2],
|
|
"a|x": null,
|
|
"a|y": null,
|
|
"a|z": null,
|
|
"b|x": 2,
|
|
"b|y": 1,
|
|
"b|z": 1,
|
|
"c|x": null,
|
|
"c|y": null,
|
|
"c|z": null,
|
|
"d|x": null,
|
|
"d|y": null,
|
|
"d|z": null,
|
|
},
|
|
{
|
|
__ROW_PATH__: [3],
|
|
"a|x": null,
|
|
"a|y": null,
|
|
"a|z": null,
|
|
"b|x": null,
|
|
"b|y": null,
|
|
"b|z": null,
|
|
"c|x": 3,
|
|
"c|y": 1,
|
|
"c|z": 1,
|
|
"d|x": null,
|
|
"d|y": null,
|
|
"d|z": null,
|
|
},
|
|
{
|
|
__ROW_PATH__: [4],
|
|
"a|x": null,
|
|
"a|y": null,
|
|
"a|z": null,
|
|
"b|x": null,
|
|
"b|y": null,
|
|
"b|z": null,
|
|
"c|x": null,
|
|
"c|y": null,
|
|
"c|z": null,
|
|
"d|x": 4,
|
|
"d|y": 1,
|
|
"d|z": 1,
|
|
},
|
|
{
|
|
__ROW_PATH__: [100],
|
|
"a|x": 100,
|
|
"a|y": 1,
|
|
"a|z": 1,
|
|
"b|x": null,
|
|
"b|y": null,
|
|
"b|z": null,
|
|
"c|x": null,
|
|
"c|y": null,
|
|
"c|z": null,
|
|
"d|x": null,
|
|
"d|y": null,
|
|
"d|z": null,
|
|
},
|
|
]);
|
|
|
|
// check that un-pivoted view reflects data correctly
|
|
let view2 = await table.view();
|
|
let result2 = await view2.to_json();
|
|
let expected = JSON.parse(JSON.stringify(data));
|
|
expected[0]["x"] = 100;
|
|
|
|
expect(result2).toEqual(expected);
|
|
|
|
view.delete();
|
|
view2.delete();
|
|
table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("Remove update", function () {
|
|
it_old_behavior("Should remove a single update", async function (done) {
|
|
let count = 0;
|
|
const cb1 = () => {
|
|
count += 1;
|
|
};
|
|
|
|
const cb2 = () => {
|
|
expect(count).toEqual(0);
|
|
setTimeout(() => {
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
}, 0);
|
|
};
|
|
|
|
const table = await perspective.table(meta);
|
|
const view = await table.view();
|
|
const sub1 = view.on_update(cb1);
|
|
view.on_update(cb2);
|
|
view.remove_update(await sub1);
|
|
await table.update(data);
|
|
});
|
|
|
|
it_old_behavior(
|
|
"Should remove multiple updates",
|
|
async function (done) {
|
|
let count1 = 0;
|
|
const cb1 = () => {
|
|
count1 += 1;
|
|
};
|
|
|
|
let count2 = 0;
|
|
const cb2 = () => {
|
|
count2 += 1;
|
|
};
|
|
|
|
const cb3 = function () {
|
|
// cb2 should have been called
|
|
expect(count1).toEqual(0);
|
|
expect(count2).toEqual(0);
|
|
setTimeout(() => {
|
|
view.delete();
|
|
table.delete();
|
|
done();
|
|
}, 0);
|
|
};
|
|
|
|
const table = await perspective.table(meta);
|
|
const view = await table.view();
|
|
const sub1 = view.on_update(cb1);
|
|
const sub2 = view.on_update(cb2);
|
|
view.on_update(cb3);
|
|
view.remove_update(await sub1);
|
|
view.remove_update(await sub2);
|
|
await table.update(data);
|
|
},
|
|
);
|
|
});
|
|
|
|
test.describe("Filtered update and remove", () => {
|
|
let table, flat_view, filtered_view;
|
|
|
|
test.beforeEach(async () => {
|
|
table = await perspective.table(
|
|
{
|
|
x: "integer",
|
|
y: "string",
|
|
},
|
|
{ index: "x" },
|
|
);
|
|
|
|
flat_view = await table.view();
|
|
filtered_view = await table.view({ filter: [["y", "==", "a"]] });
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
await filtered_view.delete();
|
|
await flat_view.delete();
|
|
|
|
filtered_view = null;
|
|
flat_view = null;
|
|
});
|
|
|
|
test.afterAll(async () => {
|
|
if (filtered_view) await filtered_view.delete();
|
|
if (flat_view) await flat_view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("View output should always be consistent with filter", async () => {
|
|
let i = 0,
|
|
op = 0,
|
|
update_idx = 1,
|
|
remove_idx = 0;
|
|
|
|
for (i; i < 90; i++) {
|
|
if (op > 2) op = 0;
|
|
|
|
switch (op) {
|
|
case 0:
|
|
{
|
|
// insert
|
|
await table.update({
|
|
x: [i],
|
|
y: [i % 2 ? "a" : "b"],
|
|
});
|
|
}
|
|
break;
|
|
case 1:
|
|
{
|
|
// partial update
|
|
await table.update({
|
|
x: [update_idx],
|
|
y: [update_idx % 2 ? "b" : "a"],
|
|
});
|
|
|
|
update_idx++;
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
// remove
|
|
await table.remove([remove_idx]);
|
|
remove_idx++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
const flat = await flat_view.to_json();
|
|
expect(await filtered_view.to_json()).toEqual(
|
|
flat.filter((row) => row.y === "a"),
|
|
);
|
|
op++;
|
|
}
|
|
});
|
|
|
|
test("View output should always be consistent with filter, recreating views", async () => {
|
|
let i = 0,
|
|
op = 0,
|
|
update_idx = 1,
|
|
remove_idx = 0;
|
|
|
|
for (i; i < 90; i++) {
|
|
if (op > 3) op = 0;
|
|
|
|
switch (op) {
|
|
case 0:
|
|
{
|
|
// insert
|
|
await table.update({
|
|
x: [i],
|
|
y: [i % 2 ? "a" : "b"],
|
|
});
|
|
}
|
|
break;
|
|
case 1:
|
|
{
|
|
// partial update
|
|
await table.update({
|
|
x: [update_idx],
|
|
y: [update_idx % 2 ? "b" : "a"],
|
|
});
|
|
|
|
update_idx++;
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
// remove
|
|
await table.remove([remove_idx]);
|
|
remove_idx++;
|
|
}
|
|
break;
|
|
case 3: {
|
|
// recreate views
|
|
await flat_view.delete();
|
|
flat_view = await table.view();
|
|
|
|
await filtered_view.delete();
|
|
filtered_view = await table.view({
|
|
filter: [["y", "==", "a"]],
|
|
});
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
const flat = await flat_view.to_json();
|
|
expect(await filtered_view.to_json()).toEqual(
|
|
flat.filter((row) => row.y === "a"),
|
|
);
|
|
op++;
|
|
}
|
|
});
|
|
|
|
test("View output should always be consistent with filter, deterministic", async () => {
|
|
filtered_view = await table.view({ filter: [["y", "==", "b"]] });
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
// append new rows
|
|
await table.update([{ x: i, y: "a" }]);
|
|
expect(await table.size()).toEqual(i + 1);
|
|
|
|
// make sure appended
|
|
const result = await flat_view.to_json();
|
|
expect(result[result.length - 1]).toEqual({ x: i, y: "a" });
|
|
|
|
// filtering
|
|
expect(await filtered_view.to_json()).toEqual(
|
|
result.filter((row) => row.y === "b"),
|
|
);
|
|
}
|
|
|
|
await filtered_view.delete();
|
|
|
|
// new view with filter
|
|
filtered_view = await table.view({ filter: [["y", "==", "a"]] });
|
|
let flat = await flat_view.to_json();
|
|
let filtered = await filtered_view.to_json();
|
|
|
|
expect(flat.length).toEqual(filtered.length);
|
|
expect(filtered).toEqual(flat);
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
// partial update rows
|
|
if (i % 2 == 0) {
|
|
await table.update([{ x: i, y: "b" }]);
|
|
|
|
flat = await flat_view.to_json();
|
|
filtered = await filtered_view.to_json();
|
|
expect(flat.length).toBeGreaterThan(filtered.length);
|
|
expect(flat.filter((row) => row.x === i)[0]).toEqual({
|
|
x: i,
|
|
y: "b",
|
|
});
|
|
expect(filtered).toEqual(
|
|
flat.filter((row) => row.y === "a"),
|
|
);
|
|
|
|
// partial updates not appends
|
|
expect(await table.size()).toEqual(10);
|
|
}
|
|
}
|
|
|
|
// Remove "b" rows
|
|
flat = await flat_view.to_json();
|
|
await table.remove(
|
|
flat.filter((row) => row.y === "b").map((row) => row.x),
|
|
);
|
|
expect(await flat_view.to_json()).toEqual([
|
|
{ x: 1, y: "a" },
|
|
{ x: 3, y: "a" },
|
|
{ x: 5, y: "a" },
|
|
{ x: 7, y: "a" },
|
|
{ x: 9, y: "a" },
|
|
]);
|
|
});
|
|
|
|
test("Output consistent with filter, out of order", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "string",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({
|
|
index: ["a", "d", "b"],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
await tbl.remove(["b"]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "abc"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: "a", x: "abc" }]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, string small", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "string",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({ index: ["a", "c"], x: ["abc", "def"] });
|
|
await tbl.remove(["c"]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "abc"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: "a", x: "abc" }]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, ordered", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "string",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({
|
|
index: ["a", "b", "c"],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
await tbl.remove(["b"]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "abc"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: "a", x: "abc" }]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, numeric index out of order", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "float",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({ index: [50, 1, 2], x: ["abc", "def", "acc"] });
|
|
await tbl.remove([1]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "acc"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: 2, x: "acc" }]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, numeric index ordered", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "float",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({ index: [1, 2, 3], x: ["abc", "def", "acc"] });
|
|
await tbl.remove([3]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "def"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: 2, x: "def" }]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, date index out of order", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "date",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({
|
|
index: [
|
|
new Date(2021, 3, 15),
|
|
new Date(2009, 1, 13),
|
|
new Date(2015, 5, 10),
|
|
],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
await tbl.remove([1]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "acc"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([
|
|
{ index: new Date(2015, 5, 10).getTime(), x: "acc" },
|
|
]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, date index ordered", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "date",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({
|
|
index: [
|
|
new Date(2009, 1, 13),
|
|
new Date(2015, 5, 10),
|
|
new Date(2021, 3, 15),
|
|
],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
await tbl.remove([3]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "def"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([
|
|
{ index: new Date(2015, 5, 10).getTime(), x: "def" },
|
|
]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, datetime index out of order", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "datetime",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({
|
|
index: [
|
|
new Date(2021, 3, 15, 5, 10, 3),
|
|
new Date(2009, 1, 13, 0, 0, 1),
|
|
new Date(2015, 5, 10, 15, 10, 2),
|
|
],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
await tbl.remove([1]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "acc"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([
|
|
{ index: new Date(2015, 5, 10, 15, 10, 2).getTime(), x: "acc" },
|
|
]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, datetime index ordered", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "datetime",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({
|
|
index: [
|
|
new Date(2009, 1, 13, 0, 0, 1),
|
|
new Date(2015, 5, 10, 15, 10, 2),
|
|
new Date(2021, 3, 15, 5, 10, 3),
|
|
],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
await tbl.remove([3]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "def"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([
|
|
{ index: new Date(2015, 5, 10, 15, 10, 2).getTime(), x: "def" },
|
|
]);
|
|
console.log(await view2.to_json());
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, out of order updates", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "string",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({
|
|
index: ["a", "d", "b"],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
await tbl.remove(["b"]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "abc"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: "a", x: "abc" }]);
|
|
|
|
await tbl.update([{ index: "b", x: "abc" }]);
|
|
|
|
expect(await view2.to_json()).toEqual([
|
|
{ index: "a", x: "abc" },
|
|
{ index: "b", x: "abc" },
|
|
]);
|
|
|
|
await tbl.remove(["a"]);
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: "b", x: "abc" }]);
|
|
|
|
await tbl.update([
|
|
{ index: "a", x: "abc" },
|
|
{ index: null, x: "abc" },
|
|
]);
|
|
|
|
expect(await view2.to_json()).toEqual([
|
|
{ index: null, x: "abc" },
|
|
{ index: "a", x: "abc" },
|
|
{ index: "b", x: "abc" },
|
|
]);
|
|
|
|
const indices = "abcdefghijklmnopqrstuvwxyz".split("");
|
|
|
|
for (const idx of indices) {
|
|
await tbl.update([{ index: idx, x: "abc" }]);
|
|
}
|
|
|
|
let result = await view2.to_json();
|
|
expect(result[0]).toEqual({ index: null, x: "abc" });
|
|
|
|
result = result.slice(1);
|
|
|
|
for (let i = 0; i < result.length; i++) {
|
|
expect(result[i]).toEqual({ index: indices[i], x: "abc" });
|
|
}
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Output consistent with filter, out of order updates random", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "string",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
const view = await tbl.view();
|
|
|
|
await tbl.update({
|
|
index: ["a", "d", "b"],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
await tbl.remove(["b"]);
|
|
|
|
const view2 = await tbl.view({ filter: [["x", "==", "abc"]] });
|
|
await view.delete();
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: "a", x: "abc" }]);
|
|
|
|
await tbl.update([{ index: "b", x: "abc" }]);
|
|
|
|
expect(await view2.to_json()).toEqual([
|
|
{ index: "a", x: "abc" },
|
|
{ index: "b", x: "abc" },
|
|
]);
|
|
|
|
await tbl.remove(["a"]);
|
|
|
|
expect(await view2.to_json()).toEqual([{ index: "b", x: "abc" }]);
|
|
|
|
await tbl.update([
|
|
{ index: "a", x: "abc" },
|
|
{ index: null, x: "abc" },
|
|
]);
|
|
|
|
expect(await view2.to_json()).toEqual([
|
|
{ index: null, x: "abc" },
|
|
{ index: "a", x: "abc" },
|
|
{ index: "b", x: "abc" },
|
|
]);
|
|
|
|
// randomize
|
|
let indices = "abcdefghijklmnopqrstuvwxyz"
|
|
.split("")
|
|
.map((v) => {
|
|
return { value: v, key: Math.random() };
|
|
})
|
|
.sort((a, b) => a.key - b.key)
|
|
.map((v) => v.value);
|
|
|
|
// console.log(indices);
|
|
|
|
for (const idx of indices) {
|
|
await tbl.update([{ index: idx, x: "abc" }]);
|
|
}
|
|
|
|
let result = await view2.to_json();
|
|
expect(result[0]).toEqual({ index: null, x: "abc" });
|
|
|
|
result = result.slice(1);
|
|
|
|
indices = indices.sort();
|
|
|
|
for (let i = 0; i < result.length; i++) {
|
|
expect(result[i]).toEqual({ index: indices[i], x: "abc" });
|
|
}
|
|
|
|
await view2.delete();
|
|
await tbl.delete();
|
|
});
|
|
|
|
test("Delete a table with pending updates", async () => {
|
|
const tbl = await perspective.table(
|
|
{
|
|
index: "string",
|
|
x: "string",
|
|
},
|
|
{ index: "index" },
|
|
);
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
tbl.update({
|
|
index: ["a", "d", "b"],
|
|
x: ["abc", "def", "acc"],
|
|
});
|
|
}
|
|
await tbl.delete();
|
|
});
|
|
});
|
|
|
|
// Regression coverage for the `t_ftrav::step_end` fast-path leak: a
|
|
// pass containing only `update_row` calls (every pkey already exists)
|
|
// used to take the fast path because `m_step_inserts` and
|
|
// `m_step_deletes` are both zero — leaving `m_updated` flags stamped
|
|
// on `m_index` and dropping the staged replacements from
|
|
// `m_new_elems`. The next pass that *did* trigger a rebuild (any
|
|
// `add_row` or `delete_row`) skipped the flagged entries with no
|
|
// counterpart to reinstate them, and the rows silently disappeared
|
|
// from the sorted view.
|
|
test.describe("Update under sorted view", function () {
|
|
const d1 = +new Date("2024-01-01T00:00:00Z");
|
|
const d2 = +new Date("2024-01-02T00:00:00Z");
|
|
const d3 = +new Date("2024-01-03T00:00:00Z");
|
|
const d4 = +new Date("2024-01-04T00:00:00Z");
|
|
const d5 = +new Date("2024-01-05T00:00:00Z");
|
|
|
|
async function seed(perspective) {
|
|
const table = await perspective.table(
|
|
{ id: "integer", ts: "datetime", payload: "string" },
|
|
{ index: "id" },
|
|
);
|
|
await table.update({
|
|
id: [1, 2, 3, 4],
|
|
ts: [d1, d2, d3, d4],
|
|
payload: ["a", "b", "c", "d"],
|
|
});
|
|
return table;
|
|
}
|
|
|
|
test("baseline: update existing rows then append a new row", async function () {
|
|
const table = await seed(perspective);
|
|
const view = await table.view({ sort: [["ts", "asc"]] });
|
|
await table.update({ id: [2, 3], payload: ["bb", "cc"] });
|
|
await table.update({ id: [5], ts: [d5], payload: ["e"] });
|
|
|
|
const cols = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(5);
|
|
expect(cols.id).toEqual([1, 2, 3, 4, 5]);
|
|
expect(cols.payload).toEqual(["a", "bb", "cc", "d", "e"]);
|
|
expect(cols.ts).toEqual([d1, d2, d3, d4, d5]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("update-only pass is correct on its own", async function () {
|
|
const table = await seed(perspective);
|
|
const view = await table.view({ sort: [["ts", "asc"]] });
|
|
await table.update({ id: [2, 3], payload: ["bb", "cc"] });
|
|
|
|
const cols = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(4);
|
|
expect(cols.id).toEqual([1, 2, 3, 4]);
|
|
expect(cols.payload).toEqual(["a", "bb", "cc", "d"]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("multiple update-only passes before an insert", async function () {
|
|
const table = await seed(perspective);
|
|
const view = await table.view({ sort: [["ts", "asc"]] });
|
|
await table.update({ id: [2], payload: ["b1"] });
|
|
await table.update({ id: [3], payload: ["c1"] });
|
|
await table.update({ id: [2], payload: ["b2"] });
|
|
|
|
await table.update({ id: [5], ts: [d5], payload: ["e"] });
|
|
|
|
const cols = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(5);
|
|
expect(cols.id).toEqual([1, 2, 3, 4, 5]);
|
|
expect(cols.payload).toEqual(["a", "b2", "c1", "d", "e"]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("update that changes the sort key, then append", async function () {
|
|
const table = await seed(perspective);
|
|
const view = await table.view({ sort: [["ts", "asc"]] });
|
|
const d_late = +new Date("2024-01-10T00:00:00Z");
|
|
await table.update({ id: [2], ts: [d_late] });
|
|
|
|
await table.update({ id: [5], ts: [d5], payload: ["e"] });
|
|
|
|
const cols = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(5);
|
|
expect(cols.id).toEqual([1, 3, 4, 5, 2]);
|
|
expect(cols.ts).toEqual([d1, d3, d4, d5, d_late]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("update-only pass followed by a delete", async function () {
|
|
const table = await seed(perspective);
|
|
const view = await table.view({ sort: [["ts", "asc"]] });
|
|
|
|
await table.update({ id: [2, 3], payload: ["bb", "cc"] });
|
|
await table.remove([4]);
|
|
|
|
const cols = await view.to_columns();
|
|
expect(await view.num_rows()).toEqual(3);
|
|
expect(cols.id).toEqual([1, 2, 3]);
|
|
expect(cols.payload).toEqual(["a", "bb", "cc"]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("no sort: update-only pass + append is unaffected", async function () {
|
|
const table = await seed(perspective);
|
|
const view = await table.view();
|
|
|
|
await table.update({ id: [2, 3], payload: ["bb", "cc"] });
|
|
await table.update({ id: [5], ts: [d5], payload: ["e"] });
|
|
|
|
expect(await view.num_rows()).toEqual(5);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
})(perspective);
|