@platforma-sdk/workflow-tengo 4.14.1 → 4.16.0

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 (41) hide show
  1. package/dist/tengo/lib/pframes.data.lib.tengo +187 -3
  2. package/dist/tengo/lib/pt.expression.lib.tengo +35 -0
  3. package/dist/tengo/lib/pt.lib.tengo +243 -43
  4. package/dist/tengo/lib/slices.lib.tengo +37 -0
  5. package/dist/tengo/tpl/anonymize.deanonymize.plj.gz +0 -0
  6. package/dist/tengo/tpl/anonymize.impl.plj.gz +0 -0
  7. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  8. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  9. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  10. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  11. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  12. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  13. package/dist/tengo/tpl/check_network.workdir_type_name_extractor.plj.gz +0 -0
  14. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  15. package/dist/tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz +0 -0
  16. package/dist/tengo/tpl/exec.exec-impl.plj.gz +0 -0
  17. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  18. package/dist/tengo/tpl/exec.load-software.impl.plj.gz +0 -0
  19. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  20. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  21. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  22. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  23. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  24. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  25. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  26. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  27. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  28. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  29. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  30. package/dist/tengo/tpl/pframes.parse-to-json.plj.gz +0 -0
  31. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  32. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  33. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  34. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  35. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  36. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  37. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  38. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  39. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  40. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  41. package/package.json +3 -3
@@ -98,9 +98,12 @@ parseData := func(inputParam, ...optsRaw) {
98
98
  state.keyLength = resourceData.keyLength
99
99
  state.relevantTopLayerKeyLength = state.keyLength
100
100
  } else if state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_JSON_PARTITIONED ||
101
- state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_BINARY_PARTITIONED ||
102
- state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_RESOURCE_MAP_PARTITIONED {
101
+ state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_BINARY_PARTITIONED {
102
+ state.partitionKeyLength = resourceData.partitionKeyLength
103
+ state.relevantTopLayerKeyLength = state.partitionKeyLength
104
+ } else if state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_RESOURCE_MAP_PARTITIONED {
103
105
  state.partitionKeyLength = resourceData.partitionKeyLength
106
+ state.keyLength = resourceData.keyLength
104
107
  state.relevantTopLayerKeyLength = state.partitionKeyLength
105
108
  } else if state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_JSON_SUPER_PARTITIONED ||
106
109
  state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_BINARY_SUPER_PARTITIONED {
@@ -188,26 +191,86 @@ _createPColumnDataInstance = func(state) {
188
191
  self := undefined
189
192
 
190
193
  self = ll.toStrict({
194
+
195
+
196
+
197
+
198
+
191
199
  resource: state.resource,
192
200
 
201
+
202
+
203
+
204
+
193
205
  parsed: state.parsed,
194
206
 
207
+
208
+
209
+
210
+
211
+
212
+
213
+
195
214
  getSpec: func() {
196
215
  return state.spec
197
216
  },
198
217
 
218
+
219
+
220
+
221
+
222
+
199
223
  getResourceType: func() {
200
224
  return state.resourceType
201
225
  },
202
226
 
227
+
228
+
229
+
230
+
203
231
  keyLength: state.keyLength,
232
+
233
+
234
+
235
+
236
+
237
+
204
238
  partitionKeyLength: state.partitionKeyLength,
239
+
240
+
241
+
242
+
243
+
205
244
  superPartitionKeyLength: state.superPartitionKeyLength,
206
245
 
246
+
247
+
248
+
249
+
250
+
251
+
252
+
253
+
207
254
  data: state.data,
208
255
 
256
+
257
+
258
+
259
+
260
+
261
+
262
+
209
263
  parseKey: _createDecodeKeyString,
210
264
 
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+
211
274
  uniqueKeyValues: func(axisIdx) {
212
275
  if !state.parsed {
213
276
  ll.panic("uniqueKeyValues: resource is not parsed.")
@@ -224,6 +287,17 @@ _createPColumnDataInstance = func(state) {
224
287
  return maps.values(uniqueValsMap)
225
288
  },
226
289
 
290
+
291
+
292
+
293
+
294
+
295
+
296
+
297
+
298
+
299
+
300
+
227
301
  createDataResource: func() {
228
302
  if !is_undefined(state.resource) {
229
303
 
@@ -271,6 +345,27 @@ _createPColumnDataInstance = func(state) {
271
345
  return builder.lockAndBuild()
272
346
  },
273
347
 
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
274
369
  filter: func(slicingParams) {
275
370
  if len(slicingParams) == 0 {
276
371
  return self
@@ -465,6 +560,26 @@ _createPColumnDataInstance = func(state) {
465
560
  ll.panic("Filtering not supported for type %s", state.resourceType)
466
561
  }
467
562
  },
563
+
564
+
565
+
566
+
567
+
568
+
569
+
570
+
571
+
572
+
573
+
574
+
575
+
576
+
577
+
578
+
579
+
580
+
581
+
582
+
468
583
 
469
584
  partition: func(...axesToPartitionBy) {
470
585
  if !state.parsed {
@@ -511,6 +626,20 @@ _createPColumnDataInstance = func(state) {
511
626
  return resultPartitions
512
627
  },
513
628
 
629
+
630
+
631
+
632
+
633
+
634
+
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
514
643
  forEach: func(userCallback) {
515
644
  if !state.parsed {
516
645
  ll.panic("forEach: resource is not parsed.")
@@ -525,6 +654,61 @@ _createPColumnDataInstance = func(state) {
525
654
  })
526
655
  },
527
656
 
657
+
658
+
659
+
660
+
661
+
662
+
663
+
664
+
665
+
666
+
667
+
668
+
669
+
670
+
671
+
672
+
673
+
674
+
675
+ forEachPartition: func(axesToPartitionBy, userCallback) {
676
+ if !state.parsed {
677
+ ll.panic("forEachPartition: resource is not parsed.")
678
+ }
679
+ if !is_array(axesToPartitionBy) {
680
+ ll.panic("forEachPartition: first argument must be an array of axis indices.")
681
+ }
682
+ if !is_callable(userCallback) {
683
+ ll.panic("forEachPartition: second argument must be a function.")
684
+ }
685
+
686
+ partitions := self.partition(axesToPartitionBy...)
687
+
688
+ maps.forEach(partitions, func(partitionKeyStr, partitionPColumnData) {
689
+ decodedPartitionKey := json.decode(partitionKeyStr)
690
+ userCallback(decodedPartitionKey, partitionPColumnData)
691
+ })
692
+ },
693
+
694
+
695
+
696
+
697
+
698
+
699
+
700
+
701
+
702
+
703
+
704
+
705
+
706
+
707
+
708
+
709
+
710
+
711
+
528
712
  flatten: func() {
529
713
  if !state.parsed {
530
714
  ll.panic("flatten: resource is not parsed.")
@@ -532,7 +716,7 @@ _createPColumnDataInstance = func(state) {
532
716
 
533
717
  isJsonSuper := state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_JSON_SUPER_PARTITIONED
534
718
  isBinarySuper := state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_BINARY_SUPER_PARTITIONED
535
- isResourceMapSuper := state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_RESOURCE_MAP_SUPER_PARTITIONED
719
+ isResourceMapSuper := state.resourceType == pConstants.RTYPE_P_COLUMN_DATA_RESOURCE_MAP_PARTITIONED
536
720
 
537
721
  if !(isJsonSuper || isBinarySuper || isResourceMapSuper) {
538
722
  return self // Not a type that can be flattened
@@ -323,6 +323,41 @@ _newExpression = func(expression, currentAlias) {
323
323
  isNotNull: func() { return unaryOp("is_not_na") }, // Polars: is_not_null(), TS: is_not_na
324
324
 
325
325
 
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+ structField: func(...fieldNames) {
342
+ ll.assert(len(fieldNames) > 0, "structField expects at least one field name")
343
+ for fieldName in fieldNames {
344
+ ll.assert(is_string(fieldName), "All field names must be strings, got: %T", fieldName)
345
+ }
346
+
347
+
348
+ currentExpr := expression
349
+ for fieldName in fieldNames {
350
+ currentExpr = {
351
+ type: "struct_field",
352
+ struct: currentExpr,
353
+ fields: fieldName
354
+ }
355
+ }
356
+
357
+ return _newExpression(currentExpr, undefined)
358
+ },
359
+
360
+
326
361
 
327
362
  strToUpper: func() { return unaryStrOp("to_upper") },
328
363
 
@@ -52,6 +52,19 @@ workflowCounter := 0
52
52
 
53
53
 
54
54
 
55
+ _detectFormatFromFileName := func(fileName) {
56
+ if text.has_suffix(fileName, ".csv") {
57
+ return "csv"
58
+ } else if text.has_suffix(fileName, ".tsv") {
59
+ return "tsv"
60
+ } else if text.has_suffix(fileName, ".ndjson") || text.has_suffix(fileName, ".jsonl") {
61
+ return "ndjson"
62
+ }
63
+ return undefined // No format detected
64
+ }
65
+
66
+
67
+
55
68
 
56
69
 
57
70
 
@@ -118,6 +131,16 @@ workflow := func() {
118
131
 
119
132
 
120
133
 
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
121
144
 
122
145
 
123
146
 
@@ -145,9 +168,11 @@ workflow := func() {
145
168
  inputXsvType := undefined
146
169
  finalDataFrameId := undefined
147
170
  finalFileName := undefined
148
- finalXsvType := undefined
171
+ finalFormat := undefined
149
172
  inferSchemaOpt := undefined
150
173
  optionsSchema := undefined
174
+ nRowsOpt := undefined
175
+ ignoreErrorsOpt := undefined
151
176
 
152
177
  isStructuralInput := is_map(frameInput) && !is_undefined(frameInput.file)
153
178
 
@@ -162,7 +187,7 @@ workflow := func() {
162
187
  inputSchema = frameInput.schema
163
188
  }
164
189
 
165
- finalXsvType = inputXsvType
190
+ finalFormat = inputXsvType
166
191
  } else {
167
192
  if smart.isReference(frameInput) {
168
193
  fileRef = frameInput
@@ -172,15 +197,25 @@ workflow := func() {
172
197
  }
173
198
  }
174
199
 
175
- ll.assert(is_undefined(finalXsvType) || is_undefined(opts.xsvType), "xsvType option cannot be used with structural input as xsvType is already defined in the input structure")
176
200
 
177
- if !is_undefined(opts.xsvType) {
178
- finalXsvType = opts.xsvType
201
+ if !is_undefined(opts.format) {
202
+ if opts.format != "csv" && opts.format != "tsv" && opts.format != "ndjson" {
203
+ ll.panic("format must be 'csv', 'tsv', or 'ndjson'. Got: %v", opts.format)
204
+ }
205
+ if !is_undefined(finalFormat) && finalFormat != opts.format {
206
+ ll.panic("format and xsvType from structural input cannot be used together. Got: %v and %v", opts.format, finalFormat)
207
+ }
208
+ finalFormat = opts.format
209
+ } else if !is_undefined(opts.xsvType) {
210
+ if opts.xsvType != "csv" && opts.xsvType != "tsv" {
211
+ ll.panic("xsvType must be 'csv' or 'tsv'. Got: %v", opts.xsvType)
212
+ }
213
+ if !is_undefined(finalFormat) && finalFormat != opts.xsvType {
214
+ ll.panic("opts.xsvType and xsvType from structural input cannot be used together. Got: %v and %v", opts.xsvType, finalFormat)
215
+ }
216
+ finalFormat = opts.xsvType
179
217
  }
180
218
 
181
- ll.assert(!is_undefined(finalXsvType), "xsvType option is required for direct file input")
182
- ll.assert(finalXsvType == "csv" || finalXsvType == "tsv", "xsvType must be 'csv' or 'tsv'")
183
-
184
219
  if !is_undefined(opts.id) {
185
220
  finalDataFrameId = opts.id
186
221
  } else {
@@ -189,11 +224,21 @@ workflow := func() {
189
224
 
190
225
  if !is_undefined(opts.fileName) {
191
226
  finalFileName = opts.fileName
227
+ if is_undefined(finalFormat) {
228
+ detectedFormat := _detectFormatFromFileName(finalFileName)
229
+ if !is_undefined(detectedFormat) {
230
+ finalFormat = detectedFormat
231
+ }
232
+ }
192
233
  } else {
193
- finalFileName = self._newAnonymousFileId(finalXsvType)
234
+ finalFileName = self._newAnonymousFileId(finalFormat)
194
235
  }
195
236
 
196
237
 
238
+ if is_undefined(finalFormat) {
239
+ ll.panic("Unable to determine file format. Please specify 'format' or 'xsvType' option, or use a fileName with a recognizable extension (.csv, .tsv, .ndjson, .jsonl)")
240
+ }
241
+
197
242
  if !is_undefined(opts.inferSchema) {
198
243
  if !is_bool(opts.inferSchema) {
199
244
  ll.panic("'inferSchema' option must be a boolean. Got: %T", opts.inferSchema)
@@ -201,7 +246,6 @@ workflow := func() {
201
246
  inferSchemaOpt = opts.inferSchema
202
247
  }
203
248
 
204
-
205
249
  if !is_undefined(opts.schema) {
206
250
  if !is_array(opts.schema) {
207
251
  ll.panic("'schema' option (from options map) must be an array. Got: %T", opts.schema)
@@ -209,6 +253,21 @@ workflow := func() {
209
253
  optionsSchema = opts.schema
210
254
  }
211
255
 
256
+ if !is_undefined(opts.nRows) {
257
+ if !is_int(opts.nRows) || opts.nRows < 0 {
258
+ ll.panic("'nRows' option must be a non-negative integer. Got: %v", opts.nRows)
259
+ }
260
+ nRowsOpt = opts.nRows
261
+ }
262
+
263
+ if !is_undefined(opts.ignoreErrors) {
264
+ if !is_bool(opts.ignoreErrors) {
265
+ ll.panic("'ignoreErrors' option must be a boolean. Got: %T", opts.ignoreErrors)
266
+ }
267
+ ignoreErrorsOpt = opts.ignoreErrors
268
+ }
269
+
270
+
212
271
  if !is_undefined(fileRef) && !smart.isReference(fileRef) {
213
272
  ll.panic("frameInput.file (for structural input) or frameInput (for direct reference) must be a valid resource reference. Got: %v", fileRef)
214
273
  } else if !is_undefined(fileContent) && !is_string(fileContent) {
@@ -221,16 +280,26 @@ workflow := func() {
221
280
  inFiles = append(inFiles, { content: fileContent, name: finalFileName })
222
281
  }
223
282
 
224
- readCsvStep := {
225
- type: "read_csv",
226
- file: finalFileName,
227
- name: finalDataFrameId
228
- }
229
283
 
230
- if finalXsvType == "csv" {
231
- readCsvStep.delimiter = ","
232
- } else if finalXsvType == "tsv" {
233
- readCsvStep.delimiter = "\t"
284
+ step := undefined
285
+ if finalFormat == "ndjson" {
286
+ step = {
287
+ type: "read_ndjson",
288
+ file: finalFileName,
289
+ name: finalDataFrameId
290
+ }
291
+ } else { // csv or tsv
292
+ step = {
293
+ type: "read_csv",
294
+ file: finalFileName,
295
+ name: finalDataFrameId
296
+ }
297
+
298
+ if finalFormat == "csv" {
299
+ step.delimiter = ","
300
+ } else if finalFormat == "tsv" {
301
+ step.delimiter = "\t"
302
+ }
234
303
  }
235
304
 
236
305
 
@@ -241,17 +310,24 @@ workflow := func() {
241
310
  finalSchemaToUse = inputSchema
242
311
  }
243
312
 
313
+
244
314
  if !is_undefined(finalSchemaToUse) {
245
- readCsvStep.schema = finalSchemaToUse
315
+ step.schema = finalSchemaToUse
246
316
  }
247
317
 
318
+ if !is_undefined(inferSchemaOpt) && inferSchemaOpt == false {
319
+ step.inferSchema = false
320
+ }
248
321
 
322
+ if !is_undefined(ignoreErrorsOpt) && ignoreErrorsOpt == true {
323
+ step.ignoreErrors = true
324
+ }
249
325
 
250
- if !is_undefined(inferSchemaOpt) && inferSchemaOpt == false {
251
- readCsvStep.inferSchema = false
326
+ if !is_undefined(nRowsOpt) {
327
+ step.nRows = nRowsOpt
252
328
  }
253
329
 
254
- self.addRawStep(readCsvStep)
330
+ self.addRawStep(step)
255
331
  return _newDataFrame(self, finalDataFrameId)
256
332
  },
257
333
 
@@ -465,32 +541,52 @@ _newDataFrame = func(parentWorkflow, dfName) {
465
541
  opts = options[0]
466
542
  }
467
543
 
468
- delimiter := undefined
469
- if text.has_suffix(outputFile, ".csv") {
470
- delimiter = ","
471
- } else if text.has_suffix(outputFile, ".tsv") {
472
- delimiter = "\t"
473
- }
544
+ finalFormat := undefined
474
545
 
475
- if !is_undefined(opts.xsvType) {
476
- if opts.xsvType == "csv" {
477
- delimiter = ","
478
- } else if opts.xsvType == "tsv" {
479
- delimiter = "\t"
480
- } else {
481
- ll.panic("Unsupported xsvType: %v", opts.xsvType)
546
+
547
+ if !is_undefined(opts.format) {
548
+ if opts.format != "csv" && opts.format != "tsv" && opts.format != "ndjson" {
549
+ ll.panic("format must be 'csv', 'tsv', or 'ndjson'. Got: %v", opts.format)
550
+ }
551
+ finalFormat = opts.format
552
+ } else if !is_undefined(opts.xsvType) {
553
+ if opts.xsvType != "csv" && opts.xsvType != "tsv" {
554
+ ll.panic("xsvType must be 'csv' or 'tsv'. Got: %v", opts.xsvType)
555
+ }
556
+ finalFormat = opts.xsvType
557
+ } else {
558
+
559
+ detectedFormat := _detectFormatFromFileName(outputFile)
560
+ if !is_undefined(detectedFormat) {
561
+ finalFormat = detectedFormat
482
562
  }
483
563
  }
484
564
 
485
- if is_undefined(delimiter) {
486
- ll.panic("Can't infer xsvType from outputFile extension, and xsvType is not specified in options")
565
+ if is_undefined(finalFormat) {
566
+ ll.panic("Unable to determine output format from file extension. Please specify 'format' or 'xsvType' option, or use a file name with a recognizable extension (.csv, .tsv, .ndjson, .jsonl)")
487
567
  }
488
568
 
489
- step := {
490
- type: "write_csv",
491
- table: dfName,
492
- file: outputFile,
493
- delimiter: delimiter
569
+ step := undefined
570
+ if finalFormat == "ndjson" {
571
+ step = {
572
+ type: "write_ndjson",
573
+ table: dfName,
574
+ file: outputFile
575
+ }
576
+ } else { // csv or tsv
577
+ delimiter := undefined
578
+ if finalFormat == "csv" {
579
+ delimiter = ","
580
+ } else if finalFormat == "tsv" {
581
+ delimiter = "\t"
582
+ }
583
+
584
+ step = {
585
+ type: "write_csv",
586
+ table: dfName,
587
+ file: outputFile,
588
+ delimiter: delimiter
589
+ }
494
590
  }
495
591
 
496
592
  if !is_undefined(opts.columns) {
@@ -589,6 +685,19 @@ _newDataFrame = func(parentWorkflow, dfName) {
589
685
 
590
686
 
591
687
 
688
+
689
+
690
+
691
+
692
+
693
+
694
+
695
+
696
+
697
+
698
+
699
+
700
+
592
701
 
593
702
 
594
703
 
@@ -616,6 +725,14 @@ _newDataFrame = func(parentWorkflow, dfName) {
616
725
 
617
726
 
618
727
 
728
+
729
+
730
+
731
+
732
+
733
+
734
+
735
+
619
736
 
620
737
 
621
738
 
@@ -1078,9 +1195,92 @@ concat := func(dataframes, ...optionsRaw) {
1078
1195
  return _newDataFrame(parentWorkflow, outputDfName)
1079
1196
  }
1080
1197
 
1198
+
1199
+
1200
+
1201
+
1202
+
1203
+
1204
+
1205
+
1206
+
1207
+
1208
+
1209
+
1210
+
1211
+
1212
+
1213
+
1214
+
1215
+
1216
+
1217
+
1218
+
1219
+
1220
+
1221
+
1222
+
1223
+
1224
+
1225
+
1226
+
1227
+ treeJoin := func(dataframes, opts) {
1228
+ ll.assert(is_array(dataframes), "First argument to treeJoin must be an array of DataFrame objects.")
1229
+ ll.assert(len(dataframes) > 0, "DataFrame array for treeJoin cannot be empty.")
1230
+
1231
+ if !is_map(opts) || is_undefined(opts.on) {
1232
+ ll.panic("treeJoin requires options with 'on' field")
1233
+ }
1234
+ if !is_string(opts.on) {
1235
+ ll.panic("'on' option must be a string")
1236
+ }
1237
+
1238
+
1239
+ if len(dataframes) == 1 {
1240
+ return dataframes[0]
1241
+ }
1242
+
1243
+
1244
+ parentWorkflow := undefined
1245
+ for i, df in dataframes {
1246
+ if is_undefined(df) || is_undefined(df._getDfName) || is_undefined(df._getWorkflow) {
1247
+ ll.panic("Argument at index %d in dataframes array is not a valid DataFrame object.", i)
1248
+ }
1249
+ currentWorkflow := df._getWorkflow()
1250
+ if is_undefined(parentWorkflow) {
1251
+ parentWorkflow = currentWorkflow
1252
+ } else if currentWorkflow._wfId != parentWorkflow._wfId {
1253
+ ll.panic("All DataFrames in a treeJoin operation must belong to the same workflow.")
1254
+ }
1255
+ }
1256
+
1257
+ pfs := dataframes
1258
+
1259
+
1260
+ for len(pfs) > 1 {
1261
+ nextPfs := []
1262
+
1263
+ for i := 1; i < len(pfs); i += 2 {
1264
+ pf1 := pfs[i - 1]
1265
+ pf2 := pfs[i]
1266
+ joined := pf1.join(pf2, { on: opts.on, how: "full", coalesce: true })
1267
+ nextPfs = append(nextPfs, joined)
1268
+ }
1269
+
1270
+ if len(pfs) % 2 == 1 {
1271
+ nextPfs = append(nextPfs, pfs[len(pfs) - 1])
1272
+ }
1273
+ pfs = nextPfs
1274
+ }
1275
+
1276
+ ll.assert(len(pfs) == 1, "Expected exactly one data frame, got " + string(len(pfs)))
1277
+ return pfs[0]
1278
+ }
1279
+
1081
1280
  export ll.toStrict({
1082
1281
  workflow: workflow,
1083
1282
  concat: concat,
1283
+ treeJoin: treeJoin,
1084
1284
 
1085
1285
  col: exp.col,
1086
1286
  lit: exp.lit,
@@ -153,6 +153,42 @@ reduce := func(slice, fn, ...init) {
153
153
  }
154
154
 
155
155
 
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+ flatten := func(slice, ...opts) {
165
+ if is_undefined(slice) {
166
+ return []
167
+ }
168
+
169
+ deep := false
170
+ if len(opts) > 0 && !is_undefined(opts[0]) && !is_undefined(opts[0].deep) {
171
+ deep = opts[0].deep
172
+ }
173
+
174
+ result := []
175
+ for _, elem in slice {
176
+ if is_array(elem) {
177
+ if deep {
178
+ flattened := flatten(elem, {deep: true})
179
+ result = append(result, flattened...)
180
+ } else {
181
+ result = append(result, elem...)
182
+ }
183
+ } else {
184
+ ll.panic("flatten: expected array element but got %s", type_name(elem))
185
+ }
186
+ }
187
+
188
+ return result
189
+ }
190
+
191
+
156
192
  every := func(slice, fn) {
157
193
  for _, elem in slice {
158
194
  if !fn(elem) {
@@ -322,6 +358,7 @@ export ll.toStrict({
322
358
  filter: filter,
323
359
  map: map,
324
360
  reduce: reduce,
361
+ flatten: flatten,
325
362
  every: every,
326
363
  quickSortInPlace: quickSortInPlace,
327
364
  quickSort: quickSort,
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/workflow-tengo",
3
- "version": "4.14.1",
3
+ "version": "4.16.0",
4
4
  "description": "Tengo SDK library",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -18,10 +18,10 @@
18
18
  "dependencies": {
19
19
  "@milaboratories/software-pframes-conv": "2.2.2",
20
20
  "@platforma-open/milaboratories.software-small-binaries": "^1.15.21",
21
- "@platforma-open/milaboratories.software-ptabler": "1.8.0"
21
+ "@platforma-open/milaboratories.software-ptabler": "1.11.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@platforma-sdk/tengo-builder": "^2.1.13"
24
+ "@platforma-sdk/tengo-builder": "^2.1.14"
25
25
  },
26
26
  "scripts": {
27
27
  "type-check": "pl-tengo check",