@malloydata/malloy 0.0.326 → 0.0.327
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 +36 -0
- package/dist/dialect/dialect.js +28 -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 +37 -15
- package/dist/dialect/standardsql/standardsql.d.ts +2 -1
- package/dist/dialect/standardsql/standardsql.js +9 -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 +14 -1
- package/dist/lang/ast/expressions/expr-number.js +71 -6
- 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 +7 -0
- package/dist/test/index.js +16 -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
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
|
}
|
|
@@ -427,7 +427,7 @@ export interface StringTypeDef {
|
|
|
427
427
|
export type StringFieldDef = StringTypeDef & AtomicFieldDef;
|
|
428
428
|
export interface NumberTypeDef {
|
|
429
429
|
type: 'number';
|
|
430
|
-
numberType?: 'integer' | 'float';
|
|
430
|
+
numberType?: 'integer' | 'float' | 'bigint';
|
|
431
431
|
}
|
|
432
432
|
export type NumberFieldDef = NumberTypeDef & AtomicFieldDef;
|
|
433
433
|
export interface BooleanTypeDef {
|
|
@@ -923,7 +923,7 @@ export interface Note {
|
|
|
923
923
|
export interface ModelAnnotation extends Annotation {
|
|
924
924
|
id: string;
|
|
925
925
|
}
|
|
926
|
-
export type QueryScalar = string | boolean | number | Date | Buffer | null;
|
|
926
|
+
export type QueryScalar = string | boolean | number | bigint | Date | Buffer | null;
|
|
927
927
|
/** One value in one column of returned data. */
|
|
928
928
|
export type QueryValue = QueryScalar | QueryData | QueryDataRow;
|
|
929
929
|
/** A row of returned data. */
|
|
@@ -14,6 +14,7 @@ const stage_writer_1 = require("./stage_writer");
|
|
|
14
14
|
const dialect_1 = require("../dialect");
|
|
15
15
|
const utils_1 = require("./materialization/utils");
|
|
16
16
|
const query_node_1 = require("./query_node");
|
|
17
|
+
const row_data_utils_1 = require("../api/row_data_utils");
|
|
17
18
|
function makeQueryModel(modelDef, eventStream) {
|
|
18
19
|
return new QueryModelImpl(modelDef, eventStream);
|
|
19
20
|
}
|
|
@@ -252,7 +253,10 @@ class QueryModelImpl {
|
|
|
252
253
|
const result = await connection.runSQL(query, {
|
|
253
254
|
rowLimit: 1000,
|
|
254
255
|
});
|
|
255
|
-
return result.rows
|
|
256
|
+
return result.rows.map(row => ({
|
|
257
|
+
...row,
|
|
258
|
+
weight: (0, row_data_utils_1.rowDataToNumber)(row['weight']),
|
|
259
|
+
}));
|
|
256
260
|
}
|
|
257
261
|
}
|
|
258
262
|
exports.QueryModelImpl = QueryModelImpl;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type * as Malloy from '@malloydata/malloy-interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* Convert Malloy.Data (array of record cells) to an array of plain JS objects.
|
|
4
|
+
* This is the inverse of mapData() in util.ts.
|
|
5
|
+
*/
|
|
6
|
+
export declare function cellsToObjects(data: Malloy.Data, schema: Malloy.Schema): Record<string, unknown>[];
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.cellsToObjects = cellsToObjects;
|
|
10
|
+
/**
|
|
11
|
+
* Convert a Malloy.Cell to a plain JS value.
|
|
12
|
+
* - null_cell → null
|
|
13
|
+
* - string_cell → string
|
|
14
|
+
* - number_cell → number
|
|
15
|
+
* - boolean_cell → boolean
|
|
16
|
+
* - date_cell → string (ISO date format)
|
|
17
|
+
* - timestamp_cell → string (ISO timestamp format)
|
|
18
|
+
* - json_cell → parsed JSON value
|
|
19
|
+
* - sql_native_cell → parsed JSON value
|
|
20
|
+
* - array_cell → array of converted values
|
|
21
|
+
* - record_cell → object with field names from schema
|
|
22
|
+
*/
|
|
23
|
+
function cellToValue(cell, fieldInfo) {
|
|
24
|
+
switch (cell.kind) {
|
|
25
|
+
case 'null_cell':
|
|
26
|
+
return null;
|
|
27
|
+
case 'string_cell':
|
|
28
|
+
return cell.string_value;
|
|
29
|
+
case 'number_cell':
|
|
30
|
+
return cell.number_value;
|
|
31
|
+
case 'big_number_cell':
|
|
32
|
+
return BigInt(cell.number_value);
|
|
33
|
+
case 'boolean_cell':
|
|
34
|
+
return cell.boolean_value;
|
|
35
|
+
case 'date_cell':
|
|
36
|
+
return cell.date_value;
|
|
37
|
+
case 'timestamp_cell':
|
|
38
|
+
return cell.timestamp_value;
|
|
39
|
+
case 'json_cell':
|
|
40
|
+
return JSON.parse(cell.json_value);
|
|
41
|
+
case 'sql_native_cell':
|
|
42
|
+
return JSON.parse(cell.sql_native_value);
|
|
43
|
+
case 'array_cell': {
|
|
44
|
+
if (fieldInfo.kind !== 'dimension') {
|
|
45
|
+
throw new Error(`Expected dimension for array, got ${fieldInfo.kind}`);
|
|
46
|
+
}
|
|
47
|
+
const type = fieldInfo.type;
|
|
48
|
+
if (type.kind !== 'array_type') {
|
|
49
|
+
throw new Error(`Expected array_type, got ${type.kind}`);
|
|
50
|
+
}
|
|
51
|
+
const elementFieldInfo = {
|
|
52
|
+
kind: 'dimension',
|
|
53
|
+
name: 'element',
|
|
54
|
+
type: type.element_type,
|
|
55
|
+
};
|
|
56
|
+
return cell.array_value.map(c => cellToValue(c, elementFieldInfo));
|
|
57
|
+
}
|
|
58
|
+
case 'record_cell': {
|
|
59
|
+
// Records can come from either a join or a dimension with record_type
|
|
60
|
+
// record_type.fields returns DimensionInfo[], schema.fields returns FieldInfo[]
|
|
61
|
+
let fields;
|
|
62
|
+
let getFieldInfo;
|
|
63
|
+
if (fieldInfo.kind === 'join') {
|
|
64
|
+
fields = fieldInfo.schema.fields;
|
|
65
|
+
getFieldInfo = i => fieldInfo.schema.fields[i];
|
|
66
|
+
}
|
|
67
|
+
else if (fieldInfo.kind === 'dimension' &&
|
|
68
|
+
fieldInfo.type.kind === 'record_type') {
|
|
69
|
+
// Capture the record type to help TypeScript narrow
|
|
70
|
+
const recordType = fieldInfo.type;
|
|
71
|
+
fields = recordType.fields;
|
|
72
|
+
// DimensionInfo needs to be wrapped as a FieldInfo
|
|
73
|
+
getFieldInfo = i => ({
|
|
74
|
+
kind: 'dimension',
|
|
75
|
+
name: recordType.fields[i].name,
|
|
76
|
+
type: recordType.fields[i].type,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
throw new Error(`Expected join or dimension with record_type for record, got ${fieldInfo.kind}`);
|
|
81
|
+
}
|
|
82
|
+
const result = {};
|
|
83
|
+
for (let i = 0; i < fields.length; i++) {
|
|
84
|
+
result[fields[i].name] = cellToValue(cell.record_value[i], getFieldInfo(i));
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`Unknown cell kind: ${cell.kind}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Convert Malloy.Data (array of record cells) to an array of plain JS objects.
|
|
94
|
+
* This is the inverse of mapData() in util.ts.
|
|
95
|
+
*/
|
|
96
|
+
function cellsToObjects(data, schema) {
|
|
97
|
+
if (data.kind !== 'array_cell') {
|
|
98
|
+
throw new Error(`Expected array_cell at root, got ${data.kind}`);
|
|
99
|
+
}
|
|
100
|
+
const rootFieldInfo = {
|
|
101
|
+
kind: 'join',
|
|
102
|
+
name: 'root',
|
|
103
|
+
relationship: 'one',
|
|
104
|
+
schema,
|
|
105
|
+
};
|
|
106
|
+
return data.array_value.map(cell => {
|
|
107
|
+
const result = cellToValue(cell, rootFieldInfo);
|
|
108
|
+
return result;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=cellsToObject.js.map
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export { TV } from './test-values';
|
|
2
|
+
export type { TypedValue } from './test-values';
|
|
3
|
+
export { mkTestModel, wrapTestModel, extendTestModel } from './test-models';
|
|
4
|
+
export type { TestModelSources, TestModel } from './test-models';
|
|
5
|
+
export { runQuery } from './runQuery';
|
|
6
|
+
export type { QueryResult } from './runQuery';
|
|
7
|
+
export type { ExpectedRow, MatcherOptions } from './resultMatchers';
|
|
1
8
|
/**
|
|
2
9
|
* Accepts databases in env, either via comma-separated dialect list
|
|
3
10
|
* (MALLOY_DATABASES=) or a single database (MALLOY_DATABASE=). returns either
|
package/dist/test/index.js
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
* Copyright
|
|
4
|
-
*
|
|
5
|
-
* Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
-
* a copy of this software and associated documentation files
|
|
7
|
-
* (the "Software"), to deal in the Software without restriction,
|
|
8
|
-
* including without limitation the rights to use, copy, modify, merge,
|
|
9
|
-
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
10
|
-
* and to permit persons to whom the Software is furnished to do so,
|
|
11
|
-
* subject to the following conditions:
|
|
12
|
-
*
|
|
13
|
-
* The above copyright notice and this permission notice shall be
|
|
14
|
-
* included in all copies or substantial portions of the Software.
|
|
15
|
-
*
|
|
16
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
-
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
-
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
-
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
-
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
23
5
|
*/
|
|
24
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.runQuery = exports.extendTestModel = exports.wrapTestModel = exports.mkTestModel = exports.TV = void 0;
|
|
25
8
|
exports.databasesFromEnvironmentOr = databasesFromEnvironmentOr;
|
|
26
9
|
exports.describeIfDatabaseAvailable = describeIfDatabaseAvailable;
|
|
27
10
|
exports.brokenIn = brokenIn;
|
|
11
|
+
// Test data creation
|
|
12
|
+
var test_values_1 = require("./test-values");
|
|
13
|
+
Object.defineProperty(exports, "TV", { enumerable: true, get: function () { return test_values_1.TV; } });
|
|
14
|
+
var test_models_1 = require("./test-models");
|
|
15
|
+
Object.defineProperty(exports, "mkTestModel", { enumerable: true, get: function () { return test_models_1.mkTestModel; } });
|
|
16
|
+
Object.defineProperty(exports, "wrapTestModel", { enumerable: true, get: function () { return test_models_1.wrapTestModel; } });
|
|
17
|
+
Object.defineProperty(exports, "extendTestModel", { enumerable: true, get: function () { return test_models_1.extendTestModel; } });
|
|
18
|
+
// Query execution
|
|
19
|
+
var runQuery_1 = require("./runQuery");
|
|
20
|
+
Object.defineProperty(exports, "runQuery", { enumerable: true, get: function () { return runQuery_1.runQuery; } });
|
|
21
|
+
/*
|
|
22
|
+
* Legacy exports - kept for backwards compatibility
|
|
23
|
+
*/
|
|
28
24
|
/**
|
|
29
25
|
* Accepts databases in env, either via comma-separated dialect list
|
|
30
26
|
* (MALLOY_DATABASES=) or a single database (MALLOY_DATABASE=). returns either
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import this module to register Jest matchers (toMatchResult, toEqualResult).
|
|
3
|
+
*
|
|
4
|
+
* Usage in jest.config.js:
|
|
5
|
+
* setupFilesAfterEnv: ['@malloydata/malloy/test/matchers']
|
|
6
|
+
*
|
|
7
|
+
* Or import directly in a test file:
|
|
8
|
+
* import '@malloydata/malloy/test/matchers';
|
|
9
|
+
*/
|
|
10
|
+
import './resultMatchers';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
/**
|
|
8
|
+
* Import this module to register Jest matchers (toMatchResult, toEqualResult).
|
|
9
|
+
*
|
|
10
|
+
* Usage in jest.config.js:
|
|
11
|
+
* setupFilesAfterEnv: ['@malloydata/malloy/test/matchers']
|
|
12
|
+
*
|
|
13
|
+
* Or import directly in a test file:
|
|
14
|
+
* import '@malloydata/malloy/test/matchers';
|
|
15
|
+
*/
|
|
16
|
+
require("./resultMatchers");
|
|
17
|
+
//# sourceMappingURL=matchers.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TestModel } from './test-models';
|
|
2
|
+
/** Expected row shape for result matching */
|
|
3
|
+
export type ExpectedRow = Record<string, unknown>;
|
|
4
|
+
/** Options for result matchers */
|
|
5
|
+
export interface MatcherOptions {
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare global {
|
|
9
|
+
namespace jest {
|
|
10
|
+
interface Matchers<R> {
|
|
11
|
+
/**
|
|
12
|
+
* Partial matching - extra fields and rows allowed.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* await expect(query).toMatchResult(tm, {name: 'alice'});
|
|
16
|
+
* await expect(query).toMatchResult(tm, {name: 'alice'}, {name: 'bob'});
|
|
17
|
+
*/
|
|
18
|
+
toMatchResult(tm: TestModel, ...rowsOrOptions: (ExpectedRow | MatcherOptions)[]): Promise<R>;
|
|
19
|
+
/**
|
|
20
|
+
* Exact matching - exact fields and exact row count.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* await expect(query).toEqualResult(tm, [{name: 'alice'}]);
|
|
24
|
+
*/
|
|
25
|
+
toEqualResult(tm: TestModel, rows: ExpectedRow[], options?: MatcherOptions): Promise<R>;
|
|
26
|
+
/**
|
|
27
|
+
* Partial fields but exact row count.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* await expect(query).toMatchRows(tm, [{name: 'alice'}, {name: 'bob'}]);
|
|
31
|
+
*/
|
|
32
|
+
toMatchRows(tm: TestModel, rows: ExpectedRow[], options?: MatcherOptions): Promise<R>;
|
|
33
|
+
/**
|
|
34
|
+
* Check nested values via dotted paths. Takes first element at each array level.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* expect(result.data[0]).toHavePath({'by_state.state': 'TX'});
|
|
38
|
+
*/
|
|
39
|
+
toHavePath(paths: Record<string, unknown>): R;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|