@malloydata/malloy 0.0.326 → 0.0.328
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.
- package/dist/api/core.js +2 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/row_data_utils.d.ts +30 -0
- package/dist/api/row_data_utils.js +87 -0
- package/dist/api/util.js +20 -38
- package/dist/dialect/dialect.d.ts +35 -0
- package/dist/dialect/dialect.js +47 -1
- package/dist/dialect/duckdb/duckdb.d.ts +2 -1
- package/dist/dialect/duckdb/duckdb.js +12 -3
- package/dist/dialect/mysql/mysql.js +18 -4
- package/dist/dialect/pg_impl.d.ts +1 -0
- package/dist/dialect/pg_impl.js +2 -0
- package/dist/dialect/postgres/postgres.js +4 -1
- package/dist/dialect/snowflake/snowflake.d.ts +2 -1
- package/dist/dialect/snowflake/snowflake.js +36 -15
- package/dist/dialect/standardsql/standardsql.d.ts +2 -1
- package/dist/dialect/standardsql/standardsql.js +8 -3
- package/dist/dialect/trino/trino.js +10 -2
- package/dist/lang/ast/expressions/expr-avg.d.ts +5 -0
- package/dist/lang/ast/expressions/expr-avg.js +9 -0
- package/dist/lang/ast/expressions/expr-coalesce.js +6 -0
- package/dist/lang/ast/expressions/expr-number.d.ts +10 -1
- package/dist/lang/ast/expressions/expr-number.js +23 -7
- package/dist/lang/ast/expressions/expr-props.d.ts +1 -1
- package/dist/lang/ast/expressions/pick-when.js +10 -3
- package/dist/lang/ast/types/expression-def.js +36 -2
- package/dist/lang/parse-log.d.ts +1 -0
- package/dist/lang/test/test-translator.js +2 -0
- package/dist/malloy.d.ts +23 -2
- package/dist/malloy.js +204 -41
- package/dist/model/malloy_types.d.ts +2 -2
- package/dist/model/query_model_impl.js +5 -1
- package/dist/test/cellsToObject.d.ts +6 -0
- package/dist/test/cellsToObject.js +111 -0
- package/dist/test/index.d.ts +47 -0
- package/dist/test/index.js +95 -20
- package/dist/test/matchers.d.ts +10 -0
- package/dist/test/matchers.js +17 -0
- package/dist/test/resultMatchers.d.ts +42 -0
- package/dist/test/resultMatchers.js +722 -0
- package/dist/test/runQuery.d.ts +31 -0
- package/dist/test/runQuery.js +67 -0
- package/dist/test/test-models.d.ts +77 -0
- package/dist/test/test-models.js +319 -0
- package/dist/test/test-values.d.ts +66 -0
- package/dist/test/test-values.js +208 -0
- package/dist/to_stable.js +3 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +9 -5
|
@@ -56,8 +56,8 @@ function qtz(qi) {
|
|
|
56
56
|
const bqToMalloyTypes = {
|
|
57
57
|
'DATE': { type: 'date' },
|
|
58
58
|
'STRING': { type: 'string' },
|
|
59
|
-
'INTEGER': { type: 'number', numberType: '
|
|
60
|
-
'INT64': { type: 'number', numberType: '
|
|
59
|
+
'INTEGER': { type: 'number', numberType: 'bigint' },
|
|
60
|
+
'INT64': { type: 'number', numberType: 'bigint' },
|
|
61
61
|
'FLOAT': { type: 'number', numberType: 'float' },
|
|
62
62
|
'FLOAT64': { type: 'number', numberType: 'float' },
|
|
63
63
|
'NUMERIC': { type: 'number', numberType: 'float' },
|
|
@@ -97,6 +97,10 @@ class StandardSQLDialect extends dialect_1.Dialect {
|
|
|
97
97
|
this.nestedArrays = false; // Can't have an array of arrays for some reason
|
|
98
98
|
this.supportsHyperLogLog = true;
|
|
99
99
|
this.likeEscape = false; // Uses \ instead of ESCAPE 'X' in like clauses
|
|
100
|
+
// BigQuery only has INT64 - all integers can exceed JS Number precision
|
|
101
|
+
this.integerTypeMappings = [
|
|
102
|
+
{ min: dialect_1.MIN_INT64, max: dialect_1.MAX_INT64, numberType: 'bigint' },
|
|
103
|
+
];
|
|
100
104
|
}
|
|
101
105
|
quoteTablePath(tablePath) {
|
|
102
106
|
return `\`${tablePath}\``;
|
|
@@ -365,7 +369,8 @@ ${(0, utils_1.indent)(sql)}
|
|
|
365
369
|
}
|
|
366
370
|
malloyTypeToSQLType(malloyType) {
|
|
367
371
|
if (malloyType.type === 'number') {
|
|
368
|
-
if (malloyType.numberType === 'integer'
|
|
372
|
+
if (malloyType.numberType === 'integer' ||
|
|
373
|
+
malloyType.numberType === 'bigint') {
|
|
369
374
|
return 'INT64';
|
|
370
375
|
}
|
|
371
376
|
else {
|
|
@@ -52,7 +52,7 @@ function qtz(qi) {
|
|
|
52
52
|
const trinoToMalloyTypes = {
|
|
53
53
|
'varchar': { type: 'string' },
|
|
54
54
|
'integer': { type: 'number', numberType: 'integer' },
|
|
55
|
-
'bigint': { type: 'number', numberType: '
|
|
55
|
+
'bigint': { type: 'number', numberType: 'bigint' },
|
|
56
56
|
'smallint': { type: 'number', numberType: 'integer' },
|
|
57
57
|
'tinyint': { type: 'number', numberType: 'integer' },
|
|
58
58
|
'double': { type: 'number', numberType: 'float' },
|
|
@@ -480,7 +480,15 @@ ${(0, utils_1.indent)(sql)}
|
|
|
480
480
|
malloyTypeToSQLType(malloyType) {
|
|
481
481
|
switch (malloyType.type) {
|
|
482
482
|
case 'number':
|
|
483
|
-
|
|
483
|
+
if (malloyType.numberType === 'integer') {
|
|
484
|
+
return 'INTEGER';
|
|
485
|
+
}
|
|
486
|
+
else if (malloyType.numberType === 'bigint') {
|
|
487
|
+
return 'BIGINT';
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
return 'DOUBLE';
|
|
491
|
+
}
|
|
484
492
|
case 'string':
|
|
485
493
|
return 'VARCHAR';
|
|
486
494
|
case 'timestamptz':
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { FieldReference } from '../query-items/field-references';
|
|
2
2
|
import type { ExpressionDef } from '../types/expression-def';
|
|
3
|
+
import type { ExprValue } from '../types/expr-value';
|
|
3
4
|
import { ExprAsymmetric } from './expr-asymmetric';
|
|
4
5
|
export declare class ExprAvg extends ExprAsymmetric {
|
|
5
6
|
constructor(expr: ExpressionDef | undefined, source?: FieldReference, explicitSource?: boolean);
|
|
7
|
+
/**
|
|
8
|
+
* avg() always returns a float, regardless of input type.
|
|
9
|
+
*/
|
|
10
|
+
returns(ev: ExprValue): ExprValue;
|
|
6
11
|
}
|
|
@@ -29,6 +29,15 @@ class ExprAvg extends expr_asymmetric_1.ExprAsymmetric {
|
|
|
29
29
|
super('avg', expr, source, explicitSource);
|
|
30
30
|
this.has({ source: source });
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* avg() always returns a float, regardless of input type.
|
|
34
|
+
*/
|
|
35
|
+
returns(ev) {
|
|
36
|
+
if (ev.type === 'number') {
|
|
37
|
+
return { ...ev, numberType: 'float' };
|
|
38
|
+
}
|
|
39
|
+
return ev;
|
|
40
|
+
}
|
|
32
41
|
}
|
|
33
42
|
exports.ExprAvg = ExprAvg;
|
|
34
43
|
//# sourceMappingURL=expr-avg.js.map
|
|
@@ -87,8 +87,14 @@ class ExprCoalesce extends expression_def_1.ExpressionDef {
|
|
|
87
87
|
this.logError('mismatched-coalesce-types', `Mismatched types for coalesce (${maybeNull.type}, ${whenNull.type})`);
|
|
88
88
|
}
|
|
89
89
|
const srcForType = maybeNull.type === 'error' ? whenNull : maybeNull;
|
|
90
|
+
// If both are numbers but subtypes differ, strip the subtype
|
|
91
|
+
const stripNumberType = srcForType.type === 'number' &&
|
|
92
|
+
maybeNull.type === 'number' &&
|
|
93
|
+
whenNull.type === 'number' &&
|
|
94
|
+
maybeNull.numberType !== whenNull.numberType;
|
|
90
95
|
return {
|
|
91
96
|
...srcForType,
|
|
97
|
+
...(stripNumberType ? { numberType: undefined } : {}),
|
|
92
98
|
expressionType: (0, model_1.maxExpressionType)(maybeNull.expressionType, whenNull.expressionType),
|
|
93
99
|
value: {
|
|
94
100
|
node: 'coalesce',
|
|
@@ -6,7 +6,16 @@ export declare class ExprNumber extends ExpressionDef {
|
|
|
6
6
|
readonly n: string;
|
|
7
7
|
elementType: string;
|
|
8
8
|
constructor(n: string);
|
|
9
|
-
getExpression(
|
|
9
|
+
getExpression(fs: FieldSpace): ExprValue;
|
|
10
|
+
/**
|
|
11
|
+
* Default number type when no dialect is available.
|
|
12
|
+
* Integers default to bigint for safety, floats to float.
|
|
13
|
+
*/
|
|
14
|
+
private defaultNumberType;
|
|
15
|
+
/**
|
|
16
|
+
* For constants (no dialect context), always use bigint for integers
|
|
17
|
+
* to ensure large values render correctly.
|
|
18
|
+
*/
|
|
10
19
|
constantExpression(): ExprValue;
|
|
11
20
|
getStableLiteral(): Malloy.LiteralValue;
|
|
12
21
|
}
|
|
@@ -31,16 +31,32 @@ class ExprNumber extends expression_def_1.ExpressionDef {
|
|
|
31
31
|
this.n = n;
|
|
32
32
|
this.elementType = 'numeric literal';
|
|
33
33
|
}
|
|
34
|
-
getExpression(
|
|
35
|
-
|
|
34
|
+
getExpression(fs) {
|
|
35
|
+
var _a;
|
|
36
|
+
const dialect = fs.dialectObj();
|
|
37
|
+
const dataType = (_a = dialect === null || dialect === void 0 ? void 0 : dialect.literalNumberType(this.n)) !== null && _a !== void 0 ? _a : this.defaultNumberType();
|
|
38
|
+
return (0, expr_value_1.literalExprValue)({
|
|
39
|
+
dataType,
|
|
40
|
+
value: { node: 'numberLiteral', literal: this.n },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Default number type when no dialect is available.
|
|
45
|
+
* Integers default to bigint for safety, floats to float.
|
|
46
|
+
*/
|
|
47
|
+
defaultNumberType() {
|
|
48
|
+
const isInteger = /^-?\d+$/.test(this.n);
|
|
49
|
+
return isInteger
|
|
50
|
+
? { type: 'number', numberType: 'bigint' }
|
|
51
|
+
: { type: 'number', numberType: 'float' };
|
|
36
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* For constants (no dialect context), always use bigint for integers
|
|
55
|
+
* to ensure large values render correctly.
|
|
56
|
+
*/
|
|
37
57
|
constantExpression() {
|
|
38
|
-
const n = Number(this.n);
|
|
39
|
-
const dataType = Number.isNaN(n)
|
|
40
|
-
? { type: 'number' }
|
|
41
|
-
: { type: 'number', numberType: Number.isInteger(n) ? 'integer' : 'float' };
|
|
42
58
|
return (0, expr_value_1.literalExprValue)({
|
|
43
|
-
dataType,
|
|
59
|
+
dataType: this.defaultNumberType(),
|
|
44
60
|
value: { node: 'numberLiteral', literal: this.n },
|
|
45
61
|
});
|
|
46
62
|
}
|
|
@@ -63,7 +63,7 @@ export declare class ExprProps extends ExpressionDef {
|
|
|
63
63
|
} | {
|
|
64
64
|
requiresGroupBy: import("../../../model/malloy_types").RequiredGroupBy[] | undefined;
|
|
65
65
|
type: "number";
|
|
66
|
-
numberType?: "integer" | "float";
|
|
66
|
+
numberType?: "integer" | "float" | "bigint";
|
|
67
67
|
expressionType: import("../../../model/malloy_types").ExpressionType;
|
|
68
68
|
evalSpace: import("../../../model/malloy_types").EvalSpace;
|
|
69
69
|
fieldUsage: import("../../../model/malloy_types").FieldUsage[];
|
|
@@ -61,9 +61,16 @@ const expr_value_1 = require("../types/expr-value");
|
|
|
61
61
|
const expression_def_1 = require("../types/expression-def");
|
|
62
62
|
const malloy_element_1 = require("../types/malloy-element");
|
|
63
63
|
function typeCoalesce(ev1, ev2) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
if (ev1 === undefined || ev1.type === 'null' || ev1.type === 'error') {
|
|
65
|
+
return ev2;
|
|
66
|
+
}
|
|
67
|
+
// If both are numbers but subtypes differ, strip the subtype
|
|
68
|
+
if (ev1.type === 'number' && ev2.type === 'number') {
|
|
69
|
+
if (ev1.numberType !== ev2.numberType) {
|
|
70
|
+
return { ...ev1, numberType: undefined };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return ev1;
|
|
67
74
|
}
|
|
68
75
|
class Pick extends expression_def_1.ExpressionDef {
|
|
69
76
|
constructor(choices, elsePick) {
|
|
@@ -362,6 +362,40 @@ function compare(fs, left, op, right) {
|
|
|
362
362
|
from: [lhs, rhs],
|
|
363
363
|
});
|
|
364
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Computes the result numberType for arithmetic operations.
|
|
367
|
+
* - Division/modulo always return float
|
|
368
|
+
* - Float wins over integer/bigint
|
|
369
|
+
* - Bigint wins over integer
|
|
370
|
+
* - Both integer = integer
|
|
371
|
+
* - Unknown = no subtype
|
|
372
|
+
*/
|
|
373
|
+
function mergeNumberTypes(lhs, rhs, op) {
|
|
374
|
+
// Division and modulo always return float
|
|
375
|
+
if (op === '/' || op === '%') {
|
|
376
|
+
return { type: 'number', numberType: 'float' };
|
|
377
|
+
}
|
|
378
|
+
// Only applies if both are numbers
|
|
379
|
+
if (lhs.type !== 'number' || rhs.type !== 'number') {
|
|
380
|
+
return { type: 'number' };
|
|
381
|
+
}
|
|
382
|
+
const leftSubtype = lhs.numberType;
|
|
383
|
+
const rightSubtype = rhs.numberType;
|
|
384
|
+
// If either is float, result is float
|
|
385
|
+
if (leftSubtype === 'float' || rightSubtype === 'float') {
|
|
386
|
+
return { type: 'number', numberType: 'float' };
|
|
387
|
+
}
|
|
388
|
+
// If either is bigint, result is bigint
|
|
389
|
+
if (leftSubtype === 'bigint' || rightSubtype === 'bigint') {
|
|
390
|
+
return { type: 'number', numberType: 'bigint' };
|
|
391
|
+
}
|
|
392
|
+
// Both are integer, result is integer
|
|
393
|
+
if (leftSubtype === 'integer' && rightSubtype === 'integer') {
|
|
394
|
+
return { type: 'number', numberType: 'integer' };
|
|
395
|
+
}
|
|
396
|
+
// Unknown - no subtype
|
|
397
|
+
return { type: 'number' };
|
|
398
|
+
}
|
|
365
399
|
function numeric(fs, left, op, right) {
|
|
366
400
|
const lhs = left.getExpression(fs);
|
|
367
401
|
const rhs = right.getExpression(fs);
|
|
@@ -379,7 +413,7 @@ function numeric(fs, left, op, right) {
|
|
|
379
413
|
}
|
|
380
414
|
else {
|
|
381
415
|
return (0, expr_value_1.computedExprValue)({
|
|
382
|
-
dataType:
|
|
416
|
+
dataType: mergeNumberTypes(lhs, rhs, op),
|
|
383
417
|
value: { node: op, kids: { left: lhs.value, right: rhs.value } },
|
|
384
418
|
from: [lhs, rhs],
|
|
385
419
|
});
|
|
@@ -459,7 +493,7 @@ function applyBinary(fs, left, op, right) {
|
|
|
459
493
|
kids: { left: num.value, right: denom.value },
|
|
460
494
|
};
|
|
461
495
|
return (0, expr_value_1.computedExprValue)({
|
|
462
|
-
dataType:
|
|
496
|
+
dataType: mergeNumberTypes(num, denom, op),
|
|
463
497
|
value: divmod,
|
|
464
498
|
from: [num, denom],
|
|
465
499
|
});
|
package/dist/lang/parse-log.d.ts
CHANGED
|
@@ -385,6 +385,7 @@ type MessageParameterTypes = {
|
|
|
385
385
|
'non-scalar-grouped-by': string;
|
|
386
386
|
'missing-required-group-by': string;
|
|
387
387
|
'invalid-partition-composite': string;
|
|
388
|
+
'integer-literal-out-of-range': string;
|
|
388
389
|
};
|
|
389
390
|
export declare const MESSAGE_FORMATTERS: PartialErrorCodeMessageMap;
|
|
390
391
|
export type MessageCode = keyof MessageParameterTypes;
|
|
@@ -133,6 +133,7 @@ function humanify(value) {
|
|
|
133
133
|
return pretty(walk(value));
|
|
134
134
|
}
|
|
135
135
|
const intType = { type: 'number', numberType: 'integer' };
|
|
136
|
+
const bigintType = { type: 'number', numberType: 'bigint' };
|
|
136
137
|
const mockSchema = {
|
|
137
138
|
'aTable': {
|
|
138
139
|
type: 'table',
|
|
@@ -144,6 +145,7 @@ const mockSchema = {
|
|
|
144
145
|
{ type: 'string', name: 'astr' },
|
|
145
146
|
{ type: 'number', name: 'af', numberType: 'float' },
|
|
146
147
|
{ ...intType, name: 'ai' },
|
|
148
|
+
{ ...bigintType, name: 'abig' },
|
|
147
149
|
{ type: 'date', name: 'ad' },
|
|
148
150
|
{ type: 'boolean', name: 'abool' },
|
|
149
151
|
{ type: 'timestamp', name: 'ats' },
|
package/dist/malloy.d.ts
CHANGED
|
@@ -1200,7 +1200,7 @@ declare class DataJSON extends ScalarData<string> {
|
|
|
1200
1200
|
}
|
|
1201
1201
|
declare class DataNumber extends ScalarData<number> {
|
|
1202
1202
|
protected _field: NumberField;
|
|
1203
|
-
constructor(value:
|
|
1203
|
+
constructor(value: unknown, field: NumberField, parent: DataArrayOrRecord | undefined, parentRecord: DataRecord | undefined);
|
|
1204
1204
|
get field(): NumberField;
|
|
1205
1205
|
get key(): string;
|
|
1206
1206
|
compareTo(other: ScalarData<number>): 0 | 1 | -1;
|
|
@@ -1239,9 +1239,21 @@ export declare class DataArray extends Data<QueryData> implements Iterable<DataR
|
|
|
1239
1239
|
*/
|
|
1240
1240
|
get field(): Explore;
|
|
1241
1241
|
/**
|
|
1242
|
-
* @return The raw
|
|
1242
|
+
* @return The raw query data as returned by the database driver.
|
|
1243
|
+
* Values may be in various formats depending on the driver (wrapper objects, strings, etc.).
|
|
1244
|
+
* Use this for passing to mapData() which handles normalization itself.
|
|
1245
|
+
*/
|
|
1246
|
+
get rawData(): QueryData;
|
|
1247
|
+
/**
|
|
1248
|
+
* @return Normalized data with JS native types (number | bigint, Date).
|
|
1249
|
+
* Use this for CSV output, tests, and general programmatic access.
|
|
1243
1250
|
*/
|
|
1244
1251
|
toObject(): QueryData;
|
|
1252
|
+
/**
|
|
1253
|
+
* @return Normalized data with JSON-safe types (numbers as number | string, dates as ISO strings).
|
|
1254
|
+
* Use this for JSON serialization.
|
|
1255
|
+
*/
|
|
1256
|
+
toJSON(): QueryData;
|
|
1245
1257
|
path(...path: (number | string)[]): DataColumn;
|
|
1246
1258
|
row(index: number): DataRecord;
|
|
1247
1259
|
get rowCount(): number;
|
|
@@ -1257,7 +1269,16 @@ export declare class DataRecord extends Data<{
|
|
|
1257
1269
|
readonly index: number | undefined;
|
|
1258
1270
|
private cellCache;
|
|
1259
1271
|
constructor(queryDataRow: QueryDataRow, index: number | undefined, field: Explore, parent: DataArrayOrRecord | undefined, parentRecord: DataRecord | undefined);
|
|
1272
|
+
/**
|
|
1273
|
+
* @return Normalized data with JS native types (number | bigint, Date).
|
|
1274
|
+
* Use this for CSV output, tests, and general programmatic access.
|
|
1275
|
+
*/
|
|
1260
1276
|
toObject(): QueryDataRow;
|
|
1277
|
+
/**
|
|
1278
|
+
* @return Normalized data with JSON-safe types (numbers as number | string, dates as ISO strings).
|
|
1279
|
+
* Use this for JSON serialization.
|
|
1280
|
+
*/
|
|
1281
|
+
toJSON(): QueryDataRow;
|
|
1261
1282
|
path(...path: (number | string)[]): DataColumn;
|
|
1262
1283
|
cell(fieldOrName: string | Field): DataColumn;
|
|
1263
1284
|
get value(): {
|
package/dist/malloy.js
CHANGED
|
@@ -25,7 +25,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.InMemoryModelCache = exports.CacheManager = exports.CSVWriter = exports.JSONWriter = exports.DataWriter = exports.DataRecord = exports.DataArray = exports.Result = exports.ExploreMaterializer = exports.PreparedResultMaterializer = exports.QueryMaterializer = exports.ModelMaterializer = exports.SingleConnectionRuntime = exports.ConnectionRuntime = exports.Runtime = exports.ExploreField = exports.JoinRelationship = exports.QueryField = exports.Query = exports.StringField = exports.UnsupportedField = exports.JSONField = exports.BooleanField = exports.NumberField = exports.TimestampField = exports.DateField = exports.TimestampTimeframe = exports.DateTimeframe = exports.AtomicField = exports.AtomicFieldType = exports.Explore = exports.SourceRelationship = exports.FixedConnectionMap = exports.InMemoryURLReader = exports.EmptyURLReader = exports.PreparedResult = exports.DocumentCompletion = exports.DocumentSymbol = exports.DocumentPosition = exports.DocumentRange = exports.DocumentTablePath = exports.Parse = exports.PreparedQuery = exports.Model = exports.MalloyError = exports.Malloy = void 0;
|
|
26
26
|
const lang_1 = require("./lang");
|
|
27
27
|
const model_1 = require("./model");
|
|
28
|
-
const luxon_1 = require("luxon");
|
|
29
28
|
const dialect_1 = require("./dialect");
|
|
30
29
|
const version_1 = require("./version");
|
|
31
30
|
const uuid_1 = require("uuid");
|
|
@@ -33,6 +32,7 @@ const annotation_1 = require("./annotation");
|
|
|
33
32
|
const sql_block_1 = require("./model/sql_block");
|
|
34
33
|
const utils_1 = require("./lang/utils");
|
|
35
34
|
const reference_list_1 = require("./lang/reference-list");
|
|
35
|
+
const row_data_utils_1 = require("./api/row_data_utils");
|
|
36
36
|
function isSourceComponent(source) {
|
|
37
37
|
return (source.type === 'table' ||
|
|
38
38
|
source.type === 'sql_select' ||
|
|
@@ -2122,7 +2122,15 @@ class ModelMaterializer extends FluentState {
|
|
|
2122
2122
|
const result = await this.loadQuery(searchMapMalloy, options).run({
|
|
2123
2123
|
rowLimit: 1000,
|
|
2124
2124
|
});
|
|
2125
|
-
|
|
2125
|
+
const rawResult = result._queryResult.result;
|
|
2126
|
+
return rawResult.map(row => ({
|
|
2127
|
+
...row,
|
|
2128
|
+
cardinality: (0, row_data_utils_1.rowDataToNumber)(row.cardinality),
|
|
2129
|
+
values: row.values.map(v => ({
|
|
2130
|
+
...v,
|
|
2131
|
+
weight: (0, row_data_utils_1.rowDataToNumber)(v.weight),
|
|
2132
|
+
})),
|
|
2133
|
+
}));
|
|
2126
2134
|
}
|
|
2127
2135
|
/**
|
|
2128
2136
|
* Materialize the final query contained within this loaded `Model`.
|
|
@@ -2630,7 +2638,7 @@ class DataJSON extends ScalarData {
|
|
|
2630
2638
|
}
|
|
2631
2639
|
class DataNumber extends ScalarData {
|
|
2632
2640
|
constructor(value, field, parent, parentRecord) {
|
|
2633
|
-
super(value, field, parent, parentRecord);
|
|
2641
|
+
super((0, row_data_utils_1.rowDataToNumber)(value), field, parent, parentRecord);
|
|
2634
2642
|
this._field = field;
|
|
2635
2643
|
}
|
|
2636
2644
|
get field() {
|
|
@@ -2650,44 +2658,13 @@ class DataNumber extends ScalarData {
|
|
|
2650
2658
|
return -1;
|
|
2651
2659
|
}
|
|
2652
2660
|
}
|
|
2653
|
-
function valueToDate(value) {
|
|
2654
|
-
// TODO properly map the data from BQ/Postgres types
|
|
2655
|
-
if (value instanceof Date) {
|
|
2656
|
-
return value;
|
|
2657
|
-
}
|
|
2658
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2659
|
-
const valAsAny = value;
|
|
2660
|
-
if (valAsAny.constructor.name === 'Date') {
|
|
2661
|
-
// For some reason duckdb TSTZ values come back as objects which do not
|
|
2662
|
-
// pass "instance of" but do seem date like.
|
|
2663
|
-
return new Date(value);
|
|
2664
|
-
}
|
|
2665
|
-
else if (typeof value === 'number') {
|
|
2666
|
-
return new Date(value);
|
|
2667
|
-
}
|
|
2668
|
-
else if (typeof value !== 'string') {
|
|
2669
|
-
return new Date(value.value);
|
|
2670
|
-
}
|
|
2671
|
-
else {
|
|
2672
|
-
// Postgres timestamps end up here, and ideally we would know the system
|
|
2673
|
-
// timezone of the postgres instance to correctly create a Date() object
|
|
2674
|
-
// which represents the same instant in time, but we don't have the data
|
|
2675
|
-
// flow to implement that. This may be a problem at some future day,
|
|
2676
|
-
// so here is a comment, for that day.
|
|
2677
|
-
let parsed = luxon_1.DateTime.fromISO(value, { zone: 'UTC' });
|
|
2678
|
-
if (!parsed.isValid) {
|
|
2679
|
-
parsed = luxon_1.DateTime.fromSQL(value, { zone: 'UTC' });
|
|
2680
|
-
}
|
|
2681
|
-
return parsed.toJSDate();
|
|
2682
|
-
}
|
|
2683
|
-
}
|
|
2684
2661
|
class DataTimestamp extends ScalarData {
|
|
2685
2662
|
constructor(value, field, parent, parentRecord) {
|
|
2686
2663
|
super(value, field, parent, parentRecord);
|
|
2687
2664
|
this._field = field;
|
|
2688
2665
|
}
|
|
2689
2666
|
get value() {
|
|
2690
|
-
return
|
|
2667
|
+
return (0, row_data_utils_1.rowDataToDate)(this._value);
|
|
2691
2668
|
}
|
|
2692
2669
|
get field() {
|
|
2693
2670
|
return this._field;
|
|
@@ -2711,7 +2688,7 @@ class DataDate extends ScalarData {
|
|
|
2711
2688
|
this._field = field;
|
|
2712
2689
|
}
|
|
2713
2690
|
get value() {
|
|
2714
|
-
return
|
|
2691
|
+
return (0, row_data_utils_1.rowDataToDate)(this._value);
|
|
2715
2692
|
}
|
|
2716
2693
|
get field() {
|
|
2717
2694
|
return this._field;
|
|
@@ -2755,6 +2732,161 @@ class DataNull extends Data {
|
|
|
2755
2732
|
return '<null>';
|
|
2756
2733
|
}
|
|
2757
2734
|
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Safe bigint conversion - handles floats that are incorrectly typed as bigint
|
|
2737
|
+
* (e.g., avg() results which should be float but Malloy marks as bigint).
|
|
2738
|
+
*/
|
|
2739
|
+
function safeRowDataToBigint(value) {
|
|
2740
|
+
const strValue = (0, row_data_utils_1.rowDataToSerializedBigint)(value);
|
|
2741
|
+
if (strValue.includes('.') ||
|
|
2742
|
+
strValue.includes('e') ||
|
|
2743
|
+
strValue.includes('E')) {
|
|
2744
|
+
return (0, row_data_utils_1.rowDataToNumber)(value);
|
|
2745
|
+
}
|
|
2746
|
+
try {
|
|
2747
|
+
return BigInt(strValue);
|
|
2748
|
+
}
|
|
2749
|
+
catch {
|
|
2750
|
+
return (0, row_data_utils_1.rowDataToNumber)(value);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Safe bigint serialization - returns number for floats that should stay as numbers.
|
|
2755
|
+
*/
|
|
2756
|
+
function safeRowDataToSerializedBigint(value) {
|
|
2757
|
+
const strValue = (0, row_data_utils_1.rowDataToSerializedBigint)(value);
|
|
2758
|
+
if (strValue.includes('.') ||
|
|
2759
|
+
strValue.includes('e') ||
|
|
2760
|
+
strValue.includes('E')) {
|
|
2761
|
+
return (0, row_data_utils_1.rowDataToNumber)(value);
|
|
2762
|
+
}
|
|
2763
|
+
return strValue;
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Normalizers for toObject() - returns JS native types (number | bigint, Date)
|
|
2767
|
+
*/
|
|
2768
|
+
const OBJECT_NORMALIZERS = {
|
|
2769
|
+
number: row_data_utils_1.rowDataToNumber,
|
|
2770
|
+
bigint: safeRowDataToBigint,
|
|
2771
|
+
date: row_data_utils_1.rowDataToDate,
|
|
2772
|
+
};
|
|
2773
|
+
/**
|
|
2774
|
+
* Normalizers for toJSON() - returns JSON-safe types (number | string, ISO strings)
|
|
2775
|
+
*/
|
|
2776
|
+
const JSON_NORMALIZERS = {
|
|
2777
|
+
number: row_data_utils_1.rowDataToNumber,
|
|
2778
|
+
bigint: safeRowDataToSerializedBigint,
|
|
2779
|
+
date: (value) => (0, row_data_utils_1.rowDataToDate)(value).toISOString(),
|
|
2780
|
+
};
|
|
2781
|
+
/**
|
|
2782
|
+
* Walk a QueryData array and normalize values according to the given normalizers.
|
|
2783
|
+
*/
|
|
2784
|
+
function walkQueryData(data, structDef, normalizers) {
|
|
2785
|
+
return data.map(row => walkQueryDataRow(row, structDef, normalizers));
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* Walk a QueryDataRow and normalize values according to the given normalizers.
|
|
2789
|
+
*/
|
|
2790
|
+
function walkQueryDataRow(row, structDef, normalizers) {
|
|
2791
|
+
var _a;
|
|
2792
|
+
const result = {};
|
|
2793
|
+
for (const fieldDef of structDef.fields) {
|
|
2794
|
+
const fieldName = (_a = fieldDef.as) !== null && _a !== void 0 ? _a : fieldDef.name;
|
|
2795
|
+
const value = row[fieldName];
|
|
2796
|
+
result[fieldName] = walkValue(value, fieldDef, normalizers);
|
|
2797
|
+
}
|
|
2798
|
+
return result;
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* Normalize a single value based on its field definition.
|
|
2802
|
+
*/
|
|
2803
|
+
function walkValue(value, fieldDef, normalizers) {
|
|
2804
|
+
if (value === null || value === undefined) {
|
|
2805
|
+
return null;
|
|
2806
|
+
}
|
|
2807
|
+
// Handle scalar types
|
|
2808
|
+
if (fieldDef.type === 'number') {
|
|
2809
|
+
const numberDef = fieldDef;
|
|
2810
|
+
if (numberDef.numberType === 'bigint') {
|
|
2811
|
+
return normalizers.bigint(value);
|
|
2812
|
+
}
|
|
2813
|
+
return normalizers.number(value);
|
|
2814
|
+
}
|
|
2815
|
+
if (fieldDef.type === 'date' ||
|
|
2816
|
+
fieldDef.type === 'timestamp' ||
|
|
2817
|
+
fieldDef.type === 'timestamptz') {
|
|
2818
|
+
return normalizers.date(value);
|
|
2819
|
+
}
|
|
2820
|
+
if (fieldDef.type === 'string' ||
|
|
2821
|
+
fieldDef.type === 'boolean' ||
|
|
2822
|
+
fieldDef.type === 'json' ||
|
|
2823
|
+
fieldDef.type === 'sql native') {
|
|
2824
|
+
// Pass through as-is (or with minimal conversion for booleans from numbers)
|
|
2825
|
+
if (fieldDef.type === 'boolean' && typeof value === 'number') {
|
|
2826
|
+
return value !== 0;
|
|
2827
|
+
}
|
|
2828
|
+
return value;
|
|
2829
|
+
}
|
|
2830
|
+
// Handle arrays
|
|
2831
|
+
if (fieldDef.type === 'array') {
|
|
2832
|
+
if (!Array.isArray(value)) {
|
|
2833
|
+
return value; // Unexpected, but don't crash
|
|
2834
|
+
}
|
|
2835
|
+
if ((0, model_1.isRepeatedRecord)(fieldDef)) {
|
|
2836
|
+
// Array of records - recurse into each record
|
|
2837
|
+
return value.map(item => walkQueryDataRow(item, fieldDef, normalizers));
|
|
2838
|
+
}
|
|
2839
|
+
else if ((0, model_1.isBasicArray)(fieldDef)) {
|
|
2840
|
+
// Scalar array - normalize each element based on elementTypeDef
|
|
2841
|
+
// Cast needed because QueryValue type doesn't cleanly express scalar arrays
|
|
2842
|
+
const elementType = fieldDef.elementTypeDef;
|
|
2843
|
+
return value.map(item => walkScalarValue(item, elementType, normalizers));
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
// Handle records (non-array)
|
|
2847
|
+
if (fieldDef.type === 'record') {
|
|
2848
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
2849
|
+
return walkQueryDataRow(value, fieldDef, normalizers);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
// Fallback - pass through
|
|
2853
|
+
return value;
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Normalize a scalar value (not in a row context, e.g., elements of a scalar array).
|
|
2857
|
+
*/
|
|
2858
|
+
function walkScalarValue(value, typeDef, normalizers) {
|
|
2859
|
+
if (value === null || value === undefined) {
|
|
2860
|
+
return null;
|
|
2861
|
+
}
|
|
2862
|
+
if (typeDef.type === 'number') {
|
|
2863
|
+
const numberDef = typeDef;
|
|
2864
|
+
if (numberDef.numberType === 'bigint') {
|
|
2865
|
+
return normalizers.bigint(value);
|
|
2866
|
+
}
|
|
2867
|
+
return normalizers.number(value);
|
|
2868
|
+
}
|
|
2869
|
+
if (typeDef.type === 'date' ||
|
|
2870
|
+
typeDef.type === 'timestamp' ||
|
|
2871
|
+
typeDef.type === 'timestamptz') {
|
|
2872
|
+
return normalizers.date(value);
|
|
2873
|
+
}
|
|
2874
|
+
if (typeDef.type === 'boolean' && typeof value === 'number') {
|
|
2875
|
+
return value !== 0;
|
|
2876
|
+
}
|
|
2877
|
+
// Handle nested arrays (array of arrays)
|
|
2878
|
+
if (typeDef.type === 'array' && Array.isArray(value)) {
|
|
2879
|
+
if ((0, model_1.isBasicArray)(typeDef)) {
|
|
2880
|
+
const elementType = typeDef.elementTypeDef;
|
|
2881
|
+
return value.map(item => walkScalarValue(item, elementType, normalizers));
|
|
2882
|
+
}
|
|
2883
|
+
else if ((0, model_1.isRepeatedRecord)(typeDef)) {
|
|
2884
|
+
return value.map(item => walkQueryDataRow(item, typeDef, normalizers));
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
// Pass through other types
|
|
2888
|
+
return value;
|
|
2889
|
+
}
|
|
2758
2890
|
class DataArray extends Data {
|
|
2759
2891
|
constructor(queryData, field, parent, parentRecord) {
|
|
2760
2892
|
super(field, parent, parentRecord);
|
|
@@ -2769,11 +2901,27 @@ class DataArray extends Data {
|
|
|
2769
2901
|
return this._field;
|
|
2770
2902
|
}
|
|
2771
2903
|
/**
|
|
2772
|
-
* @return The raw
|
|
2904
|
+
* @return The raw query data as returned by the database driver.
|
|
2905
|
+
* Values may be in various formats depending on the driver (wrapper objects, strings, etc.).
|
|
2906
|
+
* Use this for passing to mapData() which handles normalization itself.
|
|
2773
2907
|
*/
|
|
2774
|
-
|
|
2908
|
+
get rawData() {
|
|
2775
2909
|
return this.queryData;
|
|
2776
2910
|
}
|
|
2911
|
+
/**
|
|
2912
|
+
* @return Normalized data with JS native types (number | bigint, Date).
|
|
2913
|
+
* Use this for CSV output, tests, and general programmatic access.
|
|
2914
|
+
*/
|
|
2915
|
+
toObject() {
|
|
2916
|
+
return walkQueryData(this.queryData, this._field.structDef, OBJECT_NORMALIZERS);
|
|
2917
|
+
}
|
|
2918
|
+
/**
|
|
2919
|
+
* @return Normalized data with JSON-safe types (numbers as number | string, dates as ISO strings).
|
|
2920
|
+
* Use this for JSON serialization.
|
|
2921
|
+
*/
|
|
2922
|
+
toJSON() {
|
|
2923
|
+
return walkQueryData(this.queryData, this._field.structDef, JSON_NORMALIZERS);
|
|
2924
|
+
}
|
|
2777
2925
|
path(...path) {
|
|
2778
2926
|
return getPath(this, path);
|
|
2779
2927
|
}
|
|
@@ -2784,7 +2932,6 @@ class DataArray extends Data {
|
|
|
2784
2932
|
this.rowCache.set(index, record);
|
|
2785
2933
|
}
|
|
2786
2934
|
return record;
|
|
2787
|
-
return new DataRecord(this.queryData[index], index, this.field, this, this.parentRecord);
|
|
2788
2935
|
}
|
|
2789
2936
|
get rowCount() {
|
|
2790
2937
|
return this.queryData.length;
|
|
@@ -2833,8 +2980,19 @@ class DataRecord extends Data {
|
|
|
2833
2980
|
this._field = field;
|
|
2834
2981
|
this.index = index;
|
|
2835
2982
|
}
|
|
2983
|
+
/**
|
|
2984
|
+
* @return Normalized data with JS native types (number | bigint, Date).
|
|
2985
|
+
* Use this for CSV output, tests, and general programmatic access.
|
|
2986
|
+
*/
|
|
2836
2987
|
toObject() {
|
|
2837
|
-
return this.queryDataRow;
|
|
2988
|
+
return walkQueryDataRow(this.queryDataRow, this._field.structDef, OBJECT_NORMALIZERS);
|
|
2989
|
+
}
|
|
2990
|
+
/**
|
|
2991
|
+
* @return Normalized data with JSON-safe types (numbers as number | string, dates as ISO strings).
|
|
2992
|
+
* Use this for JSON serialization.
|
|
2993
|
+
*/
|
|
2994
|
+
toJSON() {
|
|
2995
|
+
return walkQueryDataRow(this.queryDataRow, this._field.structDef, JSON_NORMALIZERS);
|
|
2838
2996
|
}
|
|
2839
2997
|
path(...path) {
|
|
2840
2998
|
return getPath(this, path);
|
|
@@ -2929,7 +3087,8 @@ class JSONWriter extends DataWriter {
|
|
|
2929
3087
|
if (row.index !== undefined && row.index > 0) {
|
|
2930
3088
|
this.stream.write(',\n');
|
|
2931
3089
|
}
|
|
2932
|
-
|
|
3090
|
+
// toJSON() returns JSON-safe values: bigints as strings, dates as ISO strings
|
|
3091
|
+
const json = JSON.stringify(row.toJSON(), null, 2);
|
|
2933
3092
|
const jsonLines = json.split('\n');
|
|
2934
3093
|
for (let i = 0; i < jsonLines.length; i++) {
|
|
2935
3094
|
const line = jsonLines[i];
|
|
@@ -2991,6 +3150,10 @@ class CSVWriter extends DataWriter {
|
|
|
2991
3150
|
else if (typeof value === 'boolean' || typeof value === 'number') {
|
|
2992
3151
|
return JSON.stringify(value);
|
|
2993
3152
|
}
|
|
3153
|
+
else if (typeof value === 'bigint') {
|
|
3154
|
+
// Bigints from toObject() - write as unquoted number string
|
|
3155
|
+
return value.toString();
|
|
3156
|
+
}
|
|
2994
3157
|
else {
|
|
2995
3158
|
return `${value}`;
|
|
2996
3159
|
}
|