DaZuiZui commented on issue #18428:
URL: https://github.com/apache/iotdb/issues/18428#issuecomment-5393622269

   ## Revised addendum addressing the review blockers
   
   Thank you for reviewing the proposal against the current implementation and 
for identifying these gaps. Your comments are very helpful, and I agree with 
the points you raised. I also agree that the first three items are correctness 
and compatibility blockers.
   
   Where this revision conflicts with the previous proposal or addendum, this 
revision takes precedence. The remaining parts of the previous proposal stay 
unchanged.
   
   ### 1. Use exactly the same calendar and DST resolution as `GROUP BY TIME`
   
   The CQ implementation will not introduce the previously proposed 
preferred-anchor-offset rule for DST overlaps.
   
   CQ occurrence and RANGE calculations will reuse the same calendar conversion 
used by the current `GROUP BY TIME` month path, including the behavior of:
   
   ```java
   LocalDateTime.plusMonths(...).atZone(zoneId)
   ```
   
   The implementation will reuse `DateTimeUtils.calcPositiveIntervalByMonth` 
and its underlying `TimeDuration.calcPositiveIntervalByMonth` behavior, or 
extract that behavior into a shared helper used by both paths.
   
   The resulting rules are:
   
   - A DST overlap uses the earlier valid offset, matching the normal `atZone` 
rule.
   - A DST gap is shifted forward according to the zone transition.
   - The original anchor offset is not preferred when resolving a later overlap.
   - Calendar months are applied first.
   - The fixed-duration component is then added as elapsed ticks.
   - Fixed-only durations remain checked elapsed-tick addition.
   - The host default time zone is never used.
   
   The shared helper may be extended to support checked arithmetic and signed 
internal duration vectors needed by RANGE calculation, but the existing 
local-time resolution behavior must not change.
   
   For the example from the review:
   
   ```text
   Zone = America/New_York
   B = 2024-01-03 01:30 -05:00
   E = 10mo
   target local time = 2024-11-03 01:30
   ```
   
   both CQ and `GROUP BY TIME` will select the earlier overlap offset:
   
   ```text
   2024-11-03 01:30 -04:00 = 05:30Z
   ```
   
   Tests will assert exact instant equality between CQ-generated boundaries and 
`GROUP BY TIME` boundaries for ordinary dates, 28/29/30/31-day month 
transitions, month-end clamping, leap-day anchors, DST gaps, and DST overlaps.
   
   This replaces the preferred-`anchorOffset` rule from the previous addendum.
   
   ### 2. Make the structured wire format and mixed-version rejection 
enforceable
   
   The request will carry an explicit duration encoding version together with 
the structured duration values. For example:
   
   ```thrift
   struct TCQDuration {
     1: required i64 monthPart
     2: required i64 nonMonthDuration
   }
   
   struct TCreateCQReq {
     // Existing fields 1-10 remain unchanged.
   
     11: optional i16 durationEncodingVersion
     12: optional TCQDuration everyDuration
     13: optional TCQDuration startOffsetDuration
     14: optional TCQDuration endOffsetDuration
     15: optional bool boundaryExplicit
   }
   ```
   
   For `durationEncodingVersion = 1`:
   
   - `everyDuration`, `startOffsetDuration`, `endOffsetDuration`, and 
`boundaryExplicit` must all be present.
   - Partially structured requests are rejected.
   - Unknown encoding versions are rejected.
   - Structured durations are authoritative.
   - All month values, fixed values, arithmetic operations, and narrowing 
conversions are checked.
   - A new DataNode sets the encoding version and all structured fields for 
every newly created CQ, including fixed-only CQs.
   
   A request without `durationEncodingVersion` is a legacy-protocol request.
   
   A new ConfigNode will reject a markerless request at the CREATE ingress 
instead of automatically interpreting it as fixed-only. This deliberately 
prevents an old DataNode from sending a calendar duration that has already been 
flattened to a legacy `i64` value.
   
   During a rolling upgrade, this means an old DataNode cannot create a CQ 
through a new ConfigNode. Existing persisted CQs are unaffected and continue to 
run with their existing semantics.
   
   Legacy representations encountered while loading existing procedures, plans, 
metadata, or snapshots remain supported and are normalized as legacy 
fixed-duration CQs. The rejection applies to new CQ creation at the RPC 
ingress, not to recovery of existing state.
   
   Calendar-bearing version-1 requests additionally require a hard capability 
barrier:
   
   1. Before forwarding the request, the SQL-ingress DataNode verifies that all 
registered ConfigNodes and all registered DataNodes capable of accepting client 
SQL support duration encoding version 1.
   2. The receiving ConfigNode repeats the same verification against 
authoritative cluster node information before accepting the request.
   3. Any old, unknown, or unsupported node version causes the request to be 
rejected with a clear compatibility error.
   4. A calendar-bearing request must never be submitted while an old 
ConfigNode could deserialize or apply it.
   5. The same barrier applies before the new consensus plan, procedure 
serialization, or CQ snapshot representation can be produced.
   
   The existing required legacy `i64` fields remain unchanged for wire 
compatibility and are populated as follows:
   
   - When all three structured durations have `monthPart == 0`, the legacy 
fields contain the exact fixed-duration values.
   - The ConfigNode verifies that the legacy values and structured fixed values 
are identical.
   - If any effective duration has a nonzero `monthPart`, there is no valid 
legacy representation.
   - In that case, all three legacy duration fields are set to the invalid 
sentinel value `0`.
   - New readers validate the version-1 structured representation and do not 
use the sentinel fields.
   - The all-ConfigNode capability barrier guarantees that an old reader cannot 
receive such a calendar request.
   - The sentinel does not replace the reader barrier and must never be treated 
as a compatibility representation.
   - A calendar duration is never approximated as 28, 30, or 365 days in a 
legacy field.
   
   The following cases are rejected:
   
   - a marker without all structured fields;
   - structured fields without a supported marker;
   - an unknown encoding version;
   - conflicting fixed legacy and structured values;
   - a calendar-bearing request while any required node capability is 
unavailable;
   - a new markerless CREATE request received by a new ConfigNode.
   
   This closes the old-DataNode-to-new-ConfigNode ingress hole and defines both 
the required legacy values and the hard reader barrier.
   
   ### 3. Advance `nextOccurrenceIndex` using an exact consensus CAS and 
fencing token
   
   `nextOccurrenceIndex` is defined as the first occurrence that has not yet 
been durably completed or discarded.
   
   Progress advancement will use a consensus plan containing at least:
   
   ```text
   cqId
   cqToken
   expectedIndex
   targetIndex
   ```
   
   The CQ token acts as the CQ generation and fencing token. The state machine 
atomically applies:
   
   ```text
   if storedToken == cqToken
      and nextOccurrenceIndex == expectedIndex:
       nextOccurrenceIndex = targetIndex
   ```
   
   The possible results are:
   
   - If the CQ does not exist, the callback stops.
   - If `cqToken` does not match, the callback is fenced and stops.
   - If the stored index equals `expectedIndex`, the state machine updates it 
to `targetIndex` and returns `ADVANCED`.
   - If the stored index is greater than `expectedIndex`, the callback is stale 
or duplicated. The state machine returns `STALE`, and that callback must not 
schedule another task.
   - If the stored index is less than `expectedIndex`, the state machine 
returns a consistency error, and the callback must not schedule another task.
   
   For `BLOCKED`:
   
   ```text
   expectedIndex = n
   targetIndex = n + 1
   ```
   
   For `DISCARD`, the callback time is captured exactly once after successful 
execution of occurrence `n`:
   
   ```text
   callbackTime = captured once
   lowerBound(t) = min { k >= 0 | executionTime(k) >= t }
   
   expectedIndex = n
   targetIndex = max(n + 1, lowerBound(callbackTime))
   ```
   
   This definition covers callback time exactly equal to an occurrence, delayed 
callbacks, multiple missed occurrences, clock rollback, and stale callbacks 
from a previous leader.
   
   A callback may schedule the target occurrence only after its exact CAS 
returns `ADVANCED`. It must not increment an in-memory occurrence index or 
schedule another task after a failed progress write, token mismatch, `STALE` 
result, or consistency error.
   
   If a consensus write fails or its result is ambiguous, the callback retries 
the same transition using the same `cqToken`, `expectedIndex`, and 
`targetIndex`. It does not calculate a different target and does not rebase the 
transition on local state.
   
   If the first write committed but its acknowledgement was lost, the retry 
observes that the stored index is already greater than `expectedIndex`, 
receives `STALE`, and stops without creating a competing schedule chain.
   
   Leader recovery and scheduler reconciliation always read the persisted 
`(cqToken, nextOccurrenceIndex)` and install at most one task for that 
persisted pair. A stale callback never derives a new chain from its local 
execution time.
   
   The recovery flow is:
   
   ```text
   persisted boundary + persisted durations
   + persisted zone + persisted nextOccurrenceIndex
   -> calculate exact occurrence
   -> install one task identified by (cqToken, occurrenceIndex)
   ```
   
   Execution remains at-least-once. If query execution succeeds but the CAS is 
not durably committed before a failure, recovery may execute the same 
occurrence again. This prevents competing schedule chains but does not 
introduce an exactly-once query execution protocol.
   
   Tests will cover old- and new-leader callbacks racing on the same index, 
duplicated callbacks, lost or ambiguous consensus responses, token mismatch 
after DROP and re-CREATE with the same CQ ID, CAS retry with the same expected 
index, `DISCARD` with multiple missed occurrences, equality and clock rollback 
cases, and recovery without an off-by-one shift.
   
   ### 4. Define a deterministic and implementable duration partial order
   
   After normalization, a user-provided duration is represented as:
   
   ```text
   D = (M, F)
   ```
   
   where `M >= 0` is the calendar-month component, `F >= 0` is the fixed-tick 
component, and at least one component must be positive when the clause requires 
a positive duration.
   
   Duration-to-duration validation uses component-wise dominance:
   
   ```text
   D1 >= D2 iff M1 >= M2 and F1 >= F2
   D1 > D2  iff D1 >= D2 and D1 != D2
   ```
   
   Examples:
   
   ```text
   1mo3d > 1mo       // accepted
   2mo >= 1mo        // accepted
   12mo == 1y        // normalized equality
   1mo vs 30d        // incomparable
   30d vs 1mo        // incomparable
   2mo vs 1mo40d     // incomparable
   ```
   
   When a required ordering is incomparable, the statement is rejected with a 
clear semantic error. This partial order is used for duration-to-duration 
constraints such as:
   
   ```text
   startOffset > endOffset
   startOffset >= EVERY
   ```
   
   No month-to-day approximation is used for these comparisons.
   
   Validation against the fixed configuration value 
`continuous_query_minimum_every_interval` uses a separate deterministic 
elapsed-time lower bound.
   
   For an EVERY duration `(M, F)`:
   
   ```text
   if M == 0:
       elapsedLowerBound = F
   else:
       elapsedLowerBound = M * 28d - 36h + F
   ```
   
   The calculation uses checked arithmetic in the configured timestamp 
precision. The bound is based on these platform-level limits:
   
   - Advancing by `M` positive Gregorian calendar months spans at least `M * 
28d` in local date/time.
   - Java `ZoneOffset` is bounded between `-18:00` and `+18:00`.
   - Therefore, the offset difference between the two resolved endpoints can 
reduce elapsed time by at most 36 hours.
   - The fixed component is applied afterwards as elapsed ticks.
   
   The CQ is accepted only when:
   
   ```text
   elapsedLowerBound >= continuous_query_minimum_every_interval
   ```
   
   Otherwise it is conservatively rejected because the minimum interval cannot 
be proven.
   
   This does not reuse 28 days per month as if it were an elapsed duration. The 
additional 36-hour offset bound accounts for DST and larger historical offset 
transitions representable by the Java time model. CREATE acceptance is 
therefore independent of the host default time zone and of 
TZDB-version-specific transition data. Overflow while calculating the bound is 
a semantic error.
   
   Tests will cover equal duration vectors, strict and non-strict 
component-wise dominance, incomparable calendar-versus-fixed durations, 
compound durations, minimum-EVERY values around the lower-bound threshold, and 
checked overflow under `ms`, `us`, and `ns` precision.
   
   ### 5. Keep CQ aligned with the existing Tree SQL duration aliases
   
   CQ will not introduce private `month` or `year` aliases in this issue.
   
   The supported calendar units are limited to the existing Tree SQL 
abbreviations:
   
   ```text
   mo
   y
   ```
   
   Unit matching remains case-insensitive, and `y` is normalized to twelve 
calendar months.
   
   The following aliases are outside the scope of this issue:
   
   ```text
   month
   months
   year
   years
   ```
   
   If full unit aliases are added later, they should be introduced through a 
separate Tree SQL grammar change covering all duration positions consistently, 
including `GROUP BY TIME`, date arithmetic, `FILL`, `SESSION`, and CQ 
`EVERY`/`RANGE`.
   
   Therefore, a CQ will consistently use:
   
   ```sql
   RESAMPLE EVERY 1mo RANGE 1mo
   ...
   GROUP BY(1mo)
   ```
   
   This replaces the previous addendum section that proposed CQ-only `month` 
and `year` aliases.
   
   ### Retained implementation decisions
   
   The following parts of the previous proposal remain unchanged:
   
   - Durations use a structured `(monthPart, nonMonthDuration)` representation.
   - `1y` is normalized to `12mo`.
   - Every occurrence is derived from the original boundary:
   
   ```text
   executionTime(n) = calendarApply(B, n * E, Z)
   ```
   
   - Occurrences are not generated by repeatedly adding a duration to the 
previously clamped occurrence.
   - RANGE endpoints are derived from the original boundary in duration-vector 
space:
   
   ```text
   startTime(n) = calendarApply(B, n * E - startOffset, Z)
   endTime(n)   = calendarApply(B, n * E - endOffset, Z)
   ```
   
   - The boundary, `boundaryExplicit`, zone, normalized durations, CQ token, 
and occurrence index are persisted.
   - Existing persisted CQs retain their legacy fixed-duration behavior.
   - Existing flattened `mo`/`y` CQs are not silently migrated; recreating a CQ 
is the explicit opt-in to calendar semantics.
   - DataNodes receive concrete query start and end timestamps.
   - `TExecuteCQ.timeout` is calculated from adjacent actual occurrence 
instants:
   
   ```text
   deltaTicks = executionTime(n + 1) - executionTime(n)
   timeoutMs = ceil(deltaTicks / ticksPerMillisecond)
   ```
   
   - Restart, leader recovery, and snapshot recovery resume from the persisted 
boundary-relative occurrence index.
   - All duration multiplication, vector arithmetic, timestamp conversion, and 
timeout conversion use checked arithmetic.
   
   ### Updated focused test plan
   
   The implementation tests will include:
   
   1. `EVERY 1mo`, `EVERY 1y`, and inherited `GROUP BY(1mo)`/`GROUP BY(1y)`.
   2. Rejection of `month`, `months`, `year`, and `years`.
   3. CQ and `GROUP BY TIME` instant equality across ordinary dates, month 
ends, leap days, DST gaps, and DST overlaps.
   4. Version-1 structured request round trips.
   5. Rejection of partial, conflicting, unknown-version, and markerless new 
requests.
   6. Mixed-version calendar CREATE rejection at both DataNode ingress and 
ConfigNode acceptance.
   7. Required legacy-field sentinel behavior and the hard ConfigNode reader 
barrier.
   8. Legacy fixed-duration request, plan, procedure, and snapshot recovery.
   9. Component-wise duration comparison and incomparable-duration rejection.
   10. Fixed minimum-EVERY lower-bound validation.
   11. CAS advancement, duplicated callbacks, leader changes, token fencing, 
and ambiguous consensus results.
   12. `BLOCKED` and `DISCARD` recovery, including equality and clock rollback.
   13. Boundary-relative compound schedules and RANGE calculations.
   14. Checked overflow and `ms`/`us`/`ns` timeout conversion.
   
   With these changes, DST alignment, mixed-version ingress protection, exact 
progress transitions, deterministic comparison rules, and Tree SQL alias 
consistency are implementation requirements.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to