search: SearchRequest.afterExecutionId — composite (startTime, execId) predicate
Adds an optional afterExecutionId field to SearchRequest. When combined with a non-null timeFrom, ClickHouseSearchIndex applies a strictly-after tuple predicate (start_time > ts OR (start_time = ts AND execution_id > id)) so same-millisecond exchanges can be consumed exactly once across ticks. When afterExecutionId is null, timeFrom keeps its existing >= semantics — no behaviour change for any current caller. Also adds the SearchRequest.withCursor(ts, id) wither. Threads the field through existing withInstanceIds / withEnvironment witheres. All existing positional call-sites (SearchController, ExchangeMatchEvaluator, ClickHouseSearchIndexIT, ClickHouseChunkPipelineIT) pass null for the new slot. Task 1.2 of docs/superpowers/plans/2026-04-22-per-exchange-exactly-once.md. The evaluator-side wiring that actually supplies the cursor is Task 1.5.
This commit is contained in:
@@ -9,26 +9,29 @@ import java.util.List;
|
||||
* All filter fields are nullable/optional. When null, the filter is not applied.
|
||||
* The compact constructor validates and normalizes pagination parameters.
|
||||
*
|
||||
* @param status execution status filter (COMPLETED, FAILED, RUNNING)
|
||||
* @param timeFrom inclusive start of time range
|
||||
* @param timeTo exclusive end of time range
|
||||
* @param durationMin minimum duration in milliseconds (inclusive)
|
||||
* @param durationMax maximum duration in milliseconds (inclusive)
|
||||
* @param correlationId exact correlation ID match
|
||||
* @param text global full-text search across all text fields
|
||||
* @param textInBody full-text search scoped to exchange bodies
|
||||
* @param textInHeaders full-text search scoped to exchange headers
|
||||
* @param textInErrors full-text search scoped to error messages and stack traces
|
||||
* @param routeId exact match on route_id
|
||||
* @param instanceId exact match on instance_id
|
||||
* @param processorType matches processor_types array via has()
|
||||
* @param applicationId exact match on application_id
|
||||
* @param instanceIds list of instance IDs for an IN clause (only set when drilling down to specific agents)
|
||||
* @param offset pagination offset (0-based)
|
||||
* @param limit page size (default 50, max 500)
|
||||
* @param sortField column to sort by (default: startTime)
|
||||
* @param sortDir sort direction: asc or desc (default: desc)
|
||||
* @param environment optional environment filter (e.g. "dev", "staging", "prod")
|
||||
* @param status execution status filter (COMPLETED, FAILED, RUNNING)
|
||||
* @param timeFrom inclusive start of time range
|
||||
* @param timeTo exclusive end of time range
|
||||
* @param durationMin minimum duration in milliseconds (inclusive)
|
||||
* @param durationMax maximum duration in milliseconds (inclusive)
|
||||
* @param correlationId exact correlation ID match
|
||||
* @param text global full-text search across all text fields
|
||||
* @param textInBody full-text search scoped to exchange bodies
|
||||
* @param textInHeaders full-text search scoped to exchange headers
|
||||
* @param textInErrors full-text search scoped to error messages and stack traces
|
||||
* @param routeId exact match on route_id
|
||||
* @param instanceId exact match on instance_id
|
||||
* @param processorType matches processor_types array via has()
|
||||
* @param applicationId exact match on application_id
|
||||
* @param instanceIds list of instance IDs for an IN clause (only set when drilling down to specific agents)
|
||||
* @param offset pagination offset (0-based)
|
||||
* @param limit page size (default 50, max 500)
|
||||
* @param sortField column to sort by (default: startTime)
|
||||
* @param sortDir sort direction: asc or desc (default: desc)
|
||||
* @param afterExecutionId when combined with a non-null {@code timeFrom}, applies the composite predicate
|
||||
* {@code (start_time > timeFrom) OR (start_time = timeFrom AND execution_id > afterExecutionId)}.
|
||||
* When null, {@code timeFrom} is applied as a plain {@code >=} lower bound (existing behaviour).
|
||||
* @param environment optional environment filter (e.g. "dev", "staging", "prod")
|
||||
*/
|
||||
public record SearchRequest(
|
||||
String status,
|
||||
@@ -50,6 +53,7 @@ public record SearchRequest(
|
||||
int limit,
|
||||
String sortField,
|
||||
String sortDir,
|
||||
String afterExecutionId,
|
||||
String environment
|
||||
) {
|
||||
|
||||
@@ -92,7 +96,7 @@ public record SearchRequest(
|
||||
status, timeFrom, timeTo, durationMin, durationMax, correlationId,
|
||||
text, textInBody, textInHeaders, textInErrors,
|
||||
routeId, instanceId, processorType, applicationId, resolvedInstanceIds,
|
||||
offset, limit, sortField, sortDir, environment
|
||||
offset, limit, sortField, sortDir, afterExecutionId, environment
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,7 +106,23 @@ public record SearchRequest(
|
||||
status, timeFrom, timeTo, durationMin, durationMax, correlationId,
|
||||
text, textInBody, textInHeaders, textInErrors,
|
||||
routeId, instanceId, processorType, applicationId, instanceIds,
|
||||
offset, limit, sortField, sortDir, env
|
||||
offset, limit, sortField, sortDir, afterExecutionId, env
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy with a composite {@code (start_time, execution_id)} cursor.
|
||||
* <p>
|
||||
* The resulting request applies a strictly-after tuple predicate
|
||||
* {@code (start_time > ts) OR (start_time = ts AND execution_id > afterExecutionId)},
|
||||
* enabling exactly-once consumption of same-millisecond exchanges across scheduler ticks.
|
||||
*/
|
||||
public SearchRequest withCursor(Instant ts, String afterExecutionId) {
|
||||
return new SearchRequest(
|
||||
status, ts, timeTo, durationMin, durationMax, correlationId,
|
||||
text, textInBody, textInHeaders, textInErrors,
|
||||
routeId, instanceId, processorType, applicationId, instanceIds,
|
||||
offset, limit, sortField, sortDir, afterExecutionId, environment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user