@grepr/cli 1.6.30-551accf → 1.6.43-1a54abd
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 +257 -60
- package/build/dist/lib/job-patch.js.map +1 -1
- package/build/dist/lib/job-plan.d.ts.map +1 -1
- package/build/dist/lib/job-plan.js +8 -8
- 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 +491 -18
- package/build/dist/openapi/openApiTypes.d.ts.map +1 -1
- package/build/dist/openapi/openApiTypes.js +19 -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 {
|
|
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.
|
|
@@ -204,6 +212,12 @@ function applyOperation(input, op, index) {
|
|
|
204
212
|
return applySetFilter(input, op.phase, op.filter, index);
|
|
205
213
|
case 'clear-filter':
|
|
206
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) {
|
|
@@ -1097,79 +1118,254 @@ function applyRemoveParser(input, name, index) {
|
|
|
1097
1118
|
input.parsers.splice(idx, 1);
|
|
1098
1119
|
}
|
|
1099
1120
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
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.
|
|
1103
1124
|
*/
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
+
}
|
|
1110
1161
|
/**
|
|
1111
|
-
*
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
* @param index the operation index, for error messages
|
|
1116
|
-
* @param opLabel the op name, for error messages
|
|
1117
|
-
* @returns the matching `keyof SchemaTransforms`
|
|
1118
|
-
* @throws Error when the phase has no transforms slot
|
|
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.
|
|
1119
1166
|
*/
|
|
1120
|
-
function
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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;
|
|
1125
1177
|
}
|
|
1126
|
-
return
|
|
1127
|
-
}
|
|
1128
|
-
/** True when the existing slot value is a keep-style filter (then keep / else drop), i.e. not inverted. */
|
|
1129
|
-
function isInvertedConditionNode(node) {
|
|
1130
|
-
return (node.thenAction?.kind === DropNodeKind.drop_node &&
|
|
1131
|
-
node.elseAction?.kind === PassthroughNodeKind.passthrough_node);
|
|
1178
|
+
return gate;
|
|
1132
1179
|
}
|
|
1133
1180
|
/**
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1139
|
-
* `set-filter`'s merge contract.
|
|
1140
|
-
*
|
|
1141
|
-
* @param filter the incoming logs-filter (predicate + optional inverted/lateness)
|
|
1142
|
-
* @param existing the current condition node in the slot, if any, for field carry-over
|
|
1143
|
-
* @returns a condition node that compiles to the same logs-filter vertex
|
|
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.
|
|
1144
1186
|
*/
|
|
1145
|
-
function
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
const
|
|
1149
|
-
const
|
|
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;
|
|
1150
1196
|
return {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
elseAction: inverted ? keep : drop,
|
|
1155
|
-
...(lateDataDuration ? { lateDataDuration } : {}),
|
|
1197
|
+
passSide: elseIsDrop ? cond.thenAction : cond.elseAction,
|
|
1198
|
+
inverted: !elseIsDrop,
|
|
1199
|
+
lateDataDuration: cond.lateDataDuration,
|
|
1156
1200
|
};
|
|
1157
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
|
+
*/
|
|
1158
1210
|
function applySetFilter(input, phase, filter, index) {
|
|
1159
1211
|
if (!filter || typeof filter !== 'object') {
|
|
1160
1212
|
throw new Error(`Operation ${index} (set-filter): filter must be an object`);
|
|
1161
1213
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
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;
|
|
1166
1231
|
}
|
|
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
|
+
*/
|
|
1167
1238
|
function applyClearFilter(input, phase, index) {
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1239
|
+
const slot = phaseToTransformSlot(phase, index, 'clear-filter');
|
|
1240
|
+
const transforms = input.transforms;
|
|
1241
|
+
const existing = transforms?.[slot];
|
|
1242
|
+
if (existing === undefined)
|
|
1170
1243
|
return;
|
|
1171
|
-
const
|
|
1172
|
-
|
|
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
|
+
}
|
|
1173
1369
|
}
|
|
1174
1370
|
function applyAddSource(input, source, index) {
|
|
1175
1371
|
assertOperationIdentity(source, 'add-source', index, 'source');
|
|
@@ -1402,6 +1598,7 @@ const INPUT_FIELD_TOUCHES = {
|
|
|
1402
1598
|
rawSinkConfig: 'sink',
|
|
1403
1599
|
datasetId: 'sink',
|
|
1404
1600
|
conditionalDatasets: 'sink',
|
|
1601
|
+
confirmedDeliveryEnabled: 'sink',
|
|
1405
1602
|
reducer: 'transform',
|
|
1406
1603
|
parsers: 'transform',
|
|
1407
1604
|
transforms: 'transform',
|