@kensio/yulin 1.21.29 → 1.21.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/service/athena/engine/sim-athena-array-shims.js +1 -1
  2. package/dist/service/athena/engine/sim-athena-binary-text.d.ts +2 -2
  3. package/dist/service/athena/engine/sim-athena-binary-text.js +2 -2
  4. package/dist/service/athena/engine/sim-athena-cast-rewrite.d.ts +30 -0
  5. package/dist/service/athena/engine/sim-athena-cast-rewrite.js +107 -0
  6. package/dist/service/athena/engine/sim-athena-cast-shims.d.ts +12 -0
  7. package/dist/service/athena/engine/sim-athena-cast-shims.js +29 -0
  8. package/dist/service/athena/engine/sim-athena-cast-text.d.ts +13 -0
  9. package/dist/service/athena/engine/sim-athena-cast-text.js +66 -0
  10. package/dist/service/athena/engine/sim-athena-comparison-shims.js +1 -2
  11. package/dist/service/athena/engine/sim-athena-date-arithmetic-shims.d.ts +4 -5
  12. package/dist/service/athena/engine/sim-athena-date-arithmetic-shims.js +6 -14
  13. package/dist/service/athena/engine/sim-athena-date-units.d.ts +1 -1
  14. package/dist/service/athena/engine/sim-athena-date-units.js +1 -1
  15. package/dist/service/athena/engine/sim-athena-engine-answer.d.ts +10 -5
  16. package/dist/service/athena/engine/sim-athena-engine-answer.js +4 -4
  17. package/dist/service/athena/engine/sim-athena-engine-run.d.ts +4 -0
  18. package/dist/service/athena/engine/sim-athena-engine-run.js +10 -2
  19. package/dist/service/athena/engine/sim-athena-regexp-shims.js +2 -2
  20. package/dist/service/athena/engine/sim-athena-shim-refusal.d.ts +17 -0
  21. package/dist/service/athena/engine/sim-athena-shim-refusal.js +22 -0
  22. package/dist/service/athena/engine/sim-athena-shim-registry.d.ts +5 -1
  23. package/dist/service/athena/engine/sim-athena-shim-registry.js +14 -2
  24. package/dist/service/athena/engine/sim-athena-shims.js +2 -0
  25. package/dist/service/athena/engine/sim-athena-sql-translation.d.ts +1 -23
  26. package/dist/service/athena/engine/sim-athena-sql-translation.js +8 -23
  27. package/dist/service/athena/engine/sim-athena-translated-sql.d.ts +29 -0
  28. package/dist/service/athena/engine/sim-athena-translated-sql.js +12 -0
  29. package/dist/service/athena/engine/sim-athena-turn-down.d.ts +7 -0
  30. package/dist/service/athena/engine/sim-athena-turn-down.js +11 -0
  31. package/dist/service/athena/result/sim-athena-query-answer.d.ts +2 -1
  32. package/dist/service/athena/result/sim-athena-query-answer.js +2 -1
  33. package/dist/service/athena/sim-athena-snapshot-table.fixture.d.ts +10 -0
  34. package/dist/service/athena/sim-athena-snapshot-table.fixture.js +39 -0
  35. package/docs/services/athena/README.md +29 -8
  36. package/package.json +1 -1
@@ -57,7 +57,7 @@ function joined(array, delimiter, absent) {
57
57
  * One element written out.
58
58
  *
59
59
  * Trino refuses to join an array whose elements are not text, and raising here
60
- * turns the query down to its declared result instead.
60
+ * fails the query the same way.
61
61
  */
62
62
  function scalarText(value) {
63
63
  if (typeof value === "string") {
@@ -3,8 +3,8 @@ import type { SQLOutputValue } from "node:sqlite";
3
3
  * One encoded string read back to bytes, or null where it is null.
4
4
  *
5
5
  * Trino fails the query over text the encoding cannot carry, and `Buffer` reads
6
- * as much of it as it can and drops the rest. Raising here turns the query down
7
- * instead, which is nearer to Trino's answer than bytes nobody wrote.
6
+ * as much of it as it can and drops the rest. Raising here fails the query the
7
+ * way Trino does.
8
8
  */
9
9
  export declare function simAthenaDecodedText(text: string | undefined, encoding: "base64" | "hex"): Uint8Array | null;
10
10
  /**
@@ -20,8 +20,8 @@ const encodedReplacement = Buffer.from([0xef, 0xbf, 0xbd]);
20
20
  * One encoded string read back to bytes, or null where it is null.
21
21
  *
22
22
  * Trino fails the query over text the encoding cannot carry, and `Buffer` reads
23
- * as much of it as it can and drops the rest. Raising here turns the query down
24
- * instead, which is nearer to Trino's answer than bytes nobody wrote.
23
+ * as much of it as it can and drops the rest. Raising here fails the query the
24
+ * way Trino does.
25
25
  */
26
26
  export function simAthenaDecodedText(text, encoding) {
27
27
  if (text === undefined) {
@@ -0,0 +1,30 @@
1
+ import type { SimAthenaCatalogTable } from "../table/sim-athena-catalog-table.js";
2
+ /**
3
+ * The shims a cast to `DATE` and to `TIMESTAMP` are handed to.
4
+ *
5
+ * The names have the simulator's prefix so that neither can collide with a
6
+ * Trino function a statement calls itself.
7
+ */
8
+ export declare const simAthenaDateCast = "sim_athena_cast_date";
9
+ export declare const simAthenaTimestampCast = "sim_athena_cast_timestamp";
10
+ /** The second argument a shim is handed where the value it casts is text. */
11
+ export declare const simAthenaCastSource = "varchar";
12
+ /**
13
+ * Hand every cast to `DATE` or `TIMESTAMP` in the tree to the engine's shims.
14
+ *
15
+ * SQLite gives a column declared `DATE` or `TIMESTAMP` numeric affinity, so a
16
+ * plain `CAST('2026-08-15' AS DATE)` keeps the leading number and answers
17
+ * `2026`. Every date function downstream of that cast then reads a year where
18
+ * it expected a date.
19
+ *
20
+ * Trino's one-argument `date(x)` is the cast to `DATE` under another name,
21
+ * and it goes to the same shim. SQLite's own `date()` answers null over text
22
+ * that is no date, where Trino fails the cast.
23
+ *
24
+ * Trino reads text more strictly than a value already typed as a timestamp,
25
+ * and the shim is told which it has. A string literal and a column the catalog
26
+ * declares as text are text. Anything else is taken to be a date or a
27
+ * timestamp already, since the engine cannot tell the type a function answers
28
+ * with.
29
+ */
30
+ export declare function simAthenaRewriteCasts(ast: unknown, tables: readonly SimAthenaCatalogTable[]): void;
@@ -0,0 +1,107 @@
1
+ import { asAstNode, isColumnRef, replaceNode, simAthenaAstNodes, simAthenaCalledName, } from "./sim-athena-ast-nodes.js";
2
+ /**
3
+ * The shims a cast to `DATE` and to `TIMESTAMP` are handed to.
4
+ *
5
+ * The names have the simulator's prefix so that neither can collide with a
6
+ * Trino function a statement calls itself.
7
+ */
8
+ export const simAthenaDateCast = "sim_athena_cast_date";
9
+ export const simAthenaTimestampCast = "sim_athena_cast_timestamp";
10
+ /** The second argument a shim is handed where the value it casts is text. */
11
+ export const simAthenaCastSource = "varchar";
12
+ /** The shim each cast target goes to. */
13
+ const castShims = new Map([
14
+ ["DATE", simAthenaDateCast],
15
+ ["TIMESTAMP", simAthenaTimestampCast],
16
+ ]);
17
+ /** The Glue types a column of text is declared with. */
18
+ const textTypes = /^(?:string|varchar|char)\b/iu;
19
+ /**
20
+ * Hand every cast to `DATE` or `TIMESTAMP` in the tree to the engine's shims.
21
+ *
22
+ * SQLite gives a column declared `DATE` or `TIMESTAMP` numeric affinity, so a
23
+ * plain `CAST('2026-08-15' AS DATE)` keeps the leading number and answers
24
+ * `2026`. Every date function downstream of that cast then reads a year where
25
+ * it expected a date.
26
+ *
27
+ * Trino's one-argument `date(x)` is the cast to `DATE` under another name,
28
+ * and it goes to the same shim. SQLite's own `date()` answers null over text
29
+ * that is no date, where Trino fails the cast.
30
+ *
31
+ * Trino reads text more strictly than a value already typed as a timestamp,
32
+ * and the shim is told which it has. A string literal and a column the catalog
33
+ * declares as text are text. Anything else is taken to be a date or a
34
+ * timestamp already, since the engine cannot tell the type a function answers
35
+ * with.
36
+ */
37
+ export function simAthenaRewriteCasts(ast, tables) {
38
+ const textColumns = textColumnsOf(tables);
39
+ for (const node of simAthenaAstNodes(ast)) {
40
+ const cast = castOf(node);
41
+ if (cast !== undefined) {
42
+ replaceNode(node, {
43
+ type: "function",
44
+ name: { name: [{ type: "default", value: cast.shim }] },
45
+ args: {
46
+ type: "expr_list",
47
+ value: [cast.value, sourceOf(cast.value, textColumns)],
48
+ },
49
+ over: null,
50
+ });
51
+ }
52
+ }
53
+ }
54
+ function castOf(node) {
55
+ if (node["type"] === "cast") {
56
+ const shim = castShims.get(targetOf(node["target"]));
57
+ return shim === undefined ? undefined : { shim, value: node["expr"] };
58
+ }
59
+ const values = asAstNode(node["args"])?.["value"];
60
+ if (node["type"] !== "function" ||
61
+ simAthenaCalledName(node)?.toLowerCase() !== "date" ||
62
+ !Array.isArray(values) ||
63
+ values.length !== 1) {
64
+ return undefined;
65
+ }
66
+ return { shim: simAthenaDateCast, value: values[0] };
67
+ }
68
+ /** The one type a cast names, upper cased, or nothing for anything else. */
69
+ function targetOf(target) {
70
+ const only = Array.isArray(target) && target.length === 1 ? target[0] : null;
71
+ const dataType = asAstNode(only)?.["dataType"];
72
+ return typeof dataType === "string" ? dataType.toUpperCase() : "";
73
+ }
74
+ /** The second argument that tells the shim whether its value is text. */
75
+ function sourceOf(value, textColumns) {
76
+ const node = asAstNode(value);
77
+ const isText = node?.["type"] === "single_quote_string" ||
78
+ (node !== undefined &&
79
+ isColumnRef(node) &&
80
+ textColumns.has(node.column.toLowerCase()));
81
+ return {
82
+ type: "single_quote_string",
83
+ value: isText ? simAthenaCastSource : "",
84
+ };
85
+ }
86
+ /**
87
+ * Every column name the tables declare as text and as nothing else.
88
+ *
89
+ * A name two tables declare with different types is left out, since the
90
+ * statement could mean either and only text is held to the stricter read.
91
+ */
92
+ function textColumnsOf(tables) {
93
+ const text = new Set();
94
+ const other = new Set();
95
+ for (const table of tables) {
96
+ for (const column of [...table.columns, ...table.partitionKeys]) {
97
+ const name = column.Name.toLowerCase();
98
+ if (textTypes.test(column.Type ?? "")) {
99
+ text.add(name);
100
+ }
101
+ else {
102
+ other.add(name);
103
+ }
104
+ }
105
+ }
106
+ return text.difference(other);
107
+ }
@@ -0,0 +1,12 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ /**
3
+ * Trino's casts to `DATE` and `TIMESTAMP`, which SQLite cannot do itself.
4
+ *
5
+ * SQLite gives both types numeric affinity, so its own cast of
6
+ * `'2026-08-15'` answers `2026`. The statement's casts are rewritten onto
7
+ * these shims before it runs, and the rewrite says whether the value is text.
8
+ *
9
+ * A value the cast cannot read fails the query with Trino's own message, the
10
+ * way Athena fails it.
11
+ */
12
+ export declare function simAthenaInstallCastShims(database: DatabaseSync): void;
@@ -0,0 +1,29 @@
1
+ import { SimAthenaSetUpError } from "../error/sim-athena.error.js";
2
+ import { simAthenaCastSource, simAthenaDateCast, simAthenaTimestampCast, } from "./sim-athena-cast-rewrite.js";
3
+ import { simAthenaCastInstant, simAthenaCastRendered, } from "./sim-athena-cast-text.js";
4
+ import { shimText, simAthenaScalarShim } from "./sim-athena-shim-registry.js";
5
+ /**
6
+ * Trino's casts to `DATE` and `TIMESTAMP`, which SQLite cannot do itself.
7
+ *
8
+ * SQLite gives both types numeric affinity, so its own cast of
9
+ * `'2026-08-15'` answers `2026`. The statement's casts are rewritten onto
10
+ * these shims before it runs, and the rewrite says whether the value is text.
11
+ *
12
+ * A value the cast cannot read fails the query with Trino's own message, the
13
+ * way Athena fails it.
14
+ */
15
+ export function simAthenaInstallCastShims(database) {
16
+ simAthenaScalarShim(database, simAthenaDateCast, (value, source) => cast(value, source, "date"), "CAST(... AS DATE)");
17
+ simAthenaScalarShim(database, simAthenaTimestampCast, (value, source) => cast(value, source, "timestamp"), "CAST(... AS TIMESTAMP)");
18
+ }
19
+ function cast(value, source, type) {
20
+ const text = shimText(value);
21
+ if (text === undefined) {
22
+ return null;
23
+ }
24
+ const instant = simAthenaCastInstant(text, type, source === simAthenaCastSource);
25
+ if (instant === undefined) {
26
+ throw new SimAthenaSetUpError(`Value cannot be cast to ${type}: ${text}`);
27
+ }
28
+ return simAthenaCastRendered(instant, type);
29
+ }
@@ -0,0 +1,13 @@
1
+ /** The two types a cast in the engine reads a value as. */
2
+ export type SimAthenaCastType = "date" | "timestamp";
3
+ /**
4
+ * The instant one value names as the cast reads it, or nothing where the cast
5
+ * fails.
6
+ *
7
+ * Text is held to the formats Trino's cast from `varchar` takes. Any other
8
+ * source is a value some column or function already typed as a date or a
9
+ * timestamp, written as the ISO-8601 text the engine holds one as.
10
+ */
11
+ export declare function simAthenaCastInstant(text: string, type: SimAthenaCastType, fromText: boolean): number | undefined;
12
+ /** One instant rendered as Athena renders a value of the type. */
13
+ export declare function simAthenaCastRendered(instant: number, type: SimAthenaCastType): string;
@@ -0,0 +1,66 @@
1
+ // Both patterns below are anchored at each end with every quantifier bounded,
2
+ // so there is nothing in either to backtrack over.
3
+ // oxlint-disable security/detect-unsafe-regex
4
+ import { simAthenaInstant } from "./sim-athena-timestamp-text.js";
5
+ /**
6
+ * What Trino's cast from `varchar` to `DATE` reads, once the text is trimmed.
7
+ *
8
+ * A time of day is refused. `CAST('2026-08-15 10:00:00' AS DATE)` fails on
9
+ * Athena, and only a timestamp value loses its time to a cast.
10
+ */
11
+ const varcharDate = /^(\d{4})-(\d{1,2})-(\d{1,2})$/u;
12
+ /**
13
+ * What Trino's cast from `varchar` to `TIMESTAMP` reads.
14
+ *
15
+ * The time follows a space. A `T` between the date and the time fails the
16
+ * cast on Athena, and so does a zone, which belongs to `TIMESTAMP WITH TIME
17
+ * ZONE`.
18
+ */
19
+ const varcharTimestamp = /^(\d{4})-(\d{1,2})-(\d{1,2})(?: (\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d{1,12}))?)?)?$/u;
20
+ /**
21
+ * The instant one value names as the cast reads it, or nothing where the cast
22
+ * fails.
23
+ *
24
+ * Text is held to the formats Trino's cast from `varchar` takes. Any other
25
+ * source is a value some column or function already typed as a date or a
26
+ * timestamp, written as the ISO-8601 text the engine holds one as.
27
+ */
28
+ export function simAthenaCastInstant(text, type, fromText) {
29
+ if (!fromText) {
30
+ return simAthenaInstant(text);
31
+ }
32
+ const fields = type === "date"
33
+ ? varcharDate.exec(text.trim())
34
+ : varcharTimestamp.exec(text);
35
+ return fields === null ? undefined : instantOf(fields);
36
+ }
37
+ /** One instant rendered as Athena renders a value of the type. */
38
+ export function simAthenaCastRendered(instant, type) {
39
+ const iso = new Date(instant).toISOString();
40
+ return type === "date"
41
+ ? iso.slice(0, 10)
42
+ : iso.slice(0, 23).replace("T", " ");
43
+ }
44
+ /**
45
+ * The instant a matched value names, where every field is in range.
46
+ *
47
+ * `Date.UTC` rolls the 30th of February over into March, and Trino refuses
48
+ * it. Each field is checked against its calendar before anything is built.
49
+ * A fraction rounds to the millisecond, which is the precision Athena gives a
50
+ * `TIMESTAMP` with none written.
51
+ */
52
+ function instantOf(fields) {
53
+ const [year, month, day, hour, minute, second] = Array.from({ length: 6 }, (_, index) => Number(fields[index + 1] ?? 0));
54
+ const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
55
+ if (month < 1 ||
56
+ month > 12 ||
57
+ day < 1 ||
58
+ day > lastDay ||
59
+ hour > 23 ||
60
+ minute > 59 ||
61
+ second > 59) {
62
+ return undefined;
63
+ }
64
+ const milliseconds = Math.round(Number(`0.${fields[7] ?? "0"}`) * 1000);
65
+ return Date.UTC(year, month - 1, day, hour, minute, second, milliseconds);
66
+ }
@@ -57,8 +57,7 @@ function isNumeric(value) {
57
57
  * One argument as the text it orders by.
58
58
  *
59
59
  * A `varbinary` orders by its bytes in Trino, and nothing written out here
60
- * orders the same way. A call over one raises and the query falls back to its
61
- * declared result.
60
+ * orders the same way. A call over one raises and the query fails.
62
61
  */
63
62
  function comparableText(value) {
64
63
  if (value instanceof Uint8Array) {
@@ -2,10 +2,9 @@ import type { DatabaseSync } from "node:sqlite";
2
2
  /**
3
3
  * Trino's date arithmetic, over ISO-8601 text.
4
4
  *
5
- * Each of these raises rather than guessing where it cannot answer faithfully.
6
- * A unit outside the ones Trino names and a value that is no ISO-8601 timestamp
7
- * both land there, and the engine turns the query down and lets the declared
8
- * result answer. A shim answering a null instead would be a wrong answer
9
- * wearing the shape of a right one.
5
+ * Each of these raises where it cannot answer faithfully. A unit outside the
6
+ * ones Trino names and a value that is no ISO-8601 timestamp both land there,
7
+ * and the engine fails the query with the reason. A shim answering a null
8
+ * would be a wrong answer wearing the shape of a right one.
10
9
  */
11
10
  export declare function simAthenaInstallDateArithmeticShims(database: DatabaseSync): void;
@@ -6,11 +6,10 @@ import { simAthenaInstant, simAthenaRenderLike, } from "./sim-athena-timestamp-t
6
6
  /**
7
7
  * Trino's date arithmetic, over ISO-8601 text.
8
8
  *
9
- * Each of these raises rather than guessing where it cannot answer faithfully.
10
- * A unit outside the ones Trino names and a value that is no ISO-8601 timestamp
11
- * both land there, and the engine turns the query down and lets the declared
12
- * result answer. A shim answering a null instead would be a wrong answer
13
- * wearing the shape of a right one.
9
+ * Each of these raises where it cannot answer faithfully. A unit outside the
10
+ * ones Trino names and a value that is no ISO-8601 timestamp both land there,
11
+ * and the engine fails the query with the reason. A shim answering a null
12
+ * would be a wrong answer wearing the shape of a right one.
14
13
  */
15
14
  export function simAthenaInstallDateArithmeticShims(database) {
16
15
  simAthenaScalarShim(database, "date_add", (unit, value, timestamp) => {
@@ -47,13 +46,7 @@ function unitOf(value) {
47
46
  ? unit
48
47
  : raise(`${unit} is no date unit the engine reads`);
49
48
  }
50
- /**
51
- * Turn the query down.
52
- *
53
- * A shim that cannot answer faithfully raises, and the engine catches it and
54
- * lets the declared result answer. Answering a null instead would be a wrong
55
- * answer wearing the shape of a right one.
56
- */
49
+ /** Fail the query, saying why the shim could not answer. */
57
50
  function raise(reason) {
58
51
  throw new SimAthenaSetUpError(reason);
59
52
  }
@@ -63,8 +56,7 @@ function raise(reason) {
63
56
  * Trino answers with a timestamp carrying the zone. There is nowhere to put one
64
57
  * here, so the answer is the wall clock alone, with any `Z` the value arrived
65
58
  * with taken off. A `Z` left on would say UTC over a time that is no longer in
66
- * it. A zone nobody has heard of raises out of `Intl`, and the query falls
67
- * back.
59
+ * it. A zone nobody has heard of raises out of `Intl`, and the query fails.
68
60
  */
69
61
  function inZone(at, zone) {
70
62
  if (at === undefined || zone === undefined) {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * A shim answering a unit it does not know would be answering wrongly, so the
5
5
  * ones here are the whole of what `date_add` and `date_diff` take. Anything
6
- * else raises, and the engine turns the query down.
6
+ * else raises, and the query fails.
7
7
  */
8
8
  export declare function isDateUnit(unit: string): boolean;
9
9
  /**
@@ -19,7 +19,7 @@ const calendarUnits = new Map([
19
19
  *
20
20
  * A shim answering a unit it does not know would be answering wrongly, so the
21
21
  * ones here are the whole of what `date_add` and `date_diff` take. Anything
22
- * else raises, and the engine turns the query down.
22
+ * else raises, and the query fails.
23
23
  */
24
24
  export function isDateUnit(unit) {
25
25
  return clockUnits.has(unit) || calendarUnits.has(unit);
@@ -10,7 +10,12 @@ export interface SimAthenaEngineAnswer {
10
10
  readonly result: SimAthenaResolvedResult | undefined;
11
11
  /** What the engine could not do, where it turned the query down. */
12
12
  readonly turnedDown: string | undefined;
13
- /** Why Athena refuses the statement, where the engine found it invalid. */
13
+ /**
14
+ * Why the query fails whether or not the engine is strict.
15
+ *
16
+ * That is a statement Athena refuses, or one the engine ran and could not
17
+ * evaluate.
18
+ */
14
19
  readonly rejected: string | undefined;
15
20
  }
16
21
  /** One answer the engine ran for real. */
@@ -18,11 +23,11 @@ export declare function simAthenaEngineAnswered(result: SimAthenaResolvedResult)
18
23
  /** One query the engine turned down, and why. */
19
24
  export declare function simAthenaEngineTurnedDown(turnedDown: string): SimAthenaEngineAnswer;
20
25
  /**
21
- * One statement Athena refuses, and why.
26
+ * One statement that fails whatever mode the engine is in, and why.
22
27
  *
23
- * The engine ran nothing. A query that reaches this fails whether or not the
24
- * engine is strict, since the alternative is a green test on SQL the service
25
- * rejects.
28
+ * Athena refusing the statement is one reason, and the alternative there is a
29
+ * green test on SQL the service rejects. A function shim refusing a value is
30
+ * the other, and the alternative there is a query losing every row in silence.
26
31
  */
27
32
  export declare function simAthenaEngineRejected(rejected: string): SimAthenaEngineAnswer;
28
33
  /** An engine nobody turned on, which has no opinion about the query. */
@@ -7,11 +7,11 @@ export function simAthenaEngineTurnedDown(turnedDown) {
7
7
  return { result: undefined, turnedDown, rejected: undefined };
8
8
  }
9
9
  /**
10
- * One statement Athena refuses, and why.
10
+ * One statement that fails whatever mode the engine is in, and why.
11
11
  *
12
- * The engine ran nothing. A query that reaches this fails whether or not the
13
- * engine is strict, since the alternative is a green test on SQL the service
14
- * rejects.
12
+ * Athena refusing the statement is one reason, and the alternative there is a
13
+ * green test on SQL the service rejects. A function shim refusing a value is
14
+ * the other, and the alternative there is a query losing every row in silence.
15
15
  */
16
16
  export function simAthenaEngineRejected(rejected) {
17
17
  return { result: undefined, turnedDown: undefined, rejected };
@@ -29,5 +29,9 @@ export interface SimAthenaEngineRun {
29
29
  *
30
30
  * Each of them carries a reason out, which a strict engine fails the query
31
31
  * with.
32
+ *
33
+ * A function shim refusing one of the statement's values is the exception. The
34
+ * engine ran the statement and could not evaluate it, and the query fails
35
+ * whether or not the engine is strict.
32
36
  */
33
37
  export declare function simAthenaEngineRun(run: SimAthenaEngineRun): Promise<SimAthenaEngineAnswer>;
@@ -1,6 +1,7 @@
1
- import { simAthenaEngineAnswered, simAthenaEngineTurnedDown, } from "./sim-athena-engine-answer.js";
2
- import { simAthenaRunFailure } from "./sim-athena-turn-down.js";
1
+ import { simAthenaEngineAnswered, simAthenaEngineRejected, simAthenaEngineTurnedDown, } from "./sim-athena-engine-answer.js";
2
+ import { simAthenaRunFailure, simAthenaUnevaluable, } from "./sim-athena-turn-down.js";
3
3
  import { simAthenaEngineResult } from "./sim-athena-engine-result.js";
4
+ import { SimAthenaShimRefusal } from "./sim-athena-shim-refusal.js";
4
5
  import { simAthenaSqliteDatabase } from "./sim-athena-sqlite-database.js";
5
6
  import { simAthenaLoadedTables } from "./sim-athena-loaded-tables.js";
6
7
  /**
@@ -15,6 +16,10 @@ import { simAthenaLoadedTables } from "./sim-athena-loaded-tables.js";
15
16
  *
16
17
  * Each of them carries a reason out, which a strict engine fails the query
17
18
  * with.
19
+ *
20
+ * A function shim refusing one of the statement's values is the exception. The
21
+ * engine ran the statement and could not evaluate it, and the query fails
22
+ * whether or not the engine is strict.
18
23
  */
19
24
  export async function simAthenaEngineRun(run) {
20
25
  try {
@@ -41,6 +46,9 @@ export async function simAthenaEngineRun(run) {
41
46
  }
42
47
  }
43
48
  catch (error) {
49
+ if (error instanceof SimAthenaShimRefusal) {
50
+ return simAthenaEngineRejected(simAthenaUnevaluable(error));
51
+ }
44
52
  return simAthenaEngineTurnedDown(simAthenaRunFailure(error));
45
53
  }
46
54
  }
@@ -28,8 +28,8 @@ function matches(value, pattern) {
28
28
  *
29
29
  * Trino counts the groups from one and calls the whole match zero, which is
30
30
  * what a regular expression counts them as anywhere. Trino also refuses a group
31
- * the pattern has not got, and raising here turns the query down rather than
32
- * letting a negative index quietly count back from the end.
31
+ * the pattern has not got, and raising here fails the query where a negative
32
+ * index would quietly count back from the end.
33
33
  */
34
34
  function extracted(value, pattern, group) {
35
35
  if (!Number.isSafeInteger(group) || group < 0) {
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A Trino function shim that could not answer for one value.
3
+ *
4
+ * Each shim raises where it cannot answer faithfully, such as a date function
5
+ * handed text that is no timestamp. SQLite hands the error back out of the
6
+ * statement unchanged, and this class is how the engine tells it apart from
7
+ * SQLite refusing the statement itself.
8
+ *
9
+ * The engine fails the query on one of these whether or not it is strict. The
10
+ * statement parsed and SQLite ran it, so falling back would answer a query the
11
+ * engine had taken on with rows a declaration wrote for some other purpose,
12
+ * often no rows at all.
13
+ */
14
+ export declare class SimAthenaShimRefusal extends Error {
15
+ readonly name = "SimAthenaShimRefusal";
16
+ constructor(described: string, cause: unknown);
17
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A Trino function shim that could not answer for one value.
3
+ *
4
+ * Each shim raises where it cannot answer faithfully, such as a date function
5
+ * handed text that is no timestamp. SQLite hands the error back out of the
6
+ * statement unchanged, and this class is how the engine tells it apart from
7
+ * SQLite refusing the statement itself.
8
+ *
9
+ * The engine fails the query on one of these whether or not it is strict. The
10
+ * statement parsed and SQLite ran it, so falling back would answer a query the
11
+ * engine had taken on with rows a declaration wrote for some other purpose,
12
+ * often no rows at all.
13
+ */
14
+ export class SimAthenaShimRefusal extends Error {
15
+ name = "SimAthenaShimRefusal";
16
+ constructor(described, cause) {
17
+ const reason = cause instanceof Error ? cause.message : String(cause);
18
+ super(`${described} could not answer for this statement's values. ${reason}`, {
19
+ cause,
20
+ });
21
+ }
22
+ }
@@ -7,8 +7,12 @@ export type SimAthenaScalarShim = (...values: SQLOutputValue[]) => SQLInputValue
7
7
  * Every shim takes any number of arguments, because SQLite checks the count
8
8
  * against the JavaScript function's own arity otherwise and a Trino function
9
9
  * with an optional argument would stop resolving.
10
+ *
11
+ * Anything the shim raises comes back out as a `SimAthenaShimRefusal` naming
12
+ * the function as a statement wrote it, which fails the query. `described`
13
+ * is that name where it differs from the one registered.
10
14
  */
11
- export declare function simAthenaScalarShim(database: DatabaseSync, name: string, implementation: SimAthenaScalarShim): void;
15
+ export declare function simAthenaScalarShim(database: DatabaseSync, name: string, implementation: SimAthenaScalarShim, described?: string): void;
12
16
  /**
13
17
  * Whether an optional argument was written as an explicit `NULL`.
14
18
  *
@@ -1,12 +1,24 @@
1
+ import { SimAthenaShimRefusal } from "./sim-athena-shim-refusal.js";
1
2
  /**
2
3
  * Register one Trino scalar function on this database.
3
4
  *
4
5
  * Every shim takes any number of arguments, because SQLite checks the count
5
6
  * against the JavaScript function's own arity otherwise and a Trino function
6
7
  * with an optional argument would stop resolving.
8
+ *
9
+ * Anything the shim raises comes back out as a `SimAthenaShimRefusal` naming
10
+ * the function as a statement wrote it, which fails the query. `described`
11
+ * is that name where it differs from the one registered.
7
12
  */
8
- export function simAthenaScalarShim(database, name, implementation) {
9
- database.function(name, { varargs: true, deterministic: true }, (...values) => withEmptyBytes(implementation(...values)));
13
+ export function simAthenaScalarShim(database, name, implementation, described = name) {
14
+ database.function(name, { varargs: true, deterministic: true }, (...values) => {
15
+ try {
16
+ return withEmptyBytes(implementation(...values));
17
+ }
18
+ catch (error) {
19
+ throw new SimAthenaShimRefusal(described, error);
20
+ }
21
+ });
10
22
  }
11
23
  /**
12
24
  * One answer, with an empty `varbinary` written so that SQLite keeps it.
@@ -1,6 +1,7 @@
1
1
  import { simAthenaInstallAggregateShims } from "./sim-athena-aggregate-shims.js";
2
2
  import { simAthenaInstallArrayShims } from "./sim-athena-array-shims.js";
3
3
  import { simAthenaInstallBinaryShims } from "./sim-athena-binary-shims.js";
4
+ import { simAthenaInstallCastShims } from "./sim-athena-cast-shims.js";
4
5
  import { simAthenaInstallClockShims } from "./sim-athena-clock-shims.js";
5
6
  import { simAthenaInstallComparisonShims } from "./sim-athena-comparison-shims.js";
6
7
  import { simAthenaInstallDateArithmeticShims } from "./sim-athena-date-arithmetic-shims.js";
@@ -22,6 +23,7 @@ export function simAthenaInstallShims(database, startedAt) {
22
23
  simAthenaInstallClockShims(database, startedAt);
23
24
  simAthenaInstallDateShims(database);
24
25
  simAthenaInstallDateArithmeticShims(database);
26
+ simAthenaInstallCastShims(database);
25
27
  simAthenaInstallJsonShims(database);
26
28
  simAthenaInstallJsonDocumentShims(database);
27
29
  simAthenaInstallArrayShims(database);
@@ -1,28 +1,6 @@
1
1
  import type { SimAthenaCatalogTable } from "../table/sim-athena-catalog-table.js";
2
2
  import type { SimAthenaSqlParser } from "./sim-athena-parser-module.js";
3
- /** One statement translated for SQLite, or why it was not. */
4
- export type SimAthenaTranslatedSql = {
5
- readonly sql: string;
6
- /**
7
- * The decimal places each result column was rounded to, by position.
8
- *
9
- * Read here because the syntax tree is the only place that knows, and
10
- * the tree is gone by the time the answer is rendered.
11
- */
12
- readonly scales: ReadonlyMap<number, number>;
13
- readonly turnedDown: undefined;
14
- readonly rejected: undefined;
15
- } | {
16
- readonly sql: undefined;
17
- readonly scales: undefined;
18
- readonly turnedDown: string;
19
- readonly rejected: undefined;
20
- } | {
21
- readonly sql: undefined;
22
- readonly scales: undefined;
23
- readonly turnedDown: undefined;
24
- readonly rejected: string;
25
- };
3
+ import { type SimAthenaTranslatedSql } from "./sim-athena-translated-sql.js";
26
4
  interface SimAthenaSqlTranslationRequest {
27
5
  readonly parser: SimAthenaSqlParser;
28
6
  readonly athenaSql: string;
@@ -1,7 +1,9 @@
1
1
  import { simAthenaSqlForParser, simAthenaSqlForSqlite, } from "./sim-athena-sql-rewrites.js";
2
2
  import { simAthenaUnparsedStatement, simAthenaUnrewrittenUnnest, simAthenaUnwrittenStatement, } from "./sim-athena-turn-down.js";
3
3
  import { simAthenaRejectedStatement } from "./sim-athena-rejected-statement.js";
4
+ import { simAthenaRewriteCasts } from "./sim-athena-cast-rewrite.js";
4
5
  import { simAthenaRoundScales } from "./sim-athena-round-scales.js";
6
+ import { simAthenaRejectedSql, simAthenaTranslated, simAthenaTurnedDownSql, } from "./sim-athena-translated-sql.js";
5
7
  import { simAthenaRewriteUnnest } from "./sim-athena-unnest-rewrite.js";
6
8
  /**
7
9
  * One Athena statement written back out for SQLite, or why it is not.
@@ -26,16 +28,11 @@ export function simAthenaSqliteSql(request) {
26
28
  ast = parser.astify(read.sql, { database: "athena" });
27
29
  }
28
30
  catch {
29
- return turnedDown(simAthenaUnparsedStatement);
31
+ return simAthenaTurnedDownSql(simAthenaUnparsedStatement);
30
32
  }
31
33
  const rejected = simAthenaRejectedStatement(ast);
32
34
  if (rejected !== undefined) {
33
- return {
34
- sql: undefined,
35
- scales: undefined,
36
- turnedDown: undefined,
37
- rejected,
38
- };
35
+ return simAthenaRejectedSql(rejected);
39
36
  }
40
37
  try {
41
38
  if (!simAthenaRewriteUnnest({
@@ -43,27 +40,15 @@ export function simAthenaSqliteSql(request) {
43
40
  ordinality: read.ordinality,
44
41
  tables: request.tables,
45
42
  })) {
46
- return turnedDown(simAthenaUnrewrittenUnnest);
43
+ return simAthenaTurnedDownSql(simAthenaUnrewrittenUnnest);
47
44
  }
45
+ simAthenaRewriteCasts(ast, request.tables);
48
46
  // Read before the tree is written back out, since the rewrites above can
49
47
  // move a select item and the scale travels by position.
50
48
  const scales = simAthenaRoundScales(ast);
51
- return {
52
- sql: simAthenaSqlForSqlite(parser.sqlify(ast, { database: "sqlite" })),
53
- scales,
54
- turnedDown: undefined,
55
- rejected: undefined,
56
- };
49
+ return simAthenaTranslated(simAthenaSqlForSqlite(parser.sqlify(ast, { database: "sqlite" })), scales);
57
50
  }
58
51
  catch {
59
- return turnedDown(simAthenaUnwrittenStatement);
52
+ return simAthenaTurnedDownSql(simAthenaUnwrittenStatement);
60
53
  }
61
54
  }
62
- function turnedDown(reason) {
63
- return {
64
- sql: undefined,
65
- scales: undefined,
66
- turnedDown: reason,
67
- rejected: undefined,
68
- };
69
- }
@@ -0,0 +1,29 @@
1
+ /** One statement translated for SQLite, or why it was not. */
2
+ export type SimAthenaTranslatedSql = {
3
+ readonly sql: string;
4
+ /**
5
+ * The decimal places each result column was rounded to, by position.
6
+ *
7
+ * Read here because the syntax tree is the only place that knows, and
8
+ * the tree is gone by the time the answer is rendered.
9
+ */
10
+ readonly scales: ReadonlyMap<number, number>;
11
+ readonly turnedDown: undefined;
12
+ readonly rejected: undefined;
13
+ } | {
14
+ readonly sql: undefined;
15
+ readonly scales: undefined;
16
+ readonly turnedDown: string;
17
+ readonly rejected: undefined;
18
+ } | {
19
+ readonly sql: undefined;
20
+ readonly scales: undefined;
21
+ readonly turnedDown: undefined;
22
+ readonly rejected: string;
23
+ };
24
+ /** One statement written out for SQLite, with the scales read off its tree. */
25
+ export declare function simAthenaTranslated(sql: string, scales: ReadonlyMap<number, number>): SimAthenaTranslatedSql;
26
+ /** One statement the engine turned down, and why. */
27
+ export declare function simAthenaTurnedDownSql(turnedDown: string): SimAthenaTranslatedSql;
28
+ /** One statement Athena refuses, and why. */
29
+ export declare function simAthenaRejectedSql(rejected: string): SimAthenaTranslatedSql;
@@ -0,0 +1,12 @@
1
+ /** One statement written out for SQLite, with the scales read off its tree. */
2
+ export function simAthenaTranslated(sql, scales) {
3
+ return { sql, scales, turnedDown: undefined, rejected: undefined };
4
+ }
5
+ /** One statement the engine turned down, and why. */
6
+ export function simAthenaTurnedDownSql(turnedDown) {
7
+ return { sql: undefined, scales: undefined, turnedDown, rejected: undefined };
8
+ }
9
+ /** One statement Athena refuses, and why. */
10
+ export function simAthenaRejectedSql(rejected) {
11
+ return { sql: undefined, scales: undefined, turnedDown: undefined, rejected };
12
+ }
@@ -14,6 +14,13 @@ export declare const simAthenaUnrewrittenUnnest = "The simulated Athena query en
14
14
  export declare const simAthenaUnwrittenStatement = "The simulated Athena query engine cannot write this statement back out for SQLite.";
15
15
  /** Whatever went wrong while the tables were loaded or the statement ran. */
16
16
  export declare function simAthenaRunFailure(error: unknown): string;
17
+ /**
18
+ * A function shim that could not answer for one of the statement's values.
19
+ *
20
+ * This fails the query whether or not the engine is strict, so the reason
21
+ * points at the one declaration that still answers it.
22
+ */
23
+ export declare function simAthenaUnevaluable(refusal: Error): string;
17
24
  /**
18
25
  * One table the engine has no reader for.
19
26
  *
@@ -21,6 +21,17 @@ export function simAthenaRunFailure(error) {
21
21
  const message = error instanceof Error ? error.message : String(error);
22
22
  return `${engine} could not answer this statement. ${message}`;
23
23
  }
24
+ /**
25
+ * A function shim that could not answer for one of the statement's values.
26
+ *
27
+ * This fails the query whether or not the engine is strict, so the reason
28
+ * points at the one declaration that still answers it.
29
+ */
30
+ export function simAthenaUnevaluable(refusal) {
31
+ return (`${engine} cannot evaluate this statement. ${refusal.message} ` +
32
+ `Declare what this exact query answers with through results() to answer ` +
33
+ `it without the engine.`);
34
+ }
24
35
  /**
25
36
  * One table the engine has no reader for.
26
37
  *
@@ -51,7 +51,8 @@ interface SimAthenaQueryAnswerRequest {
51
51
  *
52
52
  * A statement Athena refuses fails whichever mode the engine is in. That is
53
53
  * SQL the service would never have run, and falling back would leave the query
54
- * green on it.
54
+ * green on it. So does a statement the engine ran and could not evaluate,
55
+ * where falling back to the default would answer with no rows at all.
55
56
  */
56
57
  export declare function simAthenaQueryAnswer(request: SimAthenaQueryAnswerRequest): Promise<SimAthenaQueryAnswer>;
57
58
  export {};
@@ -18,7 +18,8 @@ import { simAthenaStrictRefusal } from "../engine/sim-athena-turn-down.js";
18
18
  *
19
19
  * A statement Athena refuses fails whichever mode the engine is in. That is
20
20
  * SQL the service would never have run, and falling back would leave the query
21
- * green on it.
21
+ * green on it. So does a statement the engine ran and could not evaluate,
22
+ * where falling back to the default would answer with no rows at all.
22
23
  */
23
24
  export async function simAthenaQueryAnswer(request) {
24
25
  const forQuery = request.results.declaredForQuery(request.queryString);
@@ -0,0 +1,10 @@
1
+ import { type SimAthenaEngineSimulation } from "./sim-athena-engine.fixture.js";
2
+ /**
3
+ * A table of snapshots partitioned by a string `day`, with the engine on and
4
+ * a default declaration that a fallback would answer with.
5
+ *
6
+ * `taken_at` is a Glue `timestamp` written as ISO-8601 with a `T` and a `Z`.
7
+ * `noted_at` is a `string` holding a timestamp the way Trino's cast from text
8
+ * reads one.
9
+ */
10
+ export declare function aSnapshotSimulation(): Promise<SimAthenaEngineSimulation>;
@@ -0,0 +1,39 @@
1
+ import { aCatalogTable, anEngineSimulation, aSeededJson, } from "./sim-athena-engine.fixture.js";
2
+ /**
3
+ * A table of snapshots partitioned by a string `day`, with the engine on and
4
+ * a default declaration that a fallback would answer with.
5
+ *
6
+ * `taken_at` is a Glue `timestamp` written as ISO-8601 with a `T` and a `Z`.
7
+ * `noted_at` is a `string` holding a timestamp the way Trino's cast from text
8
+ * reads one.
9
+ */
10
+ export async function aSnapshotSimulation() {
11
+ const simulation = await anEngineSimulation();
12
+ aCatalogTable(simulation.simAws, {
13
+ name: "snapshots",
14
+ columns: [
15
+ { Name: "sku", Type: "string" },
16
+ { Name: "taken_at", Type: "timestamp" },
17
+ { Name: "noted_at", Type: "string" },
18
+ ],
19
+ partitionKeys: [{ Name: "day", Type: "string" }],
20
+ });
21
+ await aSeededJson(simulation.simAws, "snapshots/day=2026-08-15/part-0.json", [
22
+ {
23
+ sku: "bolt",
24
+ taken_at: "2026-08-15T23:30:00Z",
25
+ noted_at: "2026-08-15 10:00:00",
26
+ },
27
+ {
28
+ sku: "nut",
29
+ taken_at: "2026-08-14T08:00:00Z",
30
+ noted_at: "2026-08-14 09:15:00.5",
31
+ },
32
+ ]);
33
+ await simulation.simAws.athena().engine().enable();
34
+ simulation.simAws
35
+ .athena()
36
+ .results()
37
+ .byDefault({ columns: ["fallback"], rows: [["declared"]] });
38
+ return simulation;
39
+ }
@@ -383,8 +383,8 @@ test's own statement about a query the engine gets wrong.
383
383
 
384
384
  ### Failing a statement Athena refuses
385
385
 
386
- One failure ignores strict mode. Where the engine can see that Athena would refuse a statement
387
- outright, the query fails whether strict mode is on or off. A turn-down means the engine reached its
386
+ Two failures ignore strict mode. This section covers the first. Where the engine can see that
387
+ Athena would refuse a statement outright, the query fails whether strict mode is on or off. A turn-down means the engine reached its
388
388
  limits, and a declaration is a fair answer to that. A rejection means the SQL is invalid, and
389
389
  answering from a declaration would leave a test green on a query that fails against AWS.
390
390
 
@@ -411,6 +411,18 @@ well, since the two names differ and nothing shadows.
411
411
  Trino and SQLite resolve names by different rules in other places too, and the engine catches this
412
412
  one. A statement that behaves differently under the two is worth reporting.
413
413
 
414
+ ### Failing a value a function cannot read
415
+
416
+ The second failure that ignores strict mode comes from a Trino function the engine runs. Where one
417
+ cannot answer faithfully for a value the statement hands it, the query fails with a
418
+ `StateChangeReason` naming the function and the value. `date_add('fortnight', ...)` fails this way,
419
+ and so does `CAST(sku AS DATE)` over a column holding no date.
420
+
421
+ The engine took the statement on and ran it, so falling back would answer from rows a declaration
422
+ wrote for some other purpose. A query with no declaration of its own would answer from the
423
+ default, which is often no rows at all, and a test reading it would see an empty result with no
424
+ error. A declaration written against the exact query text still answers it.
425
+
414
426
  ### The objects it reads
415
427
 
416
428
  The SerDe class name in the table's storage descriptor says how its objects are decoded.
@@ -560,7 +572,7 @@ call written inside a string literal is left alone as well.
560
572
  instant it froze, and the same test answers the same way on every machine. Athena reads both at the
561
573
  instant the query started, which is what the execution records.
562
574
 
563
- A function that cannot answer faithfully raises rather than guessing, and the query falls back.
575
+ A function that cannot answer faithfully raises rather than guessing, and the query fails.
564
576
  `date_add` with a unit Trino does not name, `slice` starting at zero, `array_join` over an array of
565
577
  objects, `least` over a `varbinary`, and `regexp_extract` naming a capture group the pattern has not
566
578
  got all land there. A null
@@ -1064,7 +1076,7 @@ Current documented limitations:
1064
1076
  statement comparing the answer against a bare string matches on one and not the other.
1065
1077
  - A timestamp carrying a numeric UTC offset falls outside the date functions, and so does one
1066
1078
  written finer than the millisecond. A value written with a `Z` or with no zone at all reads as
1067
- UTC, and a value the date functions cannot read turns the query down.
1079
+ UTC, and a value the date functions cannot read fails the query.
1068
1080
  - A JSON number beyond about fifteen significant digits loses the digits past that, wherever the
1069
1081
  engine reads JSON. An identifier of that size in a JSON lines object comes back rounded, and a
1070
1082
  filter on it can then match the wrong row.
@@ -1088,9 +1100,9 @@ Current documented limitations:
1088
1100
  written any other way gets whatever slicing that text comes to.
1089
1101
  - A `varbinary` in a result row reads as its bytes decoded as UTF-8. A digest read that way loses
1090
1102
  whatever in it was no UTF-8. A query answering with one wants `to_hex` or `to_base64` around it.
1091
- - `from_hex` and `from_base64` turn the query down over text the encoding cannot carry. Trino fails
1092
- the query, and reading as much of the text as parses would answer with bytes nobody wrote.
1093
- - `from_utf8` with a replacement named turns the query down where the bytes already carry the
1103
+ - `from_hex` and `from_base64` fail the query over text the encoding cannot carry, as Trino does.
1104
+ The alternative, reading as much of the text as parses, would answer with bytes nobody wrote.
1105
+ - `from_utf8` with a replacement named fails the query where the bytes already carry the
1094
1106
  replacement character. Trino writes the replacement only where a sequence was broken, and nothing
1095
1107
  survives decoding to tell one of those apart from a character the bytes held.
1096
1108
  - `spooky_hash_v2_32` and `spooky_hash_v2_64` are absent, and so are the other Trino hashes with no
@@ -1107,7 +1119,16 @@ Current documented limitations:
1107
1119
  runs.
1108
1120
  - `try_cast` runs as a plain cast. `try_cast('abc' AS integer)` therefore answers 0 where real
1109
1121
  Athena answers null, which is the same forgiving direction as the cast above. Reading the
1110
- statement without the rewrite would turn the whole query down.
1122
+ statement without the rewrite would turn the whole query down. A `try_cast` to `DATE` or
1123
+ `TIMESTAMP` over text the cast cannot read fails the query, where Athena answers null.
1124
+ - `CAST(x AS DATE)`, `date(x)` and `CAST(x AS TIMESTAMP)` read text the way Trino's cast from
1125
+ `varchar` does. A date is `yyyy-MM-dd` and nothing else. A timestamp is a date, optionally
1126
+ followed by a space and a time, with no `T` and no zone. A timestamp renders as
1127
+ `2026-08-15 10:00:00.000`, and a fraction finer than the millisecond is rounded.
1128
+ - A cast reads its value as text only where it is a string literal or a column the catalog declares
1129
+ as `string`, `varchar` or `char`. Anything else, such as a `timestamp` column or the answer of a
1130
+ function, is taken to be a date or a timestamp already, and any ISO-8601 form of it is accepted.
1131
+ The answer is text in a `varchar` column, where Athena reports a `date` or a `timestamp`.
1111
1132
  - A `decimal` column is held as a double. A value carrying more than about fifteen significant
1112
1133
  digits loses the ones past that, and a filter, a sum or a group on it can then answer
1113
1134
  differently from Athena's exact arithmetic.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kensio/yulin",
3
- "version": "1.21.29",
3
+ "version": "1.21.30",
4
4
  "description": "AWS system behaviour simulation for isolated unit testing",
5
5
  "repository": "https://github.com/KensioSoftware/yulin",
6
6
  "homepage": "https://yulinsim.dev/",