@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.
Files changed (51) hide show
  1. package/dist/api/core.js +2 -0
  2. package/dist/api/index.d.ts +1 -0
  3. package/dist/api/index.js +1 -0
  4. package/dist/api/row_data_utils.d.ts +30 -0
  5. package/dist/api/row_data_utils.js +87 -0
  6. package/dist/api/util.js +20 -38
  7. package/dist/dialect/dialect.d.ts +35 -0
  8. package/dist/dialect/dialect.js +47 -1
  9. package/dist/dialect/duckdb/duckdb.d.ts +2 -1
  10. package/dist/dialect/duckdb/duckdb.js +12 -3
  11. package/dist/dialect/mysql/mysql.js +18 -4
  12. package/dist/dialect/pg_impl.d.ts +1 -0
  13. package/dist/dialect/pg_impl.js +2 -0
  14. package/dist/dialect/postgres/postgres.js +4 -1
  15. package/dist/dialect/snowflake/snowflake.d.ts +2 -1
  16. package/dist/dialect/snowflake/snowflake.js +36 -15
  17. package/dist/dialect/standardsql/standardsql.d.ts +2 -1
  18. package/dist/dialect/standardsql/standardsql.js +8 -3
  19. package/dist/dialect/trino/trino.js +10 -2
  20. package/dist/lang/ast/expressions/expr-avg.d.ts +5 -0
  21. package/dist/lang/ast/expressions/expr-avg.js +9 -0
  22. package/dist/lang/ast/expressions/expr-coalesce.js +6 -0
  23. package/dist/lang/ast/expressions/expr-number.d.ts +10 -1
  24. package/dist/lang/ast/expressions/expr-number.js +23 -7
  25. package/dist/lang/ast/expressions/expr-props.d.ts +1 -1
  26. package/dist/lang/ast/expressions/pick-when.js +10 -3
  27. package/dist/lang/ast/types/expression-def.js +36 -2
  28. package/dist/lang/parse-log.d.ts +1 -0
  29. package/dist/lang/test/test-translator.js +2 -0
  30. package/dist/malloy.d.ts +23 -2
  31. package/dist/malloy.js +204 -41
  32. package/dist/model/malloy_types.d.ts +2 -2
  33. package/dist/model/query_model_impl.js +5 -1
  34. package/dist/test/cellsToObject.d.ts +6 -0
  35. package/dist/test/cellsToObject.js +111 -0
  36. package/dist/test/index.d.ts +47 -0
  37. package/dist/test/index.js +95 -20
  38. package/dist/test/matchers.d.ts +10 -0
  39. package/dist/test/matchers.js +17 -0
  40. package/dist/test/resultMatchers.d.ts +42 -0
  41. package/dist/test/resultMatchers.js +722 -0
  42. package/dist/test/runQuery.d.ts +31 -0
  43. package/dist/test/runQuery.js +67 -0
  44. package/dist/test/test-models.d.ts +77 -0
  45. package/dist/test/test-models.js +319 -0
  46. package/dist/test/test-values.d.ts +66 -0
  47. package/dist/test/test-values.js +208 -0
  48. package/dist/to_stable.js +3 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. 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: 'integer' },
60
- 'INT64': { type: 'number', numberType: 'integer' },
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: 'integer' },
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
- return malloyType.numberType === 'integer' ? 'BIGINT' : 'DOUBLE';
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(_fs: FieldSpace): ExprValue;
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(_fs) {
35
- return this.constantExpression();
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
- return ev1 === undefined || ev1.type === 'null' || ev1.type === 'error'
65
- ? ev2
66
- : ev1;
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: { type: 'number' },
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: { type: 'number' },
496
+ dataType: mergeNumberTypes(num, denom, op),
463
497
  value: divmod,
464
498
  from: [num, denom],
465
499
  });
@@ -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: number, field: NumberField, parent: DataArrayOrRecord | undefined, parentRecord: DataRecord | undefined);
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 object form of the data.
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
- return result._queryResult.result;
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 valueToDate(this._value);
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 valueToDate(this._value);
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 object form of the data.
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
- toObject() {
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
- const json = JSON.stringify(row.toObject(), null, 2);
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
  }