@grepr/cli 1.6.29-bea24ac → 1.6.42-91f3e80
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/build/dist/commands/backfill-command.d.ts +17 -0
- package/build/dist/commands/backfill-command.d.ts.map +1 -0
- package/build/dist/commands/backfill-command.js +115 -0
- package/build/dist/commands/backfill-command.js.map +1 -0
- package/build/dist/commands/job-draft-command.js +5 -6
- package/build/dist/commands/job-draft-command.js.map +1 -1
- package/build/dist/commands/query-command.d.ts +4 -18
- package/build/dist/commands/query-command.d.ts.map +1 -1
- package/build/dist/commands/query-command.js +5 -49
- package/build/dist/commands/query-command.js.map +1 -1
- package/build/dist/commands/sql-validate-command.d.ts +7 -0
- package/build/dist/commands/sql-validate-command.d.ts.map +1 -0
- package/build/dist/commands/sql-validate-command.js +29 -0
- package/build/dist/commands/sql-validate-command.js.map +1 -0
- package/build/dist/grepr.d.ts +1 -1
- package/build/dist/grepr.d.ts.map +1 -1
- package/build/dist/grepr.js +6 -0
- package/build/dist/grepr.js.map +1 -1
- package/build/dist/lib/backfill-grepr-link.d.ts +9 -0
- package/build/dist/lib/backfill-grepr-link.d.ts.map +1 -0
- package/build/dist/lib/backfill-grepr-link.js +31 -0
- package/build/dist/lib/backfill-grepr-link.js.map +1 -0
- package/build/dist/lib/backfill-vendor-links.d.ts +15 -0
- package/build/dist/lib/backfill-vendor-links.d.ts.map +1 -0
- package/build/dist/lib/backfill-vendor-links.js +181 -0
- package/build/dist/lib/backfill-vendor-links.js.map +1 -0
- package/build/dist/lib/backfill-vendors.d.ts +45 -0
- package/build/dist/lib/backfill-vendors.d.ts.map +1 -0
- package/build/dist/lib/backfill-vendors.js +43 -0
- package/build/dist/lib/backfill-vendors.js.map +1 -0
- package/build/dist/lib/backfill.d.ts +51 -0
- package/build/dist/lib/backfill.d.ts.map +1 -0
- package/build/dist/lib/backfill.js +343 -0
- package/build/dist/lib/backfill.js.map +1 -0
- package/build/dist/lib/grepr-api-client.d.ts +2 -1
- package/build/dist/lib/grepr-api-client.d.ts.map +1 -1
- package/build/dist/lib/grepr-api-client.js +13 -0
- package/build/dist/lib/grepr-api-client.js.map +1 -1
- package/build/dist/lib/job-patch.d.ts +34 -2
- package/build/dist/lib/job-patch.d.ts.map +1 -1
- package/build/dist/lib/job-patch.js +271 -16
- package/build/dist/lib/job-patch.js.map +1 -1
- package/build/dist/lib/job-plan.d.ts +1 -1
- package/build/dist/lib/job-plan.d.ts.map +1 -1
- package/build/dist/lib/job-plan.js +14 -14
- package/build/dist/lib/job-plan.js.map +1 -1
- package/build/dist/lib/query-predicate.d.ts +28 -0
- package/build/dist/lib/query-predicate.d.ts.map +1 -0
- package/build/dist/lib/query-predicate.js +66 -0
- package/build/dist/lib/query-predicate.js.map +1 -0
- package/build/dist/lib/time-utils.d.ts +25 -0
- package/build/dist/lib/time-utils.d.ts.map +1 -1
- package/build/dist/lib/time-utils.js +71 -0
- package/build/dist/lib/time-utils.js.map +1 -1
- package/build/dist/lib/transformers-embeddings.d.ts.map +1 -1
- package/build/dist/lib/transformers-embeddings.js +14 -1
- package/build/dist/lib/transformers-embeddings.js.map +1 -1
- package/build/dist/openapi/openApiTypes.d.ts +521 -20
- package/build/dist/openapi/openApiTypes.d.ts.map +1 -1
- package/build/dist/openapi/openApiTypes.js +43 -1
- package/build/dist/openapi/openApiTypes.js.map +1 -1
- package/build/dist/types.d.ts +3 -7
- package/build/dist/types.d.ts.map +1 -1
- package/build/dist/types.js +1 -1
- package/build/dist/types.js.map +1 -1
- package/package.json +15 -16
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/** Structured patch format for grepr pipelines: a list of ops applied locally before any production write (see {@link JobBackend} for the two substrates). */
|
|
2
|
-
import { LogAttributesRemapperType, LogReducerType, GrokParserType, LogsFilterType, DatadogQueryPredicateType, DatadogLogSinkType, SplunkLogSinkType, NewRelicLogSinkType, SumoLogSinkType, OtlpLogSinkType, LogsIcebergTableSinkType, TemplateOperationType, TemplateQueryExceptionType, SumAttributesMergeStrategyType, MinAttributesMergeStrategyType, MaxAttributesMergeStrategyType, AverageAttributesMergeStrategyType, } from '../openapi/openApiTypes.js';
|
|
2
|
+
import { ConditionNodeKind, DropNodeKind, PassthroughNodeKind, SqlNodeKind, SqlNodeOutputRouting, SqlOperationType, SqlOutputStatementType, LogAttributesRemapperType, LogReducerType, GrokParserType, LogsFilterType, DatadogQueryPredicateType, DatadogLogSinkType, SplunkLogSinkType, NewRelicLogSinkType, SumoLogSinkType, OtlpLogSinkType, LogsIcebergTableSinkType, TemplateOperationType, TemplateQueryExceptionType, SumAttributesMergeStrategyType, MinAttributesMergeStrategyType, MaxAttributesMergeStrategyType, AverageAttributesMergeStrategyType, } from '../openapi/openApiTypes.js';
|
|
3
3
|
import { parseEdge } from './job-graph-utils.js';
|
|
4
4
|
import { RAW_ATTRIBUTES_REMAPPER, RAW_ATTRIBUTES_REMAPPER_TYPE, RAW_JSON_PROCESSOR, RAW_JSON_PROCESSOR_TYPE, RAW_LOG_REDUCER, RAW_PARSER_TYPES, RAW_PRE_EXCEPTIONS_FILTER, RAW_PRE_PARSER_FILTER, RAW_PRE_WAREHOUSE_FILTER, } from './job-graph-log-pipeline-constants.js';
|
|
5
|
+
/** The accepted-phases suffix shared by every template transform-phase error. */
|
|
6
|
+
const ACCEPTED_TRANSFORM_PHASES_MESSAGE = 'Accepted phases are pre-parser, pre-warehouse, and pre-exceptions.';
|
|
7
|
+
/** The downstream pipeline steps a `sql_output` may route to (the generated {@link SqlNodeOutputRouting} values). */
|
|
8
|
+
const SQL_OUTPUT_ROUTING_TARGETS = new Set(Object.values(SqlNodeOutputRouting));
|
|
9
|
+
const SQL_TRANSFORM_MAIN_STREAMS = new Set(['drop', 'passthrough']);
|
|
5
10
|
/** Vendor log sink types the UI supports as `add-sink target: 'vendor'`. */
|
|
6
11
|
const VENDOR_LOG_SINK_TYPES = new Set([
|
|
7
12
|
DatadogLogSinkType.datadog_log_sink,
|
|
@@ -33,6 +38,9 @@ const REQUIRED_OP_FIELDS = {
|
|
|
33
38
|
'remove-parser': [['name', 'string']],
|
|
34
39
|
'set-filter': [['phase', 'string'], ['filter', 'object']],
|
|
35
40
|
'clear-filter': [['phase', 'string']],
|
|
41
|
+
'set-transform-chain': [['phase', 'string'], ['root', 'object']],
|
|
42
|
+
'clear-transform-chain': [['phase', 'string']],
|
|
43
|
+
'set-sql-transform': [['phase', 'string'], ['sqlOperation', 'object'], ['outputRouting', 'object'], ['mainStream', 'string']],
|
|
36
44
|
'add-source': [['source', 'object']],
|
|
37
45
|
'remove-source': [['name', 'string']],
|
|
38
46
|
// Target-conditional fields (vendor removal's `name`, sink shape) stay with the apply-time guards.
|
|
@@ -203,7 +211,13 @@ function applyOperation(input, op, index) {
|
|
|
203
211
|
case 'set-filter':
|
|
204
212
|
return applySetFilter(input, op.phase, op.filter, index);
|
|
205
213
|
case 'clear-filter':
|
|
206
|
-
return applyClearFilter(input, op.phase);
|
|
214
|
+
return applyClearFilter(input, op.phase, index);
|
|
215
|
+
case 'set-transform-chain':
|
|
216
|
+
return applySetTransformChain(input, op.phase, op.root, index);
|
|
217
|
+
case 'clear-transform-chain':
|
|
218
|
+
return applyClearTransformChain(input, op.phase, index);
|
|
219
|
+
case 'set-sql-transform':
|
|
220
|
+
return applySetSqlTransform(input, op.phase, op.sqlOperation, op.outputRouting, op.mainStream, op.gate, index);
|
|
207
221
|
case 'add-source':
|
|
208
222
|
return applyAddSource(input, op.source, index);
|
|
209
223
|
case 'remove-source':
|
|
@@ -271,6 +285,13 @@ function applyJobGraphOperation(jobGraph, op, index) {
|
|
|
271
285
|
return jobGraphSetFilter(jobGraph, op.phase, op.filter, index);
|
|
272
286
|
case 'clear-filter':
|
|
273
287
|
return jobGraphClearFilter(jobGraph, op.phase, index);
|
|
288
|
+
case 'set-sql-transform':
|
|
289
|
+
throw new Error(`Operation ${index} (set-sql-transform): SQL transform edits are only supported for ` +
|
|
290
|
+
`template-backed log-reducer jobs in the CLI. This job is a direct job graph. `);
|
|
291
|
+
case 'set-transform-chain':
|
|
292
|
+
case 'clear-transform-chain':
|
|
293
|
+
throw new Error(`Operation ${index} (${op.op}): transform-chain edits are only supported for ` +
|
|
294
|
+
`template-backed log-reducer jobs in the CLI. This job is a direct job graph. `);
|
|
274
295
|
case 'add-source':
|
|
275
296
|
return jobGraphAddSource(jobGraph, op.source, index);
|
|
276
297
|
case 'remove-source':
|
|
@@ -849,9 +870,9 @@ function rawFilterNameForPhase(phase, index, opLabel) {
|
|
|
849
870
|
return RAW_PRE_WAREHOUSE_FILTER;
|
|
850
871
|
case 'pre-exceptions':
|
|
851
872
|
return RAW_PRE_EXCEPTIONS_FILTER;
|
|
852
|
-
|
|
853
|
-
throw new Error(`Operation ${index} (${opLabel}): phase "
|
|
854
|
-
|
|
873
|
+
default:
|
|
874
|
+
throw new Error(`Operation ${index} (${opLabel}): phase "${phase}" has no canonical UI raw-graph stage. ` +
|
|
875
|
+
ACCEPTED_TRANSFORM_PHASES_MESSAGE);
|
|
855
876
|
}
|
|
856
877
|
}
|
|
857
878
|
function assertRawUiLogGraph(jobGraph, opLabel, index, requiredNames) {
|
|
@@ -1096,21 +1117,255 @@ function applyRemoveParser(input, name, index) {
|
|
|
1096
1117
|
}
|
|
1097
1118
|
input.parsers.splice(idx, 1);
|
|
1098
1119
|
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Map a filter phase to its template `transforms` slot key. Only the three
|
|
1122
|
+
* transform-backed phases are accepted; any other phase fails with the shared
|
|
1123
|
+
* accepted-phases error.
|
|
1124
|
+
*/
|
|
1125
|
+
function phaseToTransformSlot(phase, index, opLabel) {
|
|
1126
|
+
switch (phase) {
|
|
1127
|
+
case 'pre-parser':
|
|
1128
|
+
return 'preParser';
|
|
1129
|
+
case 'pre-warehouse':
|
|
1130
|
+
return 'preWarehouse';
|
|
1131
|
+
case 'pre-exceptions':
|
|
1132
|
+
return 'preExceptions';
|
|
1133
|
+
default:
|
|
1134
|
+
throw new Error(`Operation ${index} (${opLabel}): phase "${phase}" is not accepted. ${ACCEPTED_TRANSFORM_PHASES_MESSAGE}`);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
function makePassthroughNode() {
|
|
1138
|
+
return { kind: PassthroughNodeKind.passthrough_node };
|
|
1139
|
+
}
|
|
1140
|
+
function makeDropNode() {
|
|
1141
|
+
return { kind: DropNodeKind.drop_node };
|
|
1142
|
+
}
|
|
1143
|
+
function makeMainStreamNode(mainStream) {
|
|
1144
|
+
return mainStream === 'drop' ? makeDropNode() : makePassthroughNode();
|
|
1145
|
+
}
|
|
1146
|
+
function nodeKind(node) {
|
|
1147
|
+
return node?.kind;
|
|
1148
|
+
}
|
|
1149
|
+
export function isDropNode(node) {
|
|
1150
|
+
return nodeKind(node) === DropNodeKind.drop_node;
|
|
1151
|
+
}
|
|
1152
|
+
export function isPassthroughNode(node) {
|
|
1153
|
+
return nodeKind(node) === PassthroughNodeKind.passthrough_node;
|
|
1154
|
+
}
|
|
1155
|
+
export function isSqlNode(node) {
|
|
1156
|
+
return nodeKind(node) === SqlNodeKind.sql_node;
|
|
1157
|
+
}
|
|
1158
|
+
export function isConditionNode(node) {
|
|
1159
|
+
return nodeKind(node) === ConditionNodeKind.condition_node;
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Build a CLI-managed filter gate routing kept events into `passSide` and
|
|
1163
|
+
* dropping the rest. A normal keep filter passes `thenAction`; an inverted
|
|
1164
|
+
* filter swaps the branches. `maxLateEventTimestampDelta` becomes the gate's
|
|
1165
|
+
* root `lateDataDuration`, matching the previous `set-filter` behavior.
|
|
1166
|
+
*/
|
|
1167
|
+
function makeFilterGate(filter, passSide) {
|
|
1168
|
+
const drop = makeDropNode();
|
|
1169
|
+
const gate = {
|
|
1170
|
+
kind: ConditionNodeKind.condition_node,
|
|
1171
|
+
predicate: filter.predicate,
|
|
1172
|
+
thenAction: filter.inverted ? drop : passSide,
|
|
1173
|
+
elseAction: filter.inverted ? passSide : drop,
|
|
1174
|
+
};
|
|
1175
|
+
if (filter.maxLateEventTimestampDelta !== undefined) {
|
|
1176
|
+
gate.lateDataDuration = filter.maxLateEventTimestampDelta;
|
|
1177
|
+
}
|
|
1178
|
+
return gate;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Detect whether `root` is an unambiguous CLI-managed filter gate: a
|
|
1182
|
+
* `ConditionNode` with exactly one terminal `DropNode` branch. Returns the pass
|
|
1183
|
+
* side and inversion, or `undefined` when `root` is not a condition or has
|
|
1184
|
+
* meaningful (non-drop) work on both branches — a chain the CLI must not guess
|
|
1185
|
+
* at.
|
|
1186
|
+
*/
|
|
1187
|
+
function findSimpleRootFilterGate(root) {
|
|
1188
|
+
if (!isConditionNode(root))
|
|
1189
|
+
return undefined;
|
|
1190
|
+
const cond = root;
|
|
1191
|
+
const thenIsDrop = isDropNode(cond.thenAction);
|
|
1192
|
+
const elseIsDrop = isDropNode(cond.elseAction);
|
|
1193
|
+
// Exactly one branch must be a drop; both-drop or neither-drop is ambiguous.
|
|
1194
|
+
if (thenIsDrop === elseIsDrop)
|
|
1195
|
+
return undefined;
|
|
1196
|
+
return {
|
|
1197
|
+
passSide: elseIsDrop ? cond.thenAction : cond.elseAction,
|
|
1198
|
+
inverted: !elseIsDrop,
|
|
1199
|
+
lateDataDuration: cond.lateDataDuration,
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Install or update a filter gate at the phase's transform slot:
|
|
1204
|
+
* - empty slot: `IF filter THEN PASSTHROUGH ELSE DROP`
|
|
1205
|
+
* - existing simple gate: rebuild with the new predicate, keeping the pass side
|
|
1206
|
+
* and inheriting `inverted`/`lateDataDuration` when the patch omits them (raw
|
|
1207
|
+
* merge semantics — a predicate-only edit must not flip keep↔drop)
|
|
1208
|
+
* - any other existing chain (e.g. a SqlNode): `IF filter THEN <chain> ELSE DROP`
|
|
1209
|
+
*/
|
|
1099
1210
|
function applySetFilter(input, phase, filter, index) {
|
|
1100
1211
|
if (!filter || typeof filter !== 'object') {
|
|
1101
1212
|
throw new Error(`Operation ${index} (set-filter): filter must be an object`);
|
|
1102
1213
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1214
|
+
if (!isEventPredicate(filter.predicate)) {
|
|
1215
|
+
throw new Error(`Operation ${index} (set-filter): filter.predicate must be an EventPredicate object with a "type"`);
|
|
1216
|
+
}
|
|
1217
|
+
const slot = phaseToTransformSlot(phase, index, 'set-filter');
|
|
1218
|
+
const transforms = input.transforms ?? {};
|
|
1219
|
+
const existing = transforms[slot];
|
|
1220
|
+
const match = existing === undefined ? undefined : findSimpleRootFilterGate(existing);
|
|
1221
|
+
const passSide = existing === undefined ? makePassthroughNode() : match?.passSide ?? existing;
|
|
1222
|
+
const gateFilter = match
|
|
1223
|
+
? {
|
|
1224
|
+
...filter,
|
|
1225
|
+
inverted: filter.inverted ?? match.inverted,
|
|
1226
|
+
maxLateEventTimestampDelta: filter.maxLateEventTimestampDelta ?? match.lateDataDuration,
|
|
1227
|
+
}
|
|
1228
|
+
: filter;
|
|
1229
|
+
transforms[slot] = makeFilterGate(gateFilter, passSide);
|
|
1230
|
+
input.transforms = transforms;
|
|
1106
1231
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1232
|
+
/**
|
|
1233
|
+
* Remove a root filter gate from the phase's transform slot, replacing the slot
|
|
1234
|
+
* with the gate's pass side. No-ops on an empty slot. Fails clearly when the
|
|
1235
|
+
* slot holds a chain that is not an unambiguous filter gate, rather than
|
|
1236
|
+
* guessing which branch is the filter.
|
|
1237
|
+
*/
|
|
1238
|
+
function applyClearFilter(input, phase, index) {
|
|
1239
|
+
const slot = phaseToTransformSlot(phase, index, 'clear-filter');
|
|
1240
|
+
const transforms = input.transforms;
|
|
1241
|
+
const existing = transforms?.[slot];
|
|
1242
|
+
if (existing === undefined)
|
|
1111
1243
|
return;
|
|
1112
|
-
|
|
1113
|
-
|
|
1244
|
+
const match = findSimpleRootFilterGate(existing);
|
|
1245
|
+
if (!match) {
|
|
1246
|
+
const detail = isConditionNode(existing)
|
|
1247
|
+
? 'contains a branching transform chain, not a simple filter gate'
|
|
1248
|
+
: `contains a ${nodeKind(existing) ?? 'non-condition'} at its root, not a filter gate`;
|
|
1249
|
+
throw new Error(`Operation ${index} (clear-filter): template transform slot "${slot}" ${detail}. ` +
|
|
1250
|
+
`Use set-transform-chain to edit it explicitly.`);
|
|
1251
|
+
}
|
|
1252
|
+
transforms[slot] = match.passSide;
|
|
1253
|
+
input.transforms = transforms;
|
|
1254
|
+
}
|
|
1255
|
+
/** True when `value` looks like an `EventPredicate` (an object carrying a string `type` discriminator). */
|
|
1256
|
+
function isEventPredicate(value) {
|
|
1257
|
+
return isRecord(value) && typeof value['type'] === 'string';
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Replace the phase's transform slot with a hand-authored chain root. The CLI
|
|
1261
|
+
* does not introspect or validate the tree beyond requiring an object; the
|
|
1262
|
+
* template compiler remains the final authority.
|
|
1263
|
+
*/
|
|
1264
|
+
function applySetTransformChain(input, phase, root, index) {
|
|
1265
|
+
if (!isRecord(root)) {
|
|
1266
|
+
throw new Error(`Operation ${index} (set-transform-chain): root must be a chain-node object`);
|
|
1267
|
+
}
|
|
1268
|
+
const slot = phaseToTransformSlot(phase, index, 'set-transform-chain');
|
|
1269
|
+
const transforms = input.transforms ?? {};
|
|
1270
|
+
transforms[slot] = root;
|
|
1271
|
+
input.transforms = transforms;
|
|
1272
|
+
}
|
|
1273
|
+
/** Remove a phase's entire transform slot (distinct from `clear-filter`, which removes only a filter gate). */
|
|
1274
|
+
function applyClearTransformChain(input, phase, index) {
|
|
1275
|
+
const slot = phaseToTransformSlot(phase, index, 'clear-transform-chain');
|
|
1276
|
+
if (input.transforms) {
|
|
1277
|
+
Reflect.deleteProperty(input.transforms, slot);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Replace the phase's transform slot with a generated `SqlNode` chain. Any
|
|
1282
|
+
* existing chain is replaced — callers needing to preserve or merge a chain
|
|
1283
|
+
* must use `set-transform-chain`.
|
|
1284
|
+
*/
|
|
1285
|
+
function applySetSqlTransform(input, phase, sqlOperation, outputRouting, mainStream, gate, index) {
|
|
1286
|
+
const slot = phaseToTransformSlot(phase, index, 'set-sql-transform');
|
|
1287
|
+
validateSqlTransform(sqlOperation, outputRouting, mainStream, gate, index);
|
|
1288
|
+
const sqlChain = {
|
|
1289
|
+
kind: SqlNodeKind.sql_node,
|
|
1290
|
+
sqlOperation,
|
|
1291
|
+
outputRouting,
|
|
1292
|
+
next: makeMainStreamNode(mainStream),
|
|
1293
|
+
};
|
|
1294
|
+
// An ungated drop (`mainStream: 'drop'`, no gate) still needs a condition node
|
|
1295
|
+
// wrapper with a match-all (`*`) predicate. The `elseAction` passthrough is
|
|
1296
|
+
// unreachable under `*`, but it is load-bearing, NOT dead code: it is the SQL
|
|
1297
|
+
// node's connected main output edge. A bare `sql-node` whose `next` is a drop
|
|
1298
|
+
// compiles to a disconnected `chainOut` in the Flink graph (see ENGT-4852 fix);
|
|
1299
|
+
// the passthrough else-branch is what keeps that main output connected. Do not
|
|
1300
|
+
// "simplify" it away — `test_setSqlTransform_noGate_dropMainStream_buildsMatchAllConditionNode`
|
|
1301
|
+
// pins this shape.
|
|
1302
|
+
const effectiveGate = gate ?? { type: DatadogQueryPredicateType.datadog_query, query: '*' };
|
|
1303
|
+
const root = mainStream === 'passthrough' && gate === undefined
|
|
1304
|
+
? sqlChain
|
|
1305
|
+
: {
|
|
1306
|
+
kind: ConditionNodeKind.condition_node,
|
|
1307
|
+
predicate: effectiveGate,
|
|
1308
|
+
thenAction: sqlChain,
|
|
1309
|
+
elseAction: makePassthroughNode(),
|
|
1310
|
+
};
|
|
1311
|
+
const transforms = input.transforms ?? {};
|
|
1312
|
+
transforms[slot] = root;
|
|
1313
|
+
input.transforms = transforms;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Validate a `set-sql-transform` op before mutating the input, so malformed
|
|
1317
|
+
* SQL fails with a readable CLI error ahead of `job:draft`.
|
|
1318
|
+
*/
|
|
1319
|
+
function validateSqlTransform(sqlOperation, outputRouting, mainStream, gate, index) {
|
|
1320
|
+
const fail = (detail) => {
|
|
1321
|
+
throw new Error(`Operation ${index} (set-sql-transform): ${detail}`);
|
|
1322
|
+
};
|
|
1323
|
+
if (!isRecord(sqlOperation) || sqlOperation['type'] !== SqlOperationType.sql_operation) {
|
|
1324
|
+
fail(`sqlOperation must be an object with type "${SqlOperationType.sql_operation}"`);
|
|
1325
|
+
}
|
|
1326
|
+
const statements = sqlOperation.statements;
|
|
1327
|
+
if (!Array.isArray(statements) || statements.length === 0) {
|
|
1328
|
+
fail('sqlOperation.statements must be a non-empty array');
|
|
1329
|
+
}
|
|
1330
|
+
const outputNames = statements
|
|
1331
|
+
.filter(s => s.type === SqlOutputStatementType.sql_output)
|
|
1332
|
+
.map(s => s.outputName);
|
|
1333
|
+
if (outputNames.length === 0) {
|
|
1334
|
+
fail('sqlOperation.statements must include at least one sql_output statement');
|
|
1335
|
+
}
|
|
1336
|
+
const seen = new Set();
|
|
1337
|
+
for (const name of outputNames) {
|
|
1338
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
1339
|
+
fail('every sql_output statement must have a non-empty outputName');
|
|
1340
|
+
}
|
|
1341
|
+
if (seen.has(name)) {
|
|
1342
|
+
fail(`duplicate sql_output outputName "${name}"; output names must be unique`);
|
|
1343
|
+
}
|
|
1344
|
+
seen.add(name);
|
|
1345
|
+
}
|
|
1346
|
+
if (!isRecord(outputRouting)) {
|
|
1347
|
+
fail('outputRouting must be an object');
|
|
1348
|
+
}
|
|
1349
|
+
if (typeof mainStream !== 'string' || !SQL_TRANSFORM_MAIN_STREAMS.has(mainStream)) {
|
|
1350
|
+
fail(`mainStream must be one of: ${[...SQL_TRANSFORM_MAIN_STREAMS].join(', ')}`);
|
|
1351
|
+
}
|
|
1352
|
+
for (const [key, target] of Object.entries(outputRouting)) {
|
|
1353
|
+
if (!seen.has(key)) {
|
|
1354
|
+
fail(`outputRouting key "${key}" does not match any sql_output outputName`);
|
|
1355
|
+
}
|
|
1356
|
+
if (typeof target !== 'string' || !SQL_OUTPUT_ROUTING_TARGETS.has(target)) {
|
|
1357
|
+
fail(`outputRouting["${key}"] = ${JSON.stringify(target)} is not a valid target step. ` +
|
|
1358
|
+
`Valid targets: ${[...SQL_OUTPUT_ROUTING_TARGETS].join(', ')}.`);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
for (const name of seen) {
|
|
1362
|
+
if (!Object.prototype.hasOwnProperty.call(outputRouting, name)) {
|
|
1363
|
+
fail(`sql_output "${name}" has no route in outputRouting`);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (gate !== undefined && !isEventPredicate(gate)) {
|
|
1367
|
+
fail('gate must be an EventPredicate object with a "type"');
|
|
1368
|
+
}
|
|
1114
1369
|
}
|
|
1115
1370
|
function applyAddSource(input, source, index) {
|
|
1116
1371
|
assertOperationIdentity(source, 'add-source', index, 'source');
|
|
@@ -1343,12 +1598,12 @@ const INPUT_FIELD_TOUCHES = {
|
|
|
1343
1598
|
rawSinkConfig: 'sink',
|
|
1344
1599
|
datasetId: 'sink',
|
|
1345
1600
|
conditionalDatasets: 'sink',
|
|
1601
|
+
confirmedDeliveryEnabled: 'sink',
|
|
1346
1602
|
reducer: 'transform',
|
|
1347
1603
|
parsers: 'transform',
|
|
1348
|
-
|
|
1604
|
+
transforms: 'transform',
|
|
1349
1605
|
exceptions: 'transform',
|
|
1350
1606
|
sampler: 'transform',
|
|
1351
|
-
sqlOperations: 'transform',
|
|
1352
1607
|
};
|
|
1353
1608
|
/**
|
|
1354
1609
|
* Classify a generic `set/unset-input-field` path by its top-level field.
|