@malloydata/malloy 0.0.123-dev240209030627 → 0.0.123-dev240209210150
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/lang/ast/expressions/expr-func.js +4 -3
- package/dist/lang/test/expressions.spec.js +19 -0
- package/dist/malloy.d.ts +20 -0
- package/dist/malloy.js +144 -34
- package/package.json +1 -1
|
@@ -226,11 +226,12 @@ class ExprFunc extends expression_def_1.ExpressionDef {
|
|
|
226
226
|
if (e.found === undefined) {
|
|
227
227
|
partitionField.log(`${partitionField.refString} is not defined`);
|
|
228
228
|
}
|
|
229
|
-
else if ((0, malloy_types_1.
|
|
230
|
-
|
|
229
|
+
else if ((0, malloy_types_1.expressionIsAnalytic)(e.found.typeDesc().expressionType) ||
|
|
230
|
+
(0, malloy_types_1.expressionIsUngroupedAggregate)(e.found.typeDesc().expressionType)) {
|
|
231
|
+
partitionField.log('Partition expression must be scalar or aggregate');
|
|
231
232
|
}
|
|
232
233
|
else {
|
|
233
|
-
partitionField.
|
|
234
|
+
partitionByFields.push(partitionField.nameString);
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
}
|
|
@@ -215,6 +215,25 @@ describe('expressions', () => {
|
|
|
215
215
|
calculate: x is lag(ai) { partition_by: ai; order_by: ai }
|
|
216
216
|
}
|
|
217
217
|
`).toTranslate();
|
|
218
|
+
});
|
|
219
|
+
test('partition by works with scalar and aggregate', () => {
|
|
220
|
+
expect((0, test_translator_1.markSource) `
|
|
221
|
+
run: a -> {
|
|
222
|
+
group_by: ai
|
|
223
|
+
aggregate: c is count()
|
|
224
|
+
calculate: x is lag(ai) { partition_by: ai, c }
|
|
225
|
+
}
|
|
226
|
+
`).toTranslate();
|
|
227
|
+
});
|
|
228
|
+
test('partition by fails with analytic and ungrouped aggregate', () => {
|
|
229
|
+
expect((0, test_translator_1.markSource) `
|
|
230
|
+
run: a -> {
|
|
231
|
+
group_by: ai
|
|
232
|
+
aggregate: ac is all(count())
|
|
233
|
+
calculate: x is lag(ai) { partition_by: ac }
|
|
234
|
+
calculate: y is lag(ai) { partition_by: x }
|
|
235
|
+
}
|
|
236
|
+
`).translationToFailWith('Partition expression must be scalar or aggregate', 'Partition expression must be scalar or aggregate');
|
|
218
237
|
});
|
|
219
238
|
test('analytics order_by requires expression', () => {
|
|
220
239
|
expect((0, test_translator_1.markSource) `
|
package/dist/malloy.d.ts
CHANGED
|
@@ -1296,13 +1296,33 @@ export declare abstract class DataWriter {
|
|
|
1296
1296
|
export declare class JSONWriter extends DataWriter {
|
|
1297
1297
|
process(data: AsyncIterableIterator<DataRecord>): Promise<void>;
|
|
1298
1298
|
}
|
|
1299
|
+
/**
|
|
1300
|
+
* CSV writer class that handles nested data.
|
|
1301
|
+
* This writer creates CSV using a DFS traversal of the result dataset.
|
|
1302
|
+
* Each trivial column value is converted to a CSV of 1x1 matrix and all the
|
|
1303
|
+
* columns are merged together to create a CSV that represents 1 QueryDataRow.
|
|
1304
|
+
* Since this follows DFS, each non trivial data is rendered into a NxM matrix
|
|
1305
|
+
* where N is the number of rows in the nested data and M is the number of
|
|
1306
|
+
* columns it has.
|
|
1307
|
+
* For any row with X number of columns, we end up with X number of NxM matrices
|
|
1308
|
+
* where the value of N,M pair may be different for each column.
|
|
1309
|
+
* We then merge the matrices so that we end up with a larger matrix of size
|
|
1310
|
+
* Max(N)xSum(M) by taking one row of csv from each matric at a time. For any
|
|
1311
|
+
* matrix with N<Max(N), we add a row of empty CSV cells of size N.
|
|
1312
|
+
*/
|
|
1299
1313
|
export declare class CSVWriter extends DataWriter {
|
|
1300
1314
|
private readonly columnSeparator;
|
|
1301
1315
|
private readonly rowSeparator;
|
|
1302
1316
|
private readonly quoteCharacter;
|
|
1303
1317
|
private readonly includeHeader;
|
|
1318
|
+
private readonly emptyCell;
|
|
1304
1319
|
private escape;
|
|
1305
1320
|
private stringify;
|
|
1321
|
+
private getColWeight;
|
|
1322
|
+
private getHeaderRow;
|
|
1323
|
+
private mergeMatrices;
|
|
1324
|
+
private getChildMatrix;
|
|
1325
|
+
private getRowMatrix;
|
|
1306
1326
|
process(data: AsyncIterableIterator<DataRecord>): Promise<void>;
|
|
1307
1327
|
}
|
|
1308
1328
|
export {};
|
package/dist/malloy.js
CHANGED
|
@@ -2831,6 +2831,20 @@ class JSONWriter extends DataWriter {
|
|
|
2831
2831
|
}
|
|
2832
2832
|
}
|
|
2833
2833
|
exports.JSONWriter = JSONWriter;
|
|
2834
|
+
/**
|
|
2835
|
+
* CSV writer class that handles nested data.
|
|
2836
|
+
* This writer creates CSV using a DFS traversal of the result dataset.
|
|
2837
|
+
* Each trivial column value is converted to a CSV of 1x1 matrix and all the
|
|
2838
|
+
* columns are merged together to create a CSV that represents 1 QueryDataRow.
|
|
2839
|
+
* Since this follows DFS, each non trivial data is rendered into a NxM matrix
|
|
2840
|
+
* where N is the number of rows in the nested data and M is the number of
|
|
2841
|
+
* columns it has.
|
|
2842
|
+
* For any row with X number of columns, we end up with X number of NxM matrices
|
|
2843
|
+
* where the value of N,M pair may be different for each column.
|
|
2844
|
+
* We then merge the matrices so that we end up with a larger matrix of size
|
|
2845
|
+
* Max(N)xSum(M) by taking one row of csv from each matric at a time. For any
|
|
2846
|
+
* matrix with N<Max(N), we add a row of empty CSV cells of size N.
|
|
2847
|
+
*/
|
|
2834
2848
|
class CSVWriter extends DataWriter {
|
|
2835
2849
|
constructor() {
|
|
2836
2850
|
super(...arguments);
|
|
@@ -2838,6 +2852,7 @@ class CSVWriter extends DataWriter {
|
|
|
2838
2852
|
this.rowSeparator = '\n';
|
|
2839
2853
|
this.quoteCharacter = '"';
|
|
2840
2854
|
this.includeHeader = true;
|
|
2855
|
+
this.emptyCell = '';
|
|
2841
2856
|
}
|
|
2842
2857
|
escape(value) {
|
|
2843
2858
|
const hasInnerQuote = value.includes(this.quoteCharacter);
|
|
@@ -2852,50 +2867,145 @@ class CSVWriter extends DataWriter {
|
|
|
2852
2867
|
}
|
|
2853
2868
|
return value;
|
|
2854
2869
|
}
|
|
2855
|
-
stringify
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
else if (cell.isArray() ||
|
|
2860
|
-
cell.isRecord() ||
|
|
2861
|
-
cell.isBoolean() ||
|
|
2862
|
-
cell.isNumber()) {
|
|
2863
|
-
return JSON.stringify(cell.value);
|
|
2870
|
+
// Re-using the old stringify method for sanity.
|
|
2871
|
+
stringify(value) {
|
|
2872
|
+
if (value === null) {
|
|
2873
|
+
return this.emptyCell;
|
|
2864
2874
|
}
|
|
2865
|
-
else if (
|
|
2866
|
-
return
|
|
2875
|
+
else if (value instanceof Date) {
|
|
2876
|
+
return value.toISOString();
|
|
2867
2877
|
}
|
|
2868
|
-
else if (
|
|
2869
|
-
return
|
|
2878
|
+
else if (typeof value === 'boolean' || typeof value === 'number') {
|
|
2879
|
+
return JSON.stringify(value);
|
|
2870
2880
|
}
|
|
2871
2881
|
else {
|
|
2872
|
-
return `${
|
|
2882
|
+
return `${value}`;
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
// Extra weight to be added becase of nested tables inside the cells.
|
|
2886
|
+
getColWeight(jsonVal) {
|
|
2887
|
+
let firstVal = jsonVal;
|
|
2888
|
+
if (Array.isArray(jsonVal)) {
|
|
2889
|
+
firstVal = jsonVal[0];
|
|
2890
|
+
}
|
|
2891
|
+
let numKeys = 0;
|
|
2892
|
+
for (const key in firstVal) {
|
|
2893
|
+
numKeys = numKeys + 1;
|
|
2894
|
+
const val = firstVal[key];
|
|
2895
|
+
if (Array.isArray(val)) {
|
|
2896
|
+
const weight = this.getColWeight(val) - 1;
|
|
2897
|
+
numKeys = numKeys + weight;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
return numKeys;
|
|
2901
|
+
}
|
|
2902
|
+
// Get header row along with extra empty spaces for nested children.
|
|
2903
|
+
getHeaderRow(row) {
|
|
2904
|
+
const csv = [];
|
|
2905
|
+
let width = 0;
|
|
2906
|
+
for (const key in row) {
|
|
2907
|
+
csv.push(this.escape(key));
|
|
2908
|
+
const val = row[key];
|
|
2909
|
+
width++;
|
|
2910
|
+
if (Array.isArray(val)) {
|
|
2911
|
+
const numKeys = this.getColWeight(val) - 1;
|
|
2912
|
+
width = width + numKeys;
|
|
2913
|
+
for (let i = 0; i < numKeys; i++) {
|
|
2914
|
+
csv.push(this.emptyCell);
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
return { rows: [csv.join(this.columnSeparator)], length: 1, width: width };
|
|
2919
|
+
}
|
|
2920
|
+
// Merge the child matrices i.e. merge the columns into one bigger matrix i.e. CSV.
|
|
2921
|
+
mergeMatrices(matrices) {
|
|
2922
|
+
const maxLength = Math.max(...matrices.map(matrix => matrix.length));
|
|
2923
|
+
const matrixWidth = matrices.reduce((sum, matrix) => sum + matrix.width, 0);
|
|
2924
|
+
const csvMatrix = [];
|
|
2925
|
+
for (let i = 0; i < maxLength; i++) {
|
|
2926
|
+
const csvRow = [];
|
|
2927
|
+
for (const matrix of matrices) {
|
|
2928
|
+
if (i < matrix.length) {
|
|
2929
|
+
csvRow.push(matrix.rows[i]);
|
|
2930
|
+
}
|
|
2931
|
+
else {
|
|
2932
|
+
// Add empty cells.
|
|
2933
|
+
const emptyCells = Array(matrix.width).fill(this.emptyCell);
|
|
2934
|
+
csvRow.push(...emptyCells);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
csvMatrix.push(csvRow.join(this.columnSeparator));
|
|
2873
2938
|
}
|
|
2939
|
+
return {
|
|
2940
|
+
rows: csvMatrix,
|
|
2941
|
+
length: maxLength,
|
|
2942
|
+
width: matrixWidth,
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
// Gets CSV for a data cell that has nested data.
|
|
2946
|
+
getChildMatrix(jsonVal) {
|
|
2947
|
+
// This is not expected to happen.
|
|
2948
|
+
if (!Array.isArray(jsonVal)) {
|
|
2949
|
+
return {
|
|
2950
|
+
rows: ['Invalid data found, value is not an array'],
|
|
2951
|
+
length: 1,
|
|
2952
|
+
width: 1,
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
else if (jsonVal.length === 0) {
|
|
2956
|
+
return {
|
|
2957
|
+
rows: [''],
|
|
2958
|
+
length: 1,
|
|
2959
|
+
width: 1,
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
const csvMatrix = [];
|
|
2963
|
+
const header = this.getHeaderRow(jsonVal[0]);
|
|
2964
|
+
// Header has 1 row.
|
|
2965
|
+
csvMatrix.push(...header.rows);
|
|
2966
|
+
const width = header.width;
|
|
2967
|
+
let rowCount = 1;
|
|
2968
|
+
for (const row of jsonVal) {
|
|
2969
|
+
const rowMatrix = this.getRowMatrix(row);
|
|
2970
|
+
rowCount = rowCount + rowMatrix.length;
|
|
2971
|
+
csvMatrix.push(...rowMatrix.rows);
|
|
2972
|
+
}
|
|
2973
|
+
return { rows: csvMatrix, length: rowCount, width: width };
|
|
2974
|
+
}
|
|
2975
|
+
// Creates CSV content for one row of data.
|
|
2976
|
+
getRowMatrix(row) {
|
|
2977
|
+
const matrices = [];
|
|
2978
|
+
for (const key in row) {
|
|
2979
|
+
const val = row[key];
|
|
2980
|
+
if (!Array.isArray(val)) {
|
|
2981
|
+
const cell = {
|
|
2982
|
+
rows: [this.stringify(val)],
|
|
2983
|
+
length: 1,
|
|
2984
|
+
width: 1,
|
|
2985
|
+
};
|
|
2986
|
+
matrices.push(cell);
|
|
2987
|
+
}
|
|
2988
|
+
else {
|
|
2989
|
+
const cell = this.getChildMatrix(val);
|
|
2990
|
+
matrices.push(cell);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
return this.mergeMatrices(matrices);
|
|
2874
2994
|
}
|
|
2875
2995
|
async process(data) {
|
|
2876
|
-
let
|
|
2996
|
+
let headerDefined = false;
|
|
2877
2997
|
for await (const row of data) {
|
|
2878
|
-
if (
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
this.stream.write(this.escape(field.name));
|
|
2884
|
-
if (fieldIndex !== fields.length - 1) {
|
|
2885
|
-
this.stream.write(this.columnSeparator);
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
this.stream.write(this.rowSeparator);
|
|
2889
|
-
}
|
|
2998
|
+
if (!headerDefined && this.includeHeader) {
|
|
2999
|
+
const header = this.getHeaderRow(row.toObject());
|
|
3000
|
+
this.stream.write(header.rows[0]);
|
|
3001
|
+
this.stream.write(this.rowSeparator);
|
|
3002
|
+
headerDefined = true;
|
|
2890
3003
|
}
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
this.stream.write(
|
|
2894
|
-
|
|
2895
|
-
this.stream.write(this.columnSeparator);
|
|
2896
|
-
}
|
|
3004
|
+
const rowCsv = this.getRowMatrix(row.toObject());
|
|
3005
|
+
for (const line of rowCsv.rows) {
|
|
3006
|
+
this.stream.write(line);
|
|
3007
|
+
this.stream.write(this.rowSeparator);
|
|
2897
3008
|
}
|
|
2898
|
-
this.stream.write(this.rowSeparator);
|
|
2899
3009
|
}
|
|
2900
3010
|
this.stream.close();
|
|
2901
3011
|
}
|