1034 lines
37 KiB
JavaScript
1034 lines
37 KiB
JavaScript
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
|
|
import { test, expect } from "@perspective-dev/test";
|
|
import perspective from "../perspective_client";
|
|
|
|
/**
|
|
* Tests the correctness of each datetime computation function in various
|
|
* environments and parameters - different types, nulls, undefined, etc.
|
|
*/
|
|
((perspective) => {
|
|
test.describe("string()", () => {
|
|
test("Should create string from all column types", async () => {
|
|
const table = await perspective.table({
|
|
a: "date",
|
|
b: "datetime",
|
|
c: "integer",
|
|
d: "float",
|
|
e: "string",
|
|
f: "boolean",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
computed: ' string("a")',
|
|
computed2: ' string("b")',
|
|
computed3: ' string("c")',
|
|
computed4: ' string("d")',
|
|
computed5: ' string("e")',
|
|
computed6: ' string("f")',
|
|
computed7: " string(1234.5678)",
|
|
},
|
|
});
|
|
|
|
await table.update({
|
|
a: [new Date(2020, 4, 30), new Date(2021, 6, 13)],
|
|
b: [
|
|
new Date(2015, 10, 29, 23, 59, 59),
|
|
new Date(2016, 10, 29, 23, 59, 59),
|
|
],
|
|
c: [12345678, 1293879852],
|
|
d: [1.2792013981, 19.218975981],
|
|
e: ["abcdefghijklmnop", "def"],
|
|
f: [false, true],
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
computed: "string",
|
|
computed2: "string",
|
|
computed3: "string",
|
|
computed4: "string",
|
|
computed5: "string",
|
|
computed6: "string",
|
|
computed7: "string",
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
|
|
expect(result["computed"]).toEqual(["2020-05-30", "2021-07-13"]);
|
|
expect(result["computed2"]).toEqual([
|
|
"2015-11-29 23:59:59.000",
|
|
"2016-11-29 23:59:59.000",
|
|
]);
|
|
expect(result["computed3"]).toEqual(["12345678", "1293879852"]);
|
|
expect(result["computed4"]).toEqual(["1.2792", "19.219"]);
|
|
expect(result["computed5"]).toEqual(["abcdefghijklmnop", "def"]);
|
|
expect(result["computed6"]).toEqual(["false", "true"]);
|
|
expect(result["computed7"]).toEqual(["1234.57", "1234.57"]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create string from scalars", async () => {
|
|
const table = await perspective.table({
|
|
x: [1],
|
|
});
|
|
|
|
const view = await table.view({
|
|
group_by: ["computed"],
|
|
aggregates: {
|
|
computed: "last",
|
|
computed2: "last",
|
|
computed3: "last",
|
|
},
|
|
expressions: {
|
|
computed: " string('abcdefg')",
|
|
computed2: " string(1234)",
|
|
computed3: " string(1234.5678)",
|
|
},
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
computed: "string",
|
|
computed2: "string",
|
|
computed3: "string",
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
|
|
expect(result["__ROW_PATH__"]).toEqual([[], ["abcdefg"]]);
|
|
expect(result["computed2"]).toEqual(["1234", "1234"]);
|
|
expect(result["computed3"]).toEqual(["1234.57", "1234.57"]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("integer()", () => {
|
|
test("Should create integers from scalars", async () => {
|
|
const table = await perspective.table({ x: [1] });
|
|
const view = await table.view({
|
|
expressions: {
|
|
[`computed`]: `integer(999999.999)`,
|
|
[`computed2`]: `integer(2147483648)`,
|
|
[`computed3`]: `integer(-2147483649)`,
|
|
[`computed4`]: `integer(123456789)`,
|
|
[`computed5`]: `integer(0.00001)`,
|
|
[`computed6`]: `integer(1.9999999999)`,
|
|
[`computed7`]: `integer(2147483647)`,
|
|
[`computed8`]: `integer(-2147483647)`,
|
|
[`computed9`]: `integer('957187281')`,
|
|
[`computed10`]: `integer('1928.2817')`,
|
|
[`computed11`]: `integer('1234abcd')`,
|
|
[`computed12`]: `integer('abcdefg1234')`,
|
|
[`computed13`]: `integer('2147483648')`,
|
|
[`computed14`]: `integer('-2147483649')`,
|
|
},
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
|
|
expect(result["computed"]).toEqual([999999]);
|
|
expect(result["computed2"]).toEqual([null]);
|
|
expect(result["computed3"]).toEqual([null]);
|
|
expect(result["computed4"]).toEqual([123456789]);
|
|
expect(result["computed5"]).toEqual([0]);
|
|
expect(result["computed6"]).toEqual([1]);
|
|
expect(result["computed7"]).toEqual([2147483647]);
|
|
expect(result["computed8"]).toEqual([-2147483647]);
|
|
expect(result["computed9"]).toEqual([957187281]);
|
|
expect(result["computed10"]).toEqual([1928]);
|
|
|
|
// unparsable strings
|
|
expect(result["computed11"]).toEqual([null]);
|
|
expect(result["computed12"]).toEqual([null]);
|
|
// check for overflow
|
|
expect(result["computed13"]).toEqual([null]);
|
|
expect(result["computed14"]).toEqual([null]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
// TODO Is this behavior correct? The purpose of this test is to check
|
|
// that integer->integer casting works, but it is also implicitly
|
|
// testing that `float`s are coerced to `integer`s when the column
|
|
// already has `integer` type. I think the new API is correct to reject
|
|
// this behavior.
|
|
test.skip("Should create integers from integer columns", async () => {
|
|
const table = await perspective.table({ x: "integer" });
|
|
const view = await table.view({
|
|
expressions: { computed: `integer("x")` },
|
|
});
|
|
|
|
await table.update({
|
|
x: [100, -17238.8123, 0.890798, -1.1295, null, 12836215.128937],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
100,
|
|
-17238,
|
|
0,
|
|
-1,
|
|
null,
|
|
12836215,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create integers from float columns", async () => {
|
|
const table = await perspective.table({ x: "float" });
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: `integer("x")` },
|
|
});
|
|
|
|
await table.update({
|
|
x: [
|
|
100.9999999,
|
|
-17238.8123,
|
|
0.890798,
|
|
-1.1295,
|
|
null,
|
|
12836215.128937,
|
|
],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
100,
|
|
-17238,
|
|
0,
|
|
-1,
|
|
null,
|
|
12836215,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create integers from date columns", async () => {
|
|
const table = await perspective.table({ x: "date" });
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: `integer("x")` },
|
|
});
|
|
|
|
const value = new Date(2020, 5, 30);
|
|
|
|
await table.update({
|
|
x: [value],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
|
|
// The custom format that Perspective uses to store dates - we
|
|
// can validate correctness by applying bitmask/shifting.
|
|
const expected = 132384030;
|
|
|
|
expect(result["computed"]).toEqual([expected]);
|
|
|
|
const year = (expected & 0xffff0000) >> 16;
|
|
const month = (expected & 0x0000ff00) >> 8;
|
|
const day = (expected & 0x000000ff) >> 0;
|
|
|
|
expect(year).toEqual(2020);
|
|
expect(month).toEqual(5);
|
|
expect(day).toEqual(30);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create integers from datetime columns", async () => {
|
|
const table = await perspective.table({ x: "datetime" });
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: `integer("x")` },
|
|
});
|
|
|
|
// first will not overflow, second will
|
|
await table.update({
|
|
x: [new Date(1970, 0, 1, 1), new Date(2020, 0, 1, 1)],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
new Date(1970, 0, 1, 1).getTime(),
|
|
null,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create integers from string columns", async () => {
|
|
const table = await perspective.table({
|
|
x: [
|
|
"1",
|
|
"2",
|
|
"3",
|
|
"abc",
|
|
"4.5",
|
|
"0.101928317581729083",
|
|
"-123456",
|
|
],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: `integer("x")` },
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([1, 2, 3, null, 4, 0, -123456]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("float()", () => {
|
|
test("Should create float from scalars", async () => {
|
|
const table = await perspective.table({ x: [1] });
|
|
const view = await table.view({
|
|
expressions: {
|
|
[`computed`]: `float(999999.999)`,
|
|
[`computed2`]: `float(2147483648)`,
|
|
[`computed3`]: `float(-2147483649)`,
|
|
[`computed4`]: `float(123456789)`,
|
|
[`computed5`]: `float(0.00001)`,
|
|
[`computed6`]: `float(1.9999999992)`,
|
|
[`computed7`]: `float(2147483647.1234567)`,
|
|
[`computed8`]: `float(-2147483647)`,
|
|
[`computed9`]: `float('957187281.00000001')`,
|
|
[`computed10`]: `float('1928.2817')`,
|
|
[`computed11`]: `float('abcdefg')`,
|
|
[`computed12`]: `float('abcdefg1234.123125')`,
|
|
[`computed13`]: `float('2147483648.1234566')`,
|
|
[`computed14`]: `float('-2147483649')`,
|
|
[`computed15`]: `float('inf')`,
|
|
[`computed16`]: `float('-inf')`,
|
|
[`computed17`]: `float(inf)`,
|
|
[`computed18`]: `float(-inf)`,
|
|
},
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
computed: "float",
|
|
computed2: "float",
|
|
computed3: "float",
|
|
computed4: "float",
|
|
computed5: "float",
|
|
computed6: "float",
|
|
computed7: "float",
|
|
computed8: "float",
|
|
computed9: "float",
|
|
computed10: "float",
|
|
computed11: "float",
|
|
computed12: "float",
|
|
computed13: "float",
|
|
computed14: "float",
|
|
computed15: "float",
|
|
computed16: "float",
|
|
computed17: "float",
|
|
computed18: "float",
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([999999.999]);
|
|
expect(result["computed2"]).toEqual([2147483648]);
|
|
expect(result["computed3"]).toEqual([-2147483649]);
|
|
expect(result["computed4"]).toEqual([123456789]);
|
|
expect(result["computed5"]).toEqual([0.00001]);
|
|
expect(result["computed6"]).toEqual([1.9999999992]);
|
|
expect(result["computed7"]).toEqual([2147483647.1234567]);
|
|
expect(result["computed8"]).toEqual([-2147483647]);
|
|
expect(result["computed9"]).toEqual([957187281.00000001]);
|
|
expect(result["computed10"]).toEqual([1928.2817]);
|
|
expect(result["computed11"]).toEqual([null]);
|
|
expect(result["computed12"]).toEqual([null]);
|
|
expect(result["computed13"]).toEqual([2147483648.1234566]);
|
|
expect(result["computed14"]).toEqual([-2147483649]);
|
|
expect(result["computed15"]).toEqual([null]);
|
|
expect(result["computed16"]).toEqual([null]);
|
|
expect(result["computed17"]).toEqual([null]);
|
|
expect(result["computed18"]).toEqual([null]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
// TODO same deal as above - not sure this behavior is correct.
|
|
// `await table.update()` should be invalid here.
|
|
test.skip("Should create float from integer columns", async () => {
|
|
const table = await perspective.table({ x: "integer" });
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: `float("x")` },
|
|
});
|
|
|
|
await table.update({
|
|
x: [100, -17238.8123, 0.890798, -1.1295, null, 12836215.128937],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
100,
|
|
-17238,
|
|
0,
|
|
-1,
|
|
null,
|
|
12836215,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create float from float columns", async () => {
|
|
const table = await perspective.table({ x: "float" });
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: ` float("x")` },
|
|
});
|
|
|
|
await table.update({
|
|
x: [
|
|
100.9999999,
|
|
-17238.8123,
|
|
0.890798,
|
|
-1.1295,
|
|
null,
|
|
12836215.128937,
|
|
],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
100.9999999,
|
|
-17238.8123,
|
|
0.890798,
|
|
-1.1295,
|
|
null,
|
|
12836215.128937,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create float from date columns", async () => {
|
|
const table = await perspective.table({ x: "date" });
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: `float("x")` },
|
|
});
|
|
|
|
const value = new Date(2020, 5, 30);
|
|
|
|
await table.update({
|
|
x: [value],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
|
|
// The custom format that Perspective uses to store dates - we
|
|
// can validate correctness by applying bitmask/shifting.
|
|
const expected = 132384030;
|
|
|
|
expect(result["computed"]).toEqual([expected]);
|
|
|
|
const year = (expected & 0xffff0000) >> 16;
|
|
const month = (expected & 0x0000ff00) >> 8;
|
|
const day = (expected & 0x000000ff) >> 0;
|
|
|
|
expect(year).toEqual(2020);
|
|
expect(month + 1).toEqual(6);
|
|
expect(day).toEqual(30);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create float from datetime columns", async () => {
|
|
const table = await perspective.table({ x: "datetime" });
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: `float("x")` },
|
|
});
|
|
|
|
await table.update({
|
|
x: [new Date(2020, 0, 1, 1), new Date(2020, 0, 1, 1, 50)],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
new Date(2020, 0, 1, 1).getTime(),
|
|
new Date(2020, 0, 1, 1, 50).getTime(),
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
// TODO This behavior is questionable at best, and given Perspective's
|
|
// more advanced expression aupport, its probably better to error
|
|
// here.
|
|
test.skip("Should create floats from string columns", async () => {
|
|
const table = await perspective.table({
|
|
x: [
|
|
"1.1238757869112321",
|
|
"2.0000001",
|
|
"abcdefg1234.12878591",
|
|
"12354.1827389abc",
|
|
"4.555555555",
|
|
"0.101928317581729083",
|
|
"-123456.21831729054781",
|
|
],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: ` float("x")` },
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
1.1238757869112321,
|
|
2.0000001,
|
|
null,
|
|
null,
|
|
4.555555555,
|
|
0.101928317581729083,
|
|
-123456.21831729054781,
|
|
]);
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("boolean()", () => {
|
|
test("Should create a boolean value", async () => {
|
|
const table = await perspective.table({
|
|
x: [1, 2, null, 4],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { 'boolean("x")': 'boolean("x")' },
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
'boolean("x")': "boolean",
|
|
});
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
x: [1, 2, null, 4],
|
|
'boolean("x")': [true, true, false, true],
|
|
});
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Empty strings and nulls", async () => {
|
|
const table = await perspective.table({
|
|
x: ["abcd", "", null, "abc"],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { 'boolean("x")': 'boolean("x")' },
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
'boolean("x")': "boolean",
|
|
});
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
x: ["abcd", "", null, "abc"],
|
|
'boolean("x")': [true, true, false, true],
|
|
});
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should respond to updates", async () => {
|
|
const table = await perspective.table(
|
|
{
|
|
idx: [1, 2, 3, 4],
|
|
x: [1, 2, null, 4],
|
|
},
|
|
{ index: "idx" },
|
|
);
|
|
|
|
const view = await table.view({
|
|
expressions: { 'boolean("x")': 'boolean("x")' },
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
'boolean("x")': "boolean",
|
|
});
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
idx: [1, 2, 3, 4],
|
|
x: [1, 2, null, 4],
|
|
'boolean("x")': [true, true, false, true],
|
|
});
|
|
|
|
await table.update({
|
|
idx: [3, 2],
|
|
x: [100, null],
|
|
});
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
idx: [1, 2, 3, 4],
|
|
x: [1, null, 100, 4],
|
|
'boolean("x")': [true, false, true, true],
|
|
});
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should respond to removes", async () => {
|
|
const table = await perspective.table(
|
|
{
|
|
idx: [1, 2, 3, 4],
|
|
x: [1, 2, null, 4],
|
|
},
|
|
{ index: "idx" },
|
|
);
|
|
|
|
const view = await table.view({
|
|
expressions: { 'boolean("x")': 'boolean("x")' },
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
'boolean("x")': "boolean",
|
|
});
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
idx: [1, 2, 3, 4],
|
|
x: [1, 2, null, 4],
|
|
'boolean("x")': [true, true, false, true],
|
|
});
|
|
|
|
await table.remove([1, 4]);
|
|
|
|
expect(await view.to_columns()).toEqual({
|
|
idx: [2, 3],
|
|
x: [2, null],
|
|
'boolean("x")': [true, false],
|
|
});
|
|
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("date()", () => {
|
|
test("Should create a date from scalars", async () => {
|
|
const table = await perspective.table({
|
|
x: [1, 2, 3, 4],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
[`computed`]: `date(2020, 7, 15)`,
|
|
[`computed2`]: `date(1970, 10, 29)`,
|
|
[`computed3`]: `date(2020, 1, "x")`,
|
|
},
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual(
|
|
Array(4).fill(new Date(2020, 6, 15).getTime()),
|
|
);
|
|
expect(result["computed2"]).toEqual(
|
|
Array(4).fill(new Date(1970, 9, 29).getTime()),
|
|
);
|
|
expect(result["computed3"]).toEqual(
|
|
Array(4)
|
|
.fill(true)
|
|
.map((_, idx) => new Date(2020, 0, idx + 1).getTime()),
|
|
);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create a date from int columns", async () => {
|
|
// NOTE: This test originally used dates that would cause an underflow issue
|
|
// in the epoch time conversion that occurs within the c++ engine.
|
|
// Revert these changes once the c++ engine is updated to use std::chrono.
|
|
const table = await perspective.table({
|
|
y: "integer",
|
|
m: "integer",
|
|
d: "integer",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: ` date("y", "m", "d")` },
|
|
});
|
|
|
|
await table.update({
|
|
// y: [0, 2020, 1776, 2018, 2020, 2020], // old values, see note above.
|
|
y: [1970, 2020, 2000, 2018, 2020, 2020],
|
|
m: [1, 2, 5, 2, 12, null],
|
|
d: [1, 29, 31, 29, 31, 1],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
const expected = [
|
|
// new Date(1900, 0, 1), // old values, see note above.
|
|
new Date(1970, 0, 1),
|
|
new Date(2020, 1, 29),
|
|
// new Date(1776, 4, 31), // old values, see note above.
|
|
new Date(2000, 4, 31),
|
|
new Date(2018, 1, 29),
|
|
new Date(2020, 11, 31),
|
|
].map((x) => x.getTime());
|
|
expected.push(null);
|
|
expect(result["computed"]).toEqual(expected);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create a date from float columns", async () => {
|
|
// NOTE: This test originally used dates that would cause an underflow issue
|
|
// in the epoch time conversion that occurs within the c++ engine.
|
|
// Revert these changes once the c++ engine is updated to use std::chrono.
|
|
const table = await perspective.table({
|
|
y: "float",
|
|
m: "float",
|
|
d: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: ` date("y", "m", "d")` },
|
|
});
|
|
|
|
await table.update({
|
|
// y: [0, 2020, 1776, 2018, 2020, 2020], // old values, see note above.
|
|
y: [1970, 2020, 2000, 2018, 2020, 2020],
|
|
m: [1, 2, 5, 2, 12, null],
|
|
d: [1, 29, 31, 29, 31, 1],
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
const expected = [
|
|
// new Date(1900, 0, 1), // old values, see note above.
|
|
new Date(1970, 0, 1),
|
|
new Date(2020, 1, 29),
|
|
// new Date(1776, 4, 31), // old values, see note above.
|
|
new Date(2000, 4, 31),
|
|
new Date(2018, 1, 29),
|
|
new Date(2020, 11, 31),
|
|
].map((x) => x.getTime());
|
|
expected.push(null);
|
|
expect(result["computed"]).toEqual(expected);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create a date from numeric columns and skip invalid values", async () => {
|
|
// NOTE: The original test used `y: [-100, 0, 2000, 3000]`, but the `3000` value
|
|
// has been replaced with `2030` due to an underflow issue with the computed
|
|
// epoch time. This test should be returned to its original state once
|
|
// the c++ engine is updated to use std::chrono.
|
|
const table = await perspective.table({
|
|
y: [-100, 0, 2000, 2030],
|
|
m: [12, 0, 12, 11],
|
|
d: [1, 10, 32, 10],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: ` date("y", "m", "d")` },
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
null,
|
|
null,
|
|
null,
|
|
new Date(2030, 10, 10).getTime(),
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create a date from variables inside expression", async () => {
|
|
const table = await perspective.table({
|
|
x: [20200101, 20090531, 19801220, 20200229],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
[`computed`]: `var year := floor("x" / 10000); var month := floor("x" % 10000 / 100); var day := floor("x" % 100); date(year, month, day)`,
|
|
},
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual(
|
|
[
|
|
new Date(2020, 0, 1),
|
|
new Date(2009, 4, 31),
|
|
new Date(1980, 11, 20),
|
|
new Date(2020, 1, 29),
|
|
].map((x) => x.getTime()),
|
|
);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should validate inputs", async () => {
|
|
const table = await perspective.table({
|
|
y: [-100, 0, 2000, 3000],
|
|
m: [12, 0, 12, 11],
|
|
d: [1, 10, 32, 10],
|
|
});
|
|
|
|
const validated = await table.validate_expressions({
|
|
[`computed`]: `date()`,
|
|
[`computed2`]: `date('abc', 'def', '123')`,
|
|
[`computed3`]: `date("y", "m", "d")`,
|
|
});
|
|
|
|
expect(validated.expression_schema).toEqual({
|
|
computed3: "date",
|
|
});
|
|
|
|
expect(validated.errors).toEqual({
|
|
computed: {
|
|
column: 6,
|
|
error_message:
|
|
"Zero parameter call to generic function: date not allowed",
|
|
line: 0,
|
|
},
|
|
computed2: {
|
|
column: 0,
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
line: 0,
|
|
},
|
|
});
|
|
|
|
await table.delete();
|
|
});
|
|
});
|
|
|
|
test.describe("datetime()", () => {
|
|
test("Should create a datetime from scalars", async () => {
|
|
const table = await perspective.table({
|
|
x: [1, 2, 3, 4],
|
|
});
|
|
|
|
const a = new Date(2005, 6, 31, 11, 59, 32).getTime();
|
|
const b = new Date(2005, 6, 31, 11, 59, 32).getTime();
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
[`computed`]: `datetime(${a})`,
|
|
[`computed2`]: `datetime(${b})`,
|
|
},
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
computed: "datetime",
|
|
computed2: "datetime",
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual(Array(4).fill(a));
|
|
expect(result["computed2"]).toEqual(Array(4).fill(b));
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create a datetime from a float()", async () => {
|
|
const table = await perspective.table({
|
|
x: [new Date(2005, 6, 31, 11, 59, 32)],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: ` datetime(float("x"))` },
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
computed: "datetime",
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual([
|
|
new Date(2005, 6, 31, 11, 59, 32).getTime(),
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
// TODO Previous behavior was to fill this column with null - new
|
|
// behavior is to error, which IMO is more correct.
|
|
test.skip("Should not create a datetime from int columns as int32 is too small", async () => {
|
|
const table = await perspective.table({
|
|
x: "integer",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: ` datetime("x")` },
|
|
});
|
|
|
|
const data = [
|
|
new Date(2020, 1, 29, 5, 1, 2),
|
|
new Date(1776, 4, 31, 13, 23, 18),
|
|
new Date(2018, 1, 29, 19, 39, 43),
|
|
new Date(2020, 11, 31, 23, 59, 59),
|
|
].map((x) => x.getTime());
|
|
data.push(null);
|
|
|
|
await table.update({
|
|
x: data,
|
|
});
|
|
|
|
let result = await view.to_columns();
|
|
|
|
expect(result["computed"]).toEqual(Array(5).fill(null));
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create a datetime from float columns", async () => {
|
|
// NOTE: This test originally used dates that would cause an underflow issue
|
|
// in the epoch time conversion that occurs within the c++ engine.
|
|
// Revert these changes once the c++ engine is updated to use std::chrono.
|
|
const table = await perspective.table({
|
|
x: "float",
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: { computed: ` datetime("x")` },
|
|
});
|
|
|
|
const data = [
|
|
new Date(2020, 1, 29, 5, 1, 2),
|
|
new Date(2000, 4, 31, 13, 23, 18),
|
|
// new Date(1776, 4, 31, 13, 23, 18), // old values, see note above.
|
|
new Date(2018, 1, 29, 19, 39, 43),
|
|
new Date(2020, 11, 31, 23, 59, 59),
|
|
].map((x) => x.getTime());
|
|
data.push(null);
|
|
|
|
await table.update({
|
|
x: data,
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
expect(result["computed"]).toEqual(data);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should create a datetime from numeric scalars < 0", async () => {
|
|
const table = await perspective.table({
|
|
x: [1],
|
|
});
|
|
|
|
const view = await table.view({
|
|
expressions: {
|
|
[`computed1`]: `datetime(-1)`,
|
|
[`computed2`]: `datetime(0)`,
|
|
computed3: `datetime(${new Date(
|
|
2002,
|
|
11,
|
|
12,
|
|
13,
|
|
14,
|
|
15,
|
|
).getTime()})`,
|
|
},
|
|
});
|
|
|
|
expect(await view.expression_schema()).toEqual({
|
|
computed1: "datetime",
|
|
computed2: "datetime",
|
|
computed3: "datetime",
|
|
});
|
|
|
|
const result = await view.to_columns();
|
|
|
|
expect(result["computed1"]).toEqual([-1]);
|
|
expect(result["computed2"]).toEqual([0]);
|
|
expect(result["computed3"]).toEqual([
|
|
new Date(2002, 11, 12, 13, 14, 15).getTime(),
|
|
]);
|
|
await view.delete();
|
|
await table.delete();
|
|
});
|
|
|
|
test("Should validate inputs", async () => {
|
|
const table = await perspective.table({
|
|
x: [1],
|
|
});
|
|
|
|
const validated = await table.validate_expressions({
|
|
[`computed1`]: `datetime()`,
|
|
[`computed2`]: `datetime('abcd')`,
|
|
[`computed3`]: `datetime(today())`,
|
|
[`computed4`]: `datetime(now())`,
|
|
[`computed5`]: `datetime(123456, 7)`,
|
|
});
|
|
|
|
expect(validated.expression_schema).toEqual({});
|
|
|
|
expect(validated.errors).toEqual({
|
|
computed1: {
|
|
column: 10,
|
|
error_message:
|
|
"Zero parameter call to generic function: datetime not allowed",
|
|
line: 0,
|
|
},
|
|
computed2: {
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
column: 0,
|
|
line: 0,
|
|
},
|
|
computed3: {
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
column: 0,
|
|
line: 0,
|
|
},
|
|
computed4: {
|
|
error_message:
|
|
"Type Error - inputs do not resolve to a valid expression.",
|
|
column: 0,
|
|
line: 0,
|
|
},
|
|
computed5: {
|
|
error_message:
|
|
"Failed parameter type check for function 'datetime', Expected 'T' call set: 'TT'",
|
|
column: 19,
|
|
line: 0,
|
|
},
|
|
});
|
|
|
|
await table.delete();
|
|
});
|
|
});
|
|
})(perspective);
|