@platforma-sdk/workflow-tengo 5.20.0 → 5.21.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 (55) hide show
  1. package/dist/index.cjs +3 -0
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +3 -0
  4. package/dist/tengo/lib/pframes.lib.tengo +12 -269
  5. package/dist/tengo/lib/pframes.process-column-batch.lib.tengo +343 -0
  6. package/dist/tengo/lib/pframes.process-column-common.lib.tengo +366 -0
  7. package/dist/tengo/lib/pframes.process-pcolumn-batch-helpers.lib.tengo +242 -0
  8. package/dist/tengo/lib/pframes.table-builder.lib.tengo +3 -0
  9. package/dist/tengo/lib/tpl.base.lib.tengo +4 -1
  10. package/dist/tengo/tpl/annotations.compute.plj.gz +0 -0
  11. package/dist/tengo/tpl/anonymize.deanonymize.plj.gz +0 -0
  12. package/dist/tengo/tpl/anonymize.impl.plj.gz +0 -0
  13. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  14. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  15. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  16. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  17. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  18. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  19. package/dist/tengo/tpl/check_network.workdir_type_name_extractor.plj.gz +0 -0
  20. package/dist/tengo/tpl/exec.conda.create-env.plj.gz +0 -0
  21. package/dist/tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz +0 -0
  22. package/dist/tengo/tpl/exec.exec-impl.plj.gz +0 -0
  23. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  24. package/dist/tengo/tpl/exec.load-software.impl.plj.gz +0 -0
  25. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  26. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  27. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  28. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  29. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  30. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  31. package/dist/tengo/tpl/pframes.build-table.plj.gz +0 -0
  32. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  33. package/dist/tengo/tpl/pframes.export-parquet-pcolumn-chunk.plj.gz +0 -0
  34. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  35. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  36. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  37. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  38. package/dist/tengo/tpl/pframes.merge-resource-maps.plj.gz +0 -0
  39. package/dist/tengo/tpl/pframes.parse-to-json.plj.gz +0 -0
  40. package/dist/tengo/tpl/pframes.process-pcolumn-batch-split.plj.gz +0 -0
  41. package/dist/tengo/tpl/pframes.process-pcolumn-batch.plj.gz +0 -0
  42. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  43. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  44. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  45. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  46. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  47. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  48. package/dist/tengo/tpl/pt.import-dir.plj.gz +0 -0
  49. package/dist/tengo/tpl/pt.workflow-run.plj.gz +0 -0
  50. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  51. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  52. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  53. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  54. package/dist/tengo/tpl/workflow.query-context-filter.plj.gz +0 -0
  55. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@ module.exports = { Templates: {
3
3
  'pframes.export-parquet-pcolumn-chunk': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-parquet-pcolumn-chunk.plj.gz') },
4
4
  'pframes.export-single-pcolumn': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-single-pcolumn.plj.gz') },
5
5
  'pframes.import-dir': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.import-dir.plj.gz') },
6
+ 'pframes.merge-resource-maps': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.merge-resource-maps.plj.gz') },
6
7
  'pframes.remove-nulls': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.remove-nulls.plj.gz') },
7
8
  'pt.import-dir': { type: 'from-file', path: require.resolve('./tengo/tpl/pt.import-dir.plj.gz') },
8
9
  'waiter.waiter': { type: 'from-file', path: require.resolve('./tengo/tpl/waiter.waiter.plj.gz') },
@@ -38,6 +39,8 @@ module.exports = { Templates: {
38
39
  'pframes.xsv-import-if-not-null': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-import-if-not-null.plj.gz') },
39
40
  'pframes.xsv-import-map': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-import-map.plj.gz') },
40
41
  'annotations.compute': { type: 'from-file', path: require.resolve('./tengo/tpl/annotations.compute.plj.gz') },
42
+ 'pframes.process-pcolumn-batch-split': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.process-pcolumn-batch-split.plj.gz') },
43
+ 'pframes.process-pcolumn-batch': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.process-pcolumn-batch.plj.gz') },
41
44
  'pframes.process-pcolumn-data': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.process-pcolumn-data.plj.gz') },
42
45
  'pframes.export-pframe': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-pframe.plj.gz') },
43
46
  'pframes.export-pframe-for-ui': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.export-pframe-for-ui.plj.gz') }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  declare type TemplateFromFile = { readonly type: "from-file"; readonly path: string; };
2
- declare type TplName = "ll.get-field" | "pframes.export-parquet-pcolumn-chunk" | "pframes.export-single-pcolumn" | "pframes.import-dir" | "pframes.remove-nulls" | "pt.import-dir" | "waiter.waiter" | "workdir.save" | "workflow.build-ctx" | "workflow.query-context-filter" | "anonymize.deanonymize" | "anonymize.impl" | "check_network.workdir_type_name_extractor" | "exec.delayed-compute-allocation.impl" | "exec.monetization_to_json" | "exec.python.install-deps" | "exec.python.venv-bin-path" | "pframes.export-multiple-pcolumns" | "workflow.query-anchored" | "check_network.create_workdir_from_storage" | "exec.conda.create-env" | "exec.exec-impl" | "exec.monetization_info" | "exec.python.create-venv" | "pframes.slice-data" | "check_network.download_blob" | "check_network.upload_blob" | "check_network.upload_template" | "exec.load-software.impl" | "pframes.parse-to-json" | "exec.exec" | "pt.workflow-run" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "pframes.build-table" | "pframes.xsv-export-pframe" | "pframes.xsv-import-if-not-null" | "pframes.xsv-import-map" | "annotations.compute" | "pframes.process-pcolumn-data" | "pframes.export-pframe" | "pframes.export-pframe-for-ui";
2
+ declare type TplName = "ll.get-field" | "pframes.export-parquet-pcolumn-chunk" | "pframes.export-single-pcolumn" | "pframes.import-dir" | "pframes.merge-resource-maps" | "pframes.remove-nulls" | "pt.import-dir" | "waiter.waiter" | "workdir.save" | "workflow.build-ctx" | "workflow.query-context-filter" | "anonymize.deanonymize" | "anonymize.impl" | "check_network.workdir_type_name_extractor" | "exec.delayed-compute-allocation.impl" | "exec.monetization_to_json" | "exec.python.install-deps" | "exec.python.venv-bin-path" | "pframes.export-multiple-pcolumns" | "workflow.query-anchored" | "check_network.create_workdir_from_storage" | "exec.conda.create-env" | "exec.exec-impl" | "exec.monetization_info" | "exec.python.create-venv" | "pframes.slice-data" | "check_network.download_blob" | "check_network.upload_blob" | "check_network.upload_template" | "exec.load-software.impl" | "pframes.parse-to-json" | "exec.exec" | "pt.workflow-run" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "pframes.build-table" | "pframes.xsv-export-pframe" | "pframes.xsv-import-if-not-null" | "pframes.xsv-import-map" | "annotations.compute" | "pframes.process-pcolumn-batch-split" | "pframes.process-pcolumn-batch" | "pframes.process-pcolumn-data" | "pframes.export-pframe" | "pframes.export-pframe-for-ui";
3
3
  declare const Templates: Record<TplName, TemplateFromFile>;
4
4
  export { Templates };
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export const Templates = {
4
4
  'pframes.export-parquet-pcolumn-chunk': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-parquet-pcolumn-chunk.plj.gz') },
5
5
  'pframes.export-single-pcolumn': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-single-pcolumn.plj.gz') },
6
6
  'pframes.import-dir': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.import-dir.plj.gz') },
7
+ 'pframes.merge-resource-maps': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.merge-resource-maps.plj.gz') },
7
8
  'pframes.remove-nulls': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.remove-nulls.plj.gz') },
8
9
  'pt.import-dir': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pt.import-dir.plj.gz') },
9
10
  'waiter.waiter': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/waiter.waiter.plj.gz') },
@@ -39,6 +40,8 @@ export const Templates = {
39
40
  'pframes.xsv-import-if-not-null': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-import-if-not-null.plj.gz') },
40
41
  'pframes.xsv-import-map': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-import-map.plj.gz') },
41
42
  'annotations.compute': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/annotations.compute.plj.gz') },
43
+ 'pframes.process-pcolumn-batch-split': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.process-pcolumn-batch-split.plj.gz') },
44
+ 'pframes.process-pcolumn-batch': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.process-pcolumn-batch.plj.gz') },
42
45
  'pframes.process-pcolumn-data': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.process-pcolumn-data.plj.gz') },
43
46
  'pframes.export-pframe': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-pframe.plj.gz') },
44
47
  'pframes.export-pframe-for-ui': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.export-pframe-for-ui.plj.gz') }
@@ -17,6 +17,8 @@ constants := import("@platforma-sdk/workflow-tengo:pframes.constants")
17
17
  xsvBuilder := import("@platforma-sdk/workflow-tengo:pframes.xsv-builder")
18
18
  tableBuilderMod := import("@platforma-sdk/workflow-tengo:pframes.table-builder")
19
19
  pColumnData := import("@platforma-sdk/workflow-tengo:pframes.data")
20
+ pCommon := import("@platforma-sdk/workflow-tengo:pframes.process-column-common")
21
+ pColumnBatch := import("@platforma-sdk/workflow-tengo:pframes.process-column-batch")
20
22
 
21
23
  llProcessTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.process-pcolumn-data")
22
24
 
@@ -587,6 +589,13 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
587
589
  ll.panic("too many arguments")
588
590
  }
589
591
 
592
+
593
+ isBatchMode := is_map(input) && !is_undefined(input.primary)
594
+ if isBatchMode {
595
+ return pColumnBatch.processColumnBatch(input, bodyTpl, outputs, opts)
596
+ }
597
+
598
+
590
599
  if is_undefined(input.spec) {
591
600
  ll.panic("no input spec provided")
592
601
  }
@@ -621,26 +630,6 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
621
630
  ll.panic("can't use passAggregationAxesSpec, passAggregationAxesNames or passDistilledAggregationSpec without aggregate")
622
631
  }
623
632
 
624
- createOutputTrace := func(output) {
625
- hasOutputTrace := !is_undefined(output.traceSteps)
626
- hasGlobalTrace := !is_undefined(opts.traceSteps)
627
- hasInputTrace := !is_undefined(input.spec.annotations[pSpec.A_TRACE])
628
-
629
- outputTraceSteps := slices.normalize(opts.traceSteps) + slices.normalize(output.traceSteps)
630
-
631
- trace := undefined
632
- if len(outputTraceSteps) > 0 || hasInputTrace {
633
- trace = pSpec.makeTrace(input.spec, outputTraceSteps...)
634
- }
635
-
636
- outputOverrideTrace := output.overrideTrace
637
- if is_undefined(outputOverrideTrace) {
638
- outputOverrideTrace = hasOutputTrace || hasGlobalTrace
639
- }
640
-
641
- return { trace: trace, overrideTrace: outputOverrideTrace }
642
- }
643
-
644
633
  processTemplateParams := {
645
634
  eph: opts.isEphemeral,
646
635
  passKey: opts.passKey
@@ -761,7 +750,7 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
761
750
  for output in outputs {
762
751
 
763
752
  outputsMap[output.name] = output
764
- traceInfo := createOutputTrace(output)
753
+ traceInfo := pCommon.buildOutputTrace(opts, output, input.spec)
765
754
  if output.type == "Resource" || output.type == "BinaryPartitioned" || output.type == "JsonPartitioned" {
766
755
  spec := maps.merge(output.spec, { axesSpec: copy(iterationAxesSpec) + slices.normalize(output.spec.axesSpec) })
767
756
  if !is_undefined(traceInfo.trace) {
@@ -851,256 +840,10 @@ processColumn := func(input, bodyTpl, outputs, ...opts) {
851
840
  }
852
841
 
853
842
  renderResult := render.createEphemeral(llProcessTpl, renderInputs)
854
-
855
- self := undefined
856
- self = ll.toStrict({
857
- renderResult: renderResult,
858
-
859
-
860
-
861
-
862
-
863
-
864
-
865
-
866
-
867
-
868
-
869
-
870
- outputSpec: func(name, ...column) {
871
- output := outputsMap[name]
872
- if is_undefined(output) {
873
- ll.panic("unknown output: " + name)
874
- }
875
-
876
- spec := outputSpecs[name]
877
- if output.type == "Xsv" || output.type == "TsvContent" {
878
- if len(column) != 1 {
879
- ll.panic("column name must be provided for " + output.type + " output")
880
- }
881
- columnName := column[0]
882
- spec = spec[columnName]
883
- if is_undefined(spec) {
884
- ll.panic("unknown column: " + columnName)
885
- }
886
- return spec
887
- } else {
888
- if len(column) > 0 {
889
- ll.panic("column name should not be provided for non-Xsv/TsvContent output")
890
- }
891
- return spec
892
- }
893
- },
894
-
895
-
896
-
897
-
898
-
899
-
900
-
901
-
902
-
903
-
904
-
905
-
906
- outputData: func(name, ...column) {
907
- output := outputsMap[name]
908
- if is_undefined(output) {
909
- ll.panic("unknown output: " + name)
910
- }
911
-
912
- if output.type == "Xsv" || output.type == "TsvContent" {
913
- if len(column) != 1 {
914
- ll.panic("column name must be provided for " + output.type + " output")
915
- }
916
- return renderResult.output(name + "/" + column[0])
917
- } else {
918
- if len(column) > 0 {
919
- ll.panic("column name should not be provided for non-Xsv/TsvContent output")
920
- }
921
- return renderResult.output(name)
922
- }
923
- },
924
-
925
-
926
-
927
-
928
-
929
-
930
-
931
-
932
-
933
-
934
-
935
-
936
- output: func(name, ...column) {
937
- output := outputsMap[name]
938
- if is_undefined(output) {
939
- ll.panic("unknown output: " + name)
940
- }
941
-
942
- if output.type == "Xsv" || output.type == "TsvContent" {
943
- if len(column) != 1 {
944
- ll.panic("column name must be provided for " + output.type + " output")
945
- }
946
- columnName := column[0]
947
- spec := outputSpecs[name][columnName]
948
- if is_undefined(spec) {
949
- ll.panic("unknown column: " + columnName)
950
- }
951
- return {
952
- data: renderResult.output(name + "/" + columnName),
953
- spec: spec
954
- }
955
- } else {
956
- if len(column) > 0 {
957
- ll.panic("column name should not be provided for non-Xsv/TsvContent output")
958
- }
959
- return {
960
- data: renderResult.output(name),
961
- spec: outputSpecs[name]
962
- }
963
- }
964
- },
965
-
966
-
967
-
968
-
969
-
970
-
971
-
972
- xsvOutputFrame: func(name) {
973
- output := outputsMap[name]
974
- if is_undefined(output) {
975
- ll.panic("unknown output: " + name)
976
- }
977
-
978
- if output.type != "Xsv" && output.type != "TsvContent" {
979
- ll.panic("outputFrame can only be used with Xsv or TsvContent outputs")
980
- }
981
-
982
- builder := builder.pFrameBuilder()
983
- for columnName, spec in outputSpecs[name] {
984
- builder.add(columnName, spec, renderResult.output(name + "/" + columnName))
985
- }
986
- return builder.build()
987
- },
988
-
989
-
990
-
991
-
992
-
993
-
994
-
995
-
996
- addAllOutputsToBuilder: func(builder, ...separator) {
997
- sep := "."
998
- if len(separator) > 0 {
999
- sep = separator[0]
1000
- }
1001
-
1002
- maps.forEach(outputsMap, func(name, output) {
1003
- if output.type == "Xsv" || output.type == "TsvContent" {
1004
-
1005
- for columnName, spec in outputSpecs[name] {
1006
- builder.add(
1007
- name + sep + columnName,
1008
- spec,
1009
- renderResult.output(name + "/" + columnName)
1010
- )
1011
- }
1012
- } else {
1013
-
1014
- builder.add(
1015
- name,
1016
- outputSpecs[name],
1017
- renderResult.output(name)
1018
- )
1019
- }
1020
- })
1021
-
1022
- return builder
1023
- },
1024
-
1025
-
1026
-
1027
-
1028
-
1029
-
1030
-
1031
- allOutputsFrame: func(...xsvNameSeparator) {
1032
- builder := builder.pFrameBuilder()
1033
- self.addAllOutputsToBuilder(builder, xsvNameSeparator...)
1034
- return builder.build()
1035
- },
1036
-
1037
-
1038
-
1039
-
1040
-
1041
-
1042
- listOutputs: func() {
1043
- return maps.getKeys(outputsMap)
1044
- },
1045
-
1046
-
1047
-
1048
-
1049
-
1050
-
1051
-
1052
- isXsvOutput: func(name) {
1053
- output := outputsMap[name]
1054
- if is_undefined(output) {
1055
- ll.panic("unknown output: " + name)
1056
- }
1057
- return output.type == "Xsv"
1058
- },
1059
-
1060
-
1061
-
1062
-
1063
-
1064
-
1065
-
1066
- listXsvColumns: func(name) {
1067
- output := outputsMap[name]
1068
- if is_undefined(output) {
1069
- ll.panic("unknown output: " + name)
1070
- }
1071
- if output.type != "Xsv" && output.type != "TsvContent" {
1072
- ll.panic("output is not Xsv or TsvContent: " + name)
1073
- }
1074
-
1075
- return maps.getKeys(outputSpecs[name])
1076
- },
1077
-
1078
-
1079
-
1080
-
1081
-
1082
-
1083
-
1084
-
1085
-
1086
- addXsvOutputToBuilder: func(builder, outputName, prefix) {
1087
- for columnName in self.listXsvColumns(outputName) {
1088
- builder.add(
1089
- prefix + columnName,
1090
- self.outputSpec(outputName, columnName),
1091
- self.outputData(outputName, columnName)
1092
- )
1093
- }
1094
-
1095
- return builder
1096
- }
1097
-
1098
-
1099
- })
1100
-
1101
- return self
843
+ return pCommon.createProcessColumnResult(renderResult, outputsMap, outputSpecs)
1102
844
  }
1103
845
 
846
+
1104
847
  export ll.toStrict({
1105
848
  parseData: pColumnData.parseData,
1106
849
  processColumn: processColumn,
@@ -0,0 +1,343 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ ll := import("@platforma-sdk/workflow-tengo:ll")
17
+ maps := import("@platforma-sdk/workflow-tengo:maps")
18
+ sets := import("@platforma-sdk/workflow-tengo:sets")
19
+ slices := import("@platforma-sdk/workflow-tengo:slices")
20
+ smart := import("@platforma-sdk/workflow-tengo:smart")
21
+ render := import("@platforma-sdk/workflow-tengo:render")
22
+ assets := import("@platforma-sdk/workflow-tengo:assets")
23
+ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
24
+ pUtil := import("@platforma-sdk/workflow-tengo:pframes.util")
25
+ pCommon := import("@platforma-sdk/workflow-tengo:pframes.process-column-common")
26
+
27
+ llBatchProcessTpl := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.process-pcolumn-batch")
28
+
29
+ __PRIMITIVE_VALUE_TYPES := sets.fromSlice(["Int", "Long", "Float", "Double", "String", "Bytes"])
30
+ __VALID_BATCH_FORMATS := sets.fromSlice(["tsv", "csv", "ndjson", "parquet"])
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+ processColumnBatch := func(input, bodyTpl, outputs, opts) {
51
+ ll.assert(!is_undefined(opts.batch),
52
+ "processColumn: batch option is required when input uses the primary+columns form; " +
53
+ "set opts.batch or use the single-input form")
54
+ ll.assert(is_undefined(opts.aggregate),
55
+ "processColumn: aggregate and batch options are mutually exclusive; remove one of them")
56
+
57
+ batch := opts.batch
58
+ ll.assert(is_int(batch.size) && batch.size > 0,
59
+ "batch.size must be a positive integer (records per batch); got %v", batch.size)
60
+
61
+
62
+
63
+
64
+ ll.assert(is_array(batch.keyColumns) && len(batch.keyColumns) > 0,
65
+ "batch.keyColumns is required and must be a non-empty array of axis names to batch over; got %v",
66
+ batch.keyColumns)
67
+
68
+ if is_undefined(batch.format) {
69
+ batch.format = "tsv"
70
+ }
71
+ ll.assert(sets.hasElement(__VALID_BATCH_FORMATS, batch.format),
72
+ "batch.format must be one of \"tsv\", \"csv\", \"ndjson\", \"parquet\"; got %v", batch.format)
73
+
74
+ if is_undefined(batch.passContent) {
75
+ batch.passContent = false
76
+ }
77
+
78
+
79
+ ll.assert(!(batch.passContent && batch.format == "parquet"),
80
+ "batch.passContent=true is not applicable to batch.format=\"parquet\" (binary format has no text payload); " +
81
+ "set passContent=false or choose a text format (tsv, csv, ndjson)")
82
+
83
+
84
+
85
+ if is_undefined(batch.maxBatches) {
86
+ batch.maxBatches = 10000
87
+ }
88
+ ll.assert(is_int(batch.maxBatches) && batch.maxBatches > 0,
89
+ "batch.maxBatches must be a positive integer (soft cap on total batch count); got %v", batch.maxBatches)
90
+
91
+ primaryJoin := input.primaryJoin
92
+ if is_undefined(primaryJoin) {
93
+ primaryJoin = "full"
94
+ }
95
+ ll.assert(primaryJoin == "full" || primaryJoin == "inner",
96
+ "input.primaryJoin must be \"full\" or \"inner\"; got %v", primaryJoin)
97
+
98
+ isEph := false
99
+ if !is_undefined(opts.isEphemeral) {
100
+ isEph = opts.isEphemeral
101
+ }
102
+
103
+
104
+ primaryEntries := pCommon.normalizeColumnEntries(input.primary)
105
+ secondaryEntries := pCommon.normalizeColumnEntries(input.columns)
106
+ ll.assert(len(primaryEntries) > 0,
107
+ "input.primary must contain at least one column")
108
+
109
+
110
+
111
+
112
+ _assertPrimitive := func(role, header, spec) {
113
+ ll.assert(sets.hasElement(__PRIMITIVE_VALUE_TYPES, spec.valueType),
114
+ "batch mode %s column %q has non-primitive valueType %q; " +
115
+ "use one of Int, Long, Float, Double, String, Bytes",
116
+ role, header, spec.valueType)
117
+ }
118
+ for entry in primaryEntries {
119
+ _assertPrimitive("primary", entry.header, entry.src.spec)
120
+ }
121
+ for entry in secondaryEntries {
122
+ _assertPrimitive("non-primary", entry.header, entry.src.spec)
123
+ }
124
+
125
+
126
+ allAxesSpec := primaryEntries[0].src.spec.axesSpec
127
+ ll.assert(!is_undefined(allAxesSpec),
128
+ "primary column %q is missing axesSpec; every input PColumn spec must define axesSpec",
129
+ primaryEntries[0].header)
130
+
131
+
132
+ batchKeyIndices := pSpec.matchAxes(allAxesSpec, batch.keyColumns)
133
+ isolationIndices := pUtil.calculateGroupAxesIndices(len(allAxesSpec), batchKeyIndices)
134
+
135
+ isolationAxesSpec := []
136
+ for i in isolationIndices {
137
+ isolationAxesSpec = append(isolationAxesSpec, allAxesSpec[i])
138
+ }
139
+
140
+ batchKeyAxesSpec := []
141
+ for i in batchKeyIndices {
142
+ batchKeyAxesSpec = append(batchKeyAxesSpec, allAxesSpec[i])
143
+ }
144
+
145
+
146
+
147
+
148
+
149
+ reservedNames := {}
150
+ _claimName := func(kind, name) {
151
+ prior := reservedNames[name]
152
+ ll.assert(is_undefined(prior),
153
+ "%s header %q collides with %s of the same name; rename one of them so every column and batch-key axis has a unique header",
154
+ kind, name, prior)
155
+ reservedNames[name] = kind
156
+ }
157
+ for axSpec in batchKeyAxesSpec {
158
+ _claimName("batch-key axis", axSpec.name)
159
+ }
160
+ for entry in primaryEntries {
161
+ _claimName("primary column", entry.header)
162
+ }
163
+ for entry in secondaryEntries {
164
+ _claimName("non-primary column", entry.header)
165
+ }
166
+
167
+
168
+ iterationAxesSpec := copy(isolationAxesSpec)
169
+
170
+
171
+
172
+ batchKeyAxisByName := {}
173
+ for axSpec in batchKeyAxesSpec {
174
+ batchKeyAxisByName[axSpec.name] = axSpec
175
+ }
176
+
177
+
178
+ processedOutputs := []
179
+ outputSpecs := {}
180
+ outputsMap := {}
181
+
182
+
183
+
184
+ batchTraceInputSpec := primaryEntries[0].src.spec
185
+
186
+ for output in outputs {
187
+ outputsMap[output.name] = output
188
+ traceInfo := pCommon.buildOutputTrace(opts, output, batchTraceInputSpec)
189
+ _maybeInjectTrace := func(spec) {
190
+ if !is_undefined(traceInfo.trace) {
191
+ return traceInfo.trace.inject(spec, { override: traceInfo.overrideTrace })
192
+ }
193
+ return spec
194
+ }
195
+
196
+ ll.assert(output.type != "Resource",
197
+ "output %q: Resource output type is not supported in batch mode; use ResourceMap or Xsv instead",
198
+ output.name)
199
+ ll.assert(output.type == "ResourceMap" || output.type == "Xsv",
200
+ "output %q has unsupported output type %q in batch mode; use ResourceMap or Xsv instead",
201
+ output.name, output.type)
202
+
203
+ if output.type == "ResourceMap" {
204
+ spec := maps.merge(output.spec, {
205
+ axesSpec: copy(iterationAxesSpec) + copy(batchKeyAxesSpec) + slices.normalize(output.spec.axesSpec)
206
+ })
207
+ outputSpecs[output.name] = _maybeInjectTrace(spec)
208
+ processedOutputs = append(processedOutputs, maps.merge(output, {
209
+ keyLength: len(output.spec.axesSpec),
210
+ spec: undefined,
211
+ traceSteps: undefined,
212
+ overrideTrace: undefined
213
+ }))
214
+ } else { // output.type == "Xsv"
215
+ ll.assert(!is_undefined(output.settings),
216
+ "Xsv output %q is missing `settings` (required: an xsv import config)", output.name)
217
+
218
+
219
+
220
+
221
+
222
+
223
+ if is_undefined(output.xsvType) {
224
+ ll.assert(batch.format != "ndjson",
225
+ "Xsv output %q requires explicit xsvType when batch.format=\"ndjson\" — " +
226
+ "set xsvType to csv, tsv, or parquet (xsv.importFile cannot read ndjson)",
227
+ output.name)
228
+ } else {
229
+ pUtil.validateXsvType(output.xsvType)
230
+ }
231
+
232
+
233
+
234
+ batchKeyColumnsNames := output.settings.batchKeyColumns
235
+ ll.assert(!is_undefined(batchKeyColumnsNames),
236
+ "Xsv output %q is missing settings.batchKeyColumns (required in batch mode; list the axis names emitted by the body)",
237
+ output.name)
238
+ ll.assert(is_undefined(output.settings.axes),
239
+ "Xsv output %q cannot set both batchKeyColumns and axes in settings; these are mutually exclusive — keep batchKeyColumns only",
240
+ output.name)
241
+
242
+ batchAxes := slices.map(batchKeyColumnsNames, func(colName) {
243
+ axSpec := batchKeyAxisByName[colName]
244
+ ll.assert(!is_undefined(axSpec),
245
+ "Xsv output %q: settings.batchKeyColumns entry %q does not match any batch.keyColumns axis",
246
+ output.name, colName)
247
+ return { column: colName, spec: axSpec }
248
+ })
249
+
250
+
251
+
252
+
253
+
254
+ modifiedSettings := maps.merge(output.settings, {
255
+ axes: batchAxes,
256
+ batchKeyColumns: undefined,
257
+ partitionKeyLength: 0
258
+ })
259
+ decomposition := pUtil.decomposePfconvImportCfg(modifiedSettings, {
260
+ additionalAxesSpec: iterationAxesSpec
261
+ })
262
+ columnsSpec := decomposition.columnsSpec
263
+ for columnName, spec in columnsSpec {
264
+ columnsSpec[columnName] = _maybeInjectTrace(spec)
265
+ }
266
+ outputSpecs[output.name] = columnsSpec
267
+ processedOutputs = append(processedOutputs, maps.merge(output, {
268
+ settings: decomposition.purifiedCfg,
269
+ flattenWithDelimiter: "/",
270
+ traceSteps: undefined,
271
+ overrideTrace: undefined
272
+ }))
273
+ }
274
+ }
275
+
276
+ primaryHeaders := slices.map(primaryEntries, func(e) { return e.header })
277
+ secondaryHeaders := slices.map(secondaryEntries, func(e) { return e.header })
278
+
279
+
280
+
281
+ batchTemplateParams := {
282
+ batchSize: batch.size,
283
+ maxBatches: batch.maxBatches,
284
+ batchFormat: batch.format,
285
+ passContent: batch.passContent,
286
+ batchKeyIndices: batchKeyIndices,
287
+ isolationIndices: isolationIndices,
288
+ primaryJoin: primaryJoin,
289
+ eph: isEph,
290
+ outputs: processedOutputs,
291
+ primaryColumnCount: len(primaryEntries),
292
+ secondaryColumnCount: len(secondaryEntries),
293
+ primaryHeaders: primaryHeaders,
294
+ secondaryHeaders: secondaryHeaders
295
+ }
296
+
297
+ renderInputs := {
298
+ params: smart.createJsonResource(batchTemplateParams),
299
+ body: bodyTpl
300
+ }
301
+ for i, entry in primaryEntries {
302
+ renderInputs["__col_primary_" + string(i) + "_data"] = entry.src.data
303
+ renderInputs["__col_primary_" + string(i) + "_spec"] = smart.createJsonResource(entry.src.spec)
304
+ if !is_undefined(entry.filter) {
305
+ renderInputs["__col_primary_" + string(i) + "_filter_data"] = entry.filter.data
306
+ renderInputs["__col_primary_" + string(i) + "_filter_spec"] = smart.createJsonResource(entry.filter.spec)
307
+ }
308
+ }
309
+ for i, entry in secondaryEntries {
310
+ renderInputs["__col_secondary_" + string(i) + "_data"] = entry.src.data
311
+ renderInputs["__col_secondary_" + string(i) + "_spec"] = smart.createJsonResource(entry.src.spec)
312
+ }
313
+
314
+ extra := opts.extra
315
+ if !is_undefined(extra) {
316
+ for k, v in extra {
317
+ renderInputs["__extra_" + k] = v
318
+ }
319
+ }
320
+ metaExtra := opts.metaExtra
321
+ if !is_undefined(metaExtra) {
322
+ for k, v in metaExtra {
323
+ renderInputs["__meta_" + k] = v
324
+ }
325
+ }
326
+
327
+
328
+
329
+ renderMetaInputs := {
330
+ globalCpu: opts.cpu,
331
+ globalMem: opts.mem,
332
+ globalQueue: opts.queue,
333
+ stepCache: opts.stepCache,
334
+ cacheInputs: opts.cacheInputs
335
+ }
336
+
337
+ renderResult := render.createEphemeral(llBatchProcessTpl, renderInputs, { metaInputs: renderMetaInputs })
338
+ return pCommon.createProcessColumnResult(renderResult, outputsMap, outputSpecs)
339
+ }
340
+
341
+ export ll.toStrict({
342
+ processColumnBatch: processColumnBatch
343
+ })