@platforma-sdk/workflow-tengo 4.5.3 → 4.6.1
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/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/index.mjs +6 -6
- package/dist/tengo/lib/pframes.bundle.lib.tengo +37 -300
- package/dist/tengo/lib/pframes.lib.tengo +4 -1
- package/dist/tengo/lib/pframes.spec.lib.tengo +21 -1
- package/dist/tengo/lib/pframes.xsv-builder.lib.tengo +625 -0
- package/dist/tengo/lib/pt.expression.lib.tengo +905 -0
- package/dist/tengo/lib/pt.lib.tengo +1000 -0
- package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
- package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
- package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
- package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
- package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
- package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
- package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
- package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
- package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
- package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
- package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
- package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
- package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
- package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
- package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
- package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
- package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
- package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
- package/package.json +3 -2
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
ll := import("@platforma-sdk/workflow-tengo:ll")
|
|
2
|
+
maps := import("@platforma-sdk/workflow-tengo:maps")
|
|
3
|
+
slices := import("@platforma-sdk/workflow-tengo:slices")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_newExpression := undefined
|
|
8
|
+
_newWindowExpression := undefined
|
|
9
|
+
col := undefined
|
|
10
|
+
lit := undefined
|
|
11
|
+
allHorizontal := undefined
|
|
12
|
+
anyHorizontal := undefined
|
|
13
|
+
and := undefined
|
|
14
|
+
or := undefined
|
|
15
|
+
when := undefined
|
|
16
|
+
rank := undefined
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_isExpression := func(item) {
|
|
20
|
+
return ll.isMap(item) && ll.methodExists(item, "getExpression")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_isAggregation := func(item) {
|
|
25
|
+
return ll.isMap(item) && ll.methodExists(item, "getAggregation")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_mapToExpressionStructList := func(itemsList, stringInterpretation) {
|
|
30
|
+
if !is_array(itemsList) {
|
|
31
|
+
itemsList = [itemsList] // Allow single item to be passed
|
|
32
|
+
}
|
|
33
|
+
if stringInterpretation != "col" && stringInterpretation != "lit" {
|
|
34
|
+
ll.panic("Invalid stringInterpretation ('%v') for _mapToExpressionStructList. Must be 'col' or 'lit'.", stringInterpretation)
|
|
35
|
+
return undefined // Unreachable
|
|
36
|
+
}
|
|
37
|
+
return slices.map(itemsList, func(item) {
|
|
38
|
+
if is_string(item) {
|
|
39
|
+
if stringInterpretation == "col" {
|
|
40
|
+
return col(item).getExpression()
|
|
41
|
+
} else { // Must be "lit" due to the check above
|
|
42
|
+
return lit(item).getExpression()
|
|
43
|
+
}
|
|
44
|
+
} else if _isExpression(item) {
|
|
45
|
+
return item.getExpression()
|
|
46
|
+
} else {
|
|
47
|
+
ll.panic("Invalid item type in list: %T. Must be a string (interpreted as '%s') or an expression object.", item, stringInterpretation)
|
|
48
|
+
return undefined // Unreachable
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_mapToSingleExpressionStruct := func(item, stringInterpretation) {
|
|
54
|
+
if _isExpression(item) {
|
|
55
|
+
return item.getExpression()
|
|
56
|
+
} else if is_int(item) || is_float(item) || is_bool(item) { // These are always literals
|
|
57
|
+
return lit(item).getExpression()
|
|
58
|
+
} else if is_string(item) {
|
|
59
|
+
if stringInterpretation == "col" {
|
|
60
|
+
return col(item).getExpression()
|
|
61
|
+
} else if stringInterpretation == "lit" {
|
|
62
|
+
return lit(item).getExpression()
|
|
63
|
+
} else { // stringInterpretation is undefined or an invalid value
|
|
64
|
+
ll.panic("String item '%v' provided, but its interpretation as 'col' or 'lit' was not specified or is not applicable in this context. (stringInterpretation: '%v')", item, stringInterpretation)
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
ll.panic("Invalid item type: %T. Expected an expression, literal (number, bool, null), or string (with 'col'/'lit' interpretation).", item)
|
|
69
|
+
return undefined
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
_newWindowExpression = func(expression, aggregation, currentAlias) {
|
|
80
|
+
self := undefined
|
|
81
|
+
self = ll.toStrict({
|
|
82
|
+
|
|
83
|
+
getExpression: func() {
|
|
84
|
+
if expression == undefined {
|
|
85
|
+
ll.panic("Using this operation as a window expression is not allowed. Use it as a group by expression instead. %v", aggregation)
|
|
86
|
+
}
|
|
87
|
+
return expression
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
getAggregation: func() {
|
|
91
|
+
if aggregation == undefined {
|
|
92
|
+
ll.panic("Using this operation as a group by expression is not allowed, most probably because you applied .over() to it. %v", expression)
|
|
93
|
+
}
|
|
94
|
+
return maps.deepMerge(aggregation, {name: self.getAlias()})
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
getAlias: func() {
|
|
98
|
+
if currentAlias == undefined {
|
|
99
|
+
ll.panic("Alias is not defined for this window expression.")
|
|
100
|
+
}
|
|
101
|
+
return currentAlias
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
alias: func(newAlias) {
|
|
111
|
+
return _newWindowExpression(expression, aggregation, newAlias)
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
over: func(partitionBy) {
|
|
120
|
+
ll.assert(is_array(partitionBy) || is_string(partitionBy) || _isExpression(partitionBy), "partitionBy must be an array of strings/expressions or a single string/expression")
|
|
121
|
+
|
|
122
|
+
if len(expression.partitionBy) > 0 {
|
|
123
|
+
ll.warn("partitionBy already set on this window expression, overriding.")
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
newExpression := maps.deepMerge(expression, {
|
|
127
|
+
partitionBy: _mapToExpressionStructList(partitionBy, "col")
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return _newWindowExpression(newExpression, undefined, currentAlias)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
return self
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
_newExpression = func(expression, currentAlias) {
|
|
142
|
+
self := undefined // forward declaration
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
binaryOp := func(opType, other) {
|
|
146
|
+
rhsExprStruct := undefined
|
|
147
|
+
if _isExpression(other) {
|
|
148
|
+
rhsExprStruct = other.getExpression()
|
|
149
|
+
} else if is_int(other) || is_float(other) || is_bool(other) || is_string(other) {
|
|
150
|
+
rhsExprStruct = lit(other).getExpression()
|
|
151
|
+
} else {
|
|
152
|
+
ll.panic(opType + " expects an expression object or a literal (number, boolean, null) as argument. Got type: %T", other)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return _newExpression(
|
|
156
|
+
{type: opType, lhs: expression, rhs: rhsExprStruct},
|
|
157
|
+
undefined // Result of operation typically doesn't carry forward alias
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
unaryOp := func(opType) {
|
|
163
|
+
return _newExpression(
|
|
164
|
+
{type: opType, value: expression},
|
|
165
|
+
undefined
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
unaryStrOp := func(opType) {
|
|
171
|
+
return _newExpression(
|
|
172
|
+
{type: opType, value: expression},
|
|
173
|
+
undefined
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
aggOp := func(aggType) {
|
|
179
|
+
return _newWindowExpression(
|
|
180
|
+
{type: "aggregate", aggregation: aggType, value: expression, partitionBy: []},
|
|
181
|
+
{aggregation: aggType, expression: expression},
|
|
182
|
+
undefined // Alias of underlying col, window expression itself is typically aliased later
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
aggOpBy := func(aggType, by) {
|
|
187
|
+
return _newWindowExpression(
|
|
188
|
+
undefined,
|
|
189
|
+
{aggregation: aggType, expression: expression, by: _mapToExpressionStructList(by, "col")},
|
|
190
|
+
undefined // Alias of underlying col, window expression itself is typically aliased later
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
self = ll.toStrict({
|
|
195
|
+
|
|
196
|
+
getExpression: func() { return expression },
|
|
197
|
+
|
|
198
|
+
getAlias: func() {
|
|
199
|
+
if currentAlias == undefined {
|
|
200
|
+
ll.panic("Alias is not defined for this expression.")
|
|
201
|
+
}
|
|
202
|
+
return currentAlias
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
alias: func(newAlias) {
|
|
211
|
+
return _newExpression(expression, newAlias)
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
gt: func(other) { return binaryOp("gt", other) },
|
|
217
|
+
|
|
218
|
+
ge: func(other) { return binaryOp("ge", other) },
|
|
219
|
+
|
|
220
|
+
eq: func(other) { return binaryOp("eq", other) },
|
|
221
|
+
|
|
222
|
+
lt: func(other) { return binaryOp("lt", other) },
|
|
223
|
+
|
|
224
|
+
le: func(other) { return binaryOp("le", other) },
|
|
225
|
+
|
|
226
|
+
neq: func(other) { return binaryOp("neq", other) },
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
plus: func(other) { return binaryOp("plus", other) },
|
|
231
|
+
|
|
232
|
+
minus: func(other) { return binaryOp("minus", other) },
|
|
233
|
+
|
|
234
|
+
multiply: func(other) { return binaryOp("multiply", other) },
|
|
235
|
+
|
|
236
|
+
truediv: func(other) { return binaryOp("truediv", other) },
|
|
237
|
+
|
|
238
|
+
floordiv: func(other) { return binaryOp("floordiv", other) },
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
log10: func() { return unaryOp("log10") },
|
|
242
|
+
|
|
243
|
+
log: func() { return unaryOp("log") },
|
|
244
|
+
|
|
245
|
+
log2: func() { return unaryOp("log2") },
|
|
246
|
+
|
|
247
|
+
abs: func() { return unaryOp("abs") },
|
|
248
|
+
|
|
249
|
+
sqrt: func() { return unaryOp("sqrt") },
|
|
250
|
+
|
|
251
|
+
negate: func() { return unaryOp("negate") },
|
|
252
|
+
|
|
253
|
+
floor: func() { return unaryOp("floor") },
|
|
254
|
+
|
|
255
|
+
round: func() { return unaryOp("round") },
|
|
256
|
+
|
|
257
|
+
ceil: func() { return unaryOp("ceil") },
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
cast: func(dtype, ...options) {
|
|
269
|
+
strictVal := false
|
|
270
|
+
if len(options) > 0 {
|
|
271
|
+
ll.assert(len(options) == 1, "cast accepts at most one options map argument.")
|
|
272
|
+
opsMap := options[0]
|
|
273
|
+
ll.assert(is_map(opsMap), "Second argument to cast(), if provided, must be an options map.")
|
|
274
|
+
if !is_undefined(opsMap.strict) {
|
|
275
|
+
ll.assert(is_bool(opsMap.strict), "strict in options map must be a boolean")
|
|
276
|
+
strictVal = opsMap.strict
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return _newExpression(
|
|
280
|
+
{type: "cast", value: expression, dtype: dtype, strict: strictVal},
|
|
281
|
+
undefined
|
|
282
|
+
)
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
and: func(other) {
|
|
288
|
+
ll.assert(_isExpression(other), "and method expects an expression object as argument")
|
|
289
|
+
return allHorizontal(self, other) // Refer to top-level function
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
or: func(other) {
|
|
293
|
+
ll.assert(_isExpression(other), "or method expects an expression object as argument")
|
|
294
|
+
return anyHorizontal(self, other) // Refer to top-level function
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
not: func() {
|
|
298
|
+
return unaryOp("not")
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
isNull: func() { return unaryOp("is_na") }, // Polars: is_null(), TS: is_na
|
|
304
|
+
|
|
305
|
+
isNotNull: func() { return unaryOp("is_not_na") }, // Polars: is_not_null(), TS: is_not_na
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
strToUpper: func() { return unaryStrOp("to_upper") },
|
|
310
|
+
|
|
311
|
+
strToLower: func() { return unaryStrOp("to_lower") },
|
|
312
|
+
|
|
313
|
+
strLenChars: func() { return unaryStrOp("str_len")},
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
strSlice: func(start, ...lengthArgs) { // Primary method with optional length via varargs
|
|
322
|
+
ll.assert(is_int(start), "start must be an integer")
|
|
323
|
+
ll.assert(len(lengthArgs) <= 1, "strSlice expects at most one optional length argument.")
|
|
324
|
+
|
|
325
|
+
exprDetails := {type: "substring", value: expression, start: start}
|
|
326
|
+
if len(lengthArgs) == 1 {
|
|
327
|
+
lengthVal := lengthArgs[0]
|
|
328
|
+
ll.assert(is_int(lengthVal), "length, if provided, must be an integer")
|
|
329
|
+
exprDetails.length = lengthVal
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return _newExpression(exprDetails, undefined)
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
strSliceEnd: func(start, endIdx) { // Method with end index
|
|
342
|
+
ll.assert(is_int(start), "start must be an integer")
|
|
343
|
+
ll.assert(is_int(endIdx), "endIdx must be an integer")
|
|
344
|
+
|
|
345
|
+
exprDetails := {type: "substring", value: expression, start: start, end: endIdx}
|
|
346
|
+
return _newExpression(exprDetails, undefined)
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
strReplace: func(pattern, replacement, ...options) {
|
|
359
|
+
replaceAllVal := false
|
|
360
|
+
literalVal := false
|
|
361
|
+
|
|
362
|
+
if len(options) > 0 {
|
|
363
|
+
ll.assert(len(options) == 1, "strReplace accepts at most one options map argument.")
|
|
364
|
+
opsMap := options[0]
|
|
365
|
+
ll.assert(is_map(opsMap), "Third argument to strReplace(), if provided, must be an options map.")
|
|
366
|
+
if !is_undefined(opsMap.replaceAll) {
|
|
367
|
+
ll.assert(is_bool(opsMap.replaceAll), "replaceAll in options map must be a boolean")
|
|
368
|
+
replaceAllVal = opsMap.replaceAll
|
|
369
|
+
}
|
|
370
|
+
if !is_undefined(opsMap.literal) {
|
|
371
|
+
ll.assert(is_bool(opsMap.literal), "literal in options map must be a boolean")
|
|
372
|
+
literalVal = opsMap.literal
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return _newExpression({
|
|
377
|
+
type: "str_replace",
|
|
378
|
+
value: expression,
|
|
379
|
+
pattern: _mapToSingleExpressionStruct(pattern, "lit"),
|
|
380
|
+
replacement: _mapToSingleExpressionStruct(replacement, "lit"),
|
|
381
|
+
replaceAll: replaceAllVal,
|
|
382
|
+
literal: literalVal
|
|
383
|
+
}, undefined)
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
strDistance: func(string2, metric, returnSimilarity) {
|
|
395
|
+
ll.assert(_isExpression(string2), "string2 must be an expression object")
|
|
396
|
+
ll.assert(is_string(metric), "metric must be a string")
|
|
397
|
+
|
|
398
|
+
returnSimilarityVal := false
|
|
399
|
+
if returnSimilarity != undefined {
|
|
400
|
+
ll.assert(is_bool(returnSimilarity), "returnSimilarity must be a boolean")
|
|
401
|
+
returnSimilarityVal = returnSimilarity
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return _newExpression({
|
|
405
|
+
type: "string_distance",
|
|
406
|
+
string1: expression,
|
|
407
|
+
string2: string2.getExpression(),
|
|
408
|
+
metric: metric,
|
|
409
|
+
returnSimilarity: returnSimilarityVal
|
|
410
|
+
}, undefined)
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
fuzzyFilter: func(patternExpr, metric, bound) {
|
|
422
|
+
ll.assert(_isExpression(patternExpr), "patternExpr must be an expression object")
|
|
423
|
+
ll.assert(is_string(metric), "metric must be a string")
|
|
424
|
+
ll.assert(is_int(bound) || is_float(bound), "bound must be a number")
|
|
425
|
+
|
|
426
|
+
return _newExpression({
|
|
427
|
+
type: "fuzzy_string_filter",
|
|
428
|
+
value: expression,
|
|
429
|
+
pattern: patternExpr.getExpression(),
|
|
430
|
+
metric: metric,
|
|
431
|
+
bound: bound
|
|
432
|
+
}, undefined)
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
hash: func(hashType, encoding, bits) {
|
|
444
|
+
ll.assert(is_string(hashType), "hashType must be a string")
|
|
445
|
+
ll.assert(is_string(encoding), "encoding must be a string")
|
|
446
|
+
if bits != undefined {
|
|
447
|
+
ll.assert(is_int(bits), "bits must be a number")
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
hashExpr := {
|
|
451
|
+
type: "hash",
|
|
452
|
+
value: expression,
|
|
453
|
+
hashType: hashType,
|
|
454
|
+
encoding: encoding
|
|
455
|
+
}
|
|
456
|
+
if bits != undefined {
|
|
457
|
+
hashExpr.bits = bits
|
|
458
|
+
}
|
|
459
|
+
return _newExpression(hashExpr, undefined)
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
fillNull: func(fillValueExpr) {
|
|
469
|
+
return _newExpression(
|
|
470
|
+
{type: "fill_na", input: expression, fillValue: _mapToSingleExpressionStruct(fillValueExpr, "lit")},
|
|
471
|
+
undefined
|
|
472
|
+
)
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
sum: func() { return aggOp("sum") },
|
|
478
|
+
|
|
479
|
+
mean: func() { return aggOp("mean") },
|
|
480
|
+
|
|
481
|
+
median: func() { return aggOp("median") },
|
|
482
|
+
|
|
483
|
+
min: func() { return aggOp("min") },
|
|
484
|
+
|
|
485
|
+
max: func() { return aggOp("max") },
|
|
486
|
+
|
|
487
|
+
std: func() { return aggOp("std") },
|
|
488
|
+
|
|
489
|
+
var: func() { return aggOp("var") },
|
|
490
|
+
|
|
491
|
+
count: func() { return aggOp("count") },
|
|
492
|
+
|
|
493
|
+
first: func() { return aggOp("first") },
|
|
494
|
+
|
|
495
|
+
last: func() { return aggOp("last") },
|
|
496
|
+
|
|
497
|
+
nUnique: func() { return aggOp("n_unique") },
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
maxBy: func(...by) { return aggOpBy("max_by", by) },
|
|
501
|
+
|
|
502
|
+
minBy: func(...by) { return aggOpBy("min_by", by) },
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
rank: func(...descendingArgs) {
|
|
511
|
+
ll.assert(len(descendingArgs) <= 1, "rank method expects at most one optional boolean argument for descending.")
|
|
512
|
+
descendingVal := false
|
|
513
|
+
if len(descendingArgs) == 1 {
|
|
514
|
+
ll.assert(is_bool(descendingArgs[0]), "descending argument for rank must be a boolean")
|
|
515
|
+
descendingVal = descendingArgs[0]
|
|
516
|
+
}
|
|
517
|
+
return _newWindowExpression({
|
|
518
|
+
type: "rank",
|
|
519
|
+
orderBy: [expression],
|
|
520
|
+
partitionBy: [], // To be set by .over()
|
|
521
|
+
descending: descendingVal
|
|
522
|
+
}, undefined)
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
cumsum: func(...args) {
|
|
537
|
+
additionalOrderByVal := []
|
|
538
|
+
descendingVal := false
|
|
539
|
+
actualArgs := args
|
|
540
|
+
|
|
541
|
+
if len(args) > 0 && is_map(args[len(args)-1]) && !(_isExpression(args[len(args)-1])) {
|
|
542
|
+
ops := args[len(args)-1]
|
|
543
|
+
actualArgs = args[0:len(args)-1]
|
|
544
|
+
if maps.containsKey(ops, "orderBy") {
|
|
545
|
+
additionalOrderByVal = _mapToExpressionStructList(ops.orderBy, "col")
|
|
546
|
+
}
|
|
547
|
+
if maps.containsKey(ops, "descending") {
|
|
548
|
+
ll.assert(is_bool(ops.descending), "descending in options map must be a boolean")
|
|
549
|
+
descendingVal = ops.descending
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
if len(additionalOrderByVal) == 0 && len(actualArgs) > 0 {
|
|
555
|
+
if is_array(actualArgs[0]) || is_string(actualArgs[0]) || _isExpression(actualArgs[0]) {
|
|
556
|
+
additionalOrderByVal = _mapToExpressionStructList(actualArgs[0], "col")
|
|
557
|
+
if len(actualArgs) > 1 && is_bool(actualArgs[1]) {
|
|
558
|
+
descendingVal = actualArgs[1]
|
|
559
|
+
}
|
|
560
|
+
} else if is_bool(actualArgs[0]) && len(additionalOrderByVal) == 0 { // only if not set by ops.orderBy
|
|
561
|
+
descendingVal = actualArgs[0]
|
|
562
|
+
} else if len(actualArgs) > 0 {
|
|
563
|
+
ll.panic("Invalid arguments to cumsum. Expected order expressions or options map.")
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return _newWindowExpression({
|
|
568
|
+
type: "cumsum",
|
|
569
|
+
value: expression,
|
|
570
|
+
additionalOrderBy: additionalOrderByVal,
|
|
571
|
+
partitionBy: [],
|
|
572
|
+
descending: descendingVal
|
|
573
|
+
}, undefined, undefined)
|
|
574
|
+
}
|
|
575
|
+
})
|
|
576
|
+
return self
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
col = func(name) {
|
|
589
|
+
ll.assert(is_string(name), "col name must be a string")
|
|
590
|
+
return _newExpression({
|
|
591
|
+
type: "col",
|
|
592
|
+
name: name
|
|
593
|
+
}, name)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
lit = func(value) {
|
|
602
|
+
return _newExpression({
|
|
603
|
+
type: "const",
|
|
604
|
+
value: value
|
|
605
|
+
}, undefined)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
concatStr := func(expressions, ...options) {
|
|
616
|
+
ll.assert(is_array(expressions), "First argument to concatStr must be an array of expressions")
|
|
617
|
+
ll.assert(len(expressions) > 0, "Expression array for concatStr cannot be empty")
|
|
618
|
+
|
|
619
|
+
delimiterVal := "" // Default to empty string
|
|
620
|
+
if len(options) > 0 {
|
|
621
|
+
ll.assert(len(options) == 1, "concatStr accepts at most one options map argument.")
|
|
622
|
+
opsMap := options[0]
|
|
623
|
+
ll.assert(is_map(opsMap), "Second argument to concatStr(), if provided, must be an options map.")
|
|
624
|
+
if !is_undefined(opsMap.delimiter) {
|
|
625
|
+
ll.assert(is_string(opsMap.delimiter), "delimiter in options map must be a string")
|
|
626
|
+
delimiterVal = opsMap.delimiter
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return _newExpression({
|
|
631
|
+
type: "str_join",
|
|
632
|
+
operands: _mapToExpressionStructList(expressions, "lit"),
|
|
633
|
+
delimiter: delimiterVal
|
|
634
|
+
}, undefined)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
minHorizontal := func(expressions) {
|
|
643
|
+
ll.assert(is_array(expressions), "Argument to minHorizontal must be an array of expressions")
|
|
644
|
+
ll.assert(len(expressions) > 0, "Expression array for minHorizontal cannot be empty")
|
|
645
|
+
return _newExpression({
|
|
646
|
+
type: "min",
|
|
647
|
+
operands: _mapToExpressionStructList(expressions, "col")
|
|
648
|
+
}, undefined)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
maxHorizontal := func(expressions) {
|
|
657
|
+
ll.assert(is_array(expressions), "Argument to maxHorizontal must be an array of expressions")
|
|
658
|
+
ll.assert(len(expressions) > 0, "Expression array for maxHorizontal cannot be empty")
|
|
659
|
+
return _newExpression({
|
|
660
|
+
type: "max",
|
|
661
|
+
operands: _mapToExpressionStructList(expressions, "col")
|
|
662
|
+
}, undefined)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
allHorizontal = func(...args){
|
|
671
|
+
processedOperands := []
|
|
672
|
+
for arg in args {
|
|
673
|
+
exprStruct := undefined
|
|
674
|
+
if _isExpression(arg) {
|
|
675
|
+
exprStruct = arg.getExpression()
|
|
676
|
+
} else if is_string(arg) {
|
|
677
|
+
exprStruct = col(arg).getExpression()
|
|
678
|
+
} else {
|
|
679
|
+
ll.panic("Invalid argument type for allHorizontal: %T. Must be an expression object or string.", arg)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if exprStruct.type == "and" && is_array(exprStruct.operands) {
|
|
683
|
+
processedOperands = append(processedOperands, exprStruct.operands...)
|
|
684
|
+
} else {
|
|
685
|
+
processedOperands = append(processedOperands, exprStruct)
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if len(processedOperands) == 0 {
|
|
689
|
+
ll.panic("allHorizontal requires at least one expression.")
|
|
690
|
+
}
|
|
691
|
+
return _newExpression({
|
|
692
|
+
type: "and",
|
|
693
|
+
operands: processedOperands
|
|
694
|
+
}, undefined)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
anyHorizontal = func(...args){
|
|
703
|
+
processedOperands := []
|
|
704
|
+
for arg in args {
|
|
705
|
+
exprStruct := undefined
|
|
706
|
+
if _isExpression(arg) {
|
|
707
|
+
exprStruct = arg.getExpression()
|
|
708
|
+
} else if is_string(arg) {
|
|
709
|
+
exprStruct = col(arg).getExpression()
|
|
710
|
+
} else {
|
|
711
|
+
ll.panic("Invalid argument type for anyHorizontal: %T. Must be an expression object or string.", arg)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if exprStruct.type == "or" && is_array(exprStruct.operands) {
|
|
715
|
+
processedOperands = append(processedOperands, exprStruct.operands...)
|
|
716
|
+
} else {
|
|
717
|
+
processedOperands = append(processedOperands, exprStruct)
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if len(processedOperands) == 0 {
|
|
721
|
+
ll.panic("anyHorizontal requires at least one expression.")
|
|
722
|
+
}
|
|
723
|
+
return _newExpression({
|
|
724
|
+
type: "or",
|
|
725
|
+
operands: processedOperands
|
|
726
|
+
}, undefined)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
and = func(...args) {
|
|
735
|
+
return allHorizontal(args...)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
or = func(...args) {
|
|
744
|
+
return anyHorizontal(args...)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
rank = func(orderByExpressions, ...options) {
|
|
767
|
+
orderByVal := []
|
|
768
|
+
descendingVal := false
|
|
769
|
+
|
|
770
|
+
ll.assert(orderByExpressions != undefined, "rank() requires orderByExpressions (single expression/string or array of them).")
|
|
771
|
+
orderByVal = _mapToExpressionStructList(orderByExpressions, "col")
|
|
772
|
+
ll.assert(len(orderByVal) > 0, "rank() 'orderBy' list cannot be empty after processing arguments.")
|
|
773
|
+
|
|
774
|
+
if len(options) > 0 {
|
|
775
|
+
ll.assert(len(options) == 1, "rank() accepts at most one options map argument.")
|
|
776
|
+
opsMap := options[0]
|
|
777
|
+
ll.assert(is_map(opsMap), "Second argument to rank(), if provided, must be an options map.")
|
|
778
|
+
if maps.containsKey(opsMap, "descending") {
|
|
779
|
+
ll.assert(is_bool(opsMap.descending), "descending in options map must be a boolean")
|
|
780
|
+
descendingVal = opsMap.descending
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
return _newWindowExpression({
|
|
785
|
+
type: "rank",
|
|
786
|
+
orderBy: orderByVal,
|
|
787
|
+
partitionBy: [],
|
|
788
|
+
descending: descendingVal
|
|
789
|
+
}, undefined, undefined)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
_newWhenThenBuilder := func(currentClauses, currentWhenExprOrUndefined) {
|
|
794
|
+
builderSelf := undefined
|
|
795
|
+
builderSelf = ll.toStrict({
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
then: func(thenExpr) {
|
|
802
|
+
ll.assert(currentWhenExprOrUndefined != undefined, ".then() must follow a .when() or initial when() call.")
|
|
803
|
+
ll.assert(_isExpression(thenExpr), ".then() expects an expression object argument.")
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
newClauses := maps.clone(currentClauses) // Deep copy existing clauses
|
|
807
|
+
newClauses = append(newClauses, {
|
|
808
|
+
when: currentWhenExprOrUndefined.getExpression(),
|
|
809
|
+
then: thenExpr.getExpression()
|
|
810
|
+
})
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
return _newWhenThenBuilder(newClauses, undefined)
|
|
814
|
+
},
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
when: func(conditionExpr) {
|
|
822
|
+
ll.assert(currentWhenExprOrUndefined == undefined, ".when() must follow a .then() call.")
|
|
823
|
+
ll.assert(_isExpression(conditionExpr), ".when() expects an expression object argument.")
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
return _newWhenThenBuilder(currentClauses, conditionExpr)
|
|
828
|
+
},
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
otherwise: func(otherwiseExpr) {
|
|
836
|
+
ll.assert(currentWhenExprOrUndefined == undefined, ".otherwise() must follow a .then() call.")
|
|
837
|
+
ll.assert(len(currentClauses) > 0, "At least one .when().then() clause is required before .otherwise().")
|
|
838
|
+
ll.assert(_isExpression(otherwiseExpr), ".otherwise() expects an expression object argument.")
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
finalClauses := maps.clone(currentClauses)
|
|
842
|
+
return _newExpression({
|
|
843
|
+
type: "when_then_otherwise",
|
|
844
|
+
conditions: finalClauses,
|
|
845
|
+
otherwise: otherwiseExpr.getExpression()
|
|
846
|
+
}, undefined)
|
|
847
|
+
}
|
|
848
|
+
})
|
|
849
|
+
return builderSelf
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
when = func(conditionExpr) {
|
|
863
|
+
ll.assert(_isExpression(conditionExpr), "Initial when() expects an expression object argument.")
|
|
864
|
+
return _newWhenThenBuilder([], conditionExpr)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
rawExp := func(expression, ...alias) {
|
|
879
|
+
ll.assert(len(alias) <= 1, "rawExpression expects at most one alias argument")
|
|
880
|
+
aliasVal := undefined
|
|
881
|
+
if len(alias) > 0 {
|
|
882
|
+
aliasVal = alias[0]
|
|
883
|
+
}
|
|
884
|
+
ll.assert(is_map(expression), "rawExpression expects a map as argument")
|
|
885
|
+
return _newExpression(expression, aliasVal)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
export ll.toStrict({
|
|
889
|
+
col: col,
|
|
890
|
+
lit: lit,
|
|
891
|
+
concatStr: concatStr,
|
|
892
|
+
minHorizontal: minHorizontal,
|
|
893
|
+
maxHorizontal: maxHorizontal,
|
|
894
|
+
allHorizontal: allHorizontal,
|
|
895
|
+
anyHorizontal: anyHorizontal,
|
|
896
|
+
and: and, // Top-level alias for allHorizontal
|
|
897
|
+
or: or, // Top-level alias for anyHorizontal
|
|
898
|
+
rank: rank,
|
|
899
|
+
when: when,
|
|
900
|
+
rawExp: rawExp,
|
|
901
|
+
_isExpression: _isExpression,
|
|
902
|
+
_isAggregation: _isAggregation,
|
|
903
|
+
_mapToExpressionStructList: _mapToExpressionStructList,
|
|
904
|
+
_mapToSingleExpressionStruct: _mapToSingleExpressionStruct
|
|
905
|
+
})
|