Files
2026-07-13 12:25:07 +08:00

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);