@@ -190,12 +190,9 @@ func findDateMathFn(node expr.Node) BoundaryFns {
190190 // Only handle BETWEEN operator with specific node types
191191 if n .Operator .T == lex .TokenBetween && len (n .Args ) == 3 {
192192 // Check if first arg is IdentityNode and other two are StringNodes
193- _ , isFirstIdentity := n .Args [0 ].(* expr.IdentityNode )
194- _ , isSecondString := n .Args [1 ].(* expr.StringNode )
195- _ , isThirdString := n .Args [2 ].(* expr.StringNode )
196-
197- if isFirstIdentity && isSecondString && isThirdString {
198- fns = append (fns , findBoundaryForBetween (n ))
193+ fn := findBoundaryForBetween (n )
194+ if fn != nil {
195+ fns = append (fns , fn )
199196 return fns
200197 }
201198 }
@@ -222,67 +219,76 @@ func findDateMathFn(node expr.Node) BoundaryFns {
222219 return fns
223220}
224221
225- // Ct = Comparison time, left hand side of expression
226- // Lb = Relative time result of Lower Bound Anchor Time offset by datemath "now-3d"
227- // Ub = Relative time result of Upper Bound Anchor Time offset by datemath "now+3d"
228- // Bt = Boundary time = calculated time at which expression will change boolean expression value
229- // example: FILTER Ct BETWEEN Lb AND Ub
230- // WHERE "now" = 01/22/2025 00:00:00
222+ // findBoundaryForBetween calculates the next time boundary for a BETWEEN expression
223+ // with date math boundaries. It handles expressions like:
224+ //
225+ // time_column BETWEEN "now-3d" AND "now+3d"
226+ //
227+ // The function returns a boundary function that:
228+ // 1. Evaluates the comparison time (Ct) against the window boundaries
229+ // 2. Determines when the expression's boolean value will change
230+ // 3. Returns the appropriate re-evaluation time
231231//
232- // "Lb" = 01/19/2025 00:00:00
233- // "Ub" = 01/25/2025 00:00:00
232+ // Example:
234233//
235- // NOTE: When evaluating expressions like this, an entity "enters" the expression by passing the upper bound and sliding into the window
236- // and "exits" the expression by passing the lower bound and sliding out of the window. Although the "Upper Bound" is after the "Lower Bound",
237- // the order of entry and exit is reversed. This example should show how the entity appears to move backwards as the window moves forward.
238- // Example timeline: Ct = 01/22/2025 00:00:00
239- // Day 0: now = 01/22/2025 00:00:00 === Lb--Ct++Ub+++++++++
240- // Day 1: now = 01/23/2025 00:00:00 === -Lb-Ct+++Ub++++++++
241- // Day 2: now = 01/24/2025 00:00:00 === --LbCt++++Ub+++++++
242- // Day 3: now = 01/25/2025 00:00:00 === ---CLtb+++++Ub+++++ Ct == Lb
243- // Day 4: now = 01/26/2025 00:00:00 === ---CtLb++++++Ub++++
244- // Day 5: now = 01/27/2025 00:00:00 === ---Ct-Lb++++++Ub+++
245- // Day 6: now = 01/28/2025 00:00:00 === ---Ct--Lb++++++Ub++
246- // Day 7: now = 01/29/2025 00:00:00 === ---Ct---Lb++++++Ub+
247- // Day 8: now = 01/30/2025 00:00:00 === ---Ct----Lb++++++Ub
234+ // Input: time_column BETWEEN "now-3d" AND "now+3d"
235+ // When: now = 2025-01-22
236+ // Window: 2025-01-19 to 2025-01-25
248237//
249- // Notice how it appears as if the entity is moving backwards through the window as the window slides forward in relation to "now".
250- // Ct = 01/01/2025 00:00:00
251- // In this case the expression itself currently evaluates to false, and is already PRIOR to the lower bound.
252- // As such it will always evaluate to false, so we can skip queueing for reevaluation.
253- // // Ct = 01/30/2025 00:00:00
254- // In this case the expression itself currently evaluates to false, and is AFTER of the prior to the upper bound.
255- // The next evaluation time should be on the day the upper bound is reached. Queue for reevaluation at (Ct - 3d) [since the Ub is now+3d].
256- // // Ct = 01/22/2025 00:00:00
257- // In this case the expression itself currently evaluates to true, and is BETWEEN the lower and upper bounds.
258- // The next evaluation time should be on the day the lower bound is reached. Queue for reevaluation at (Ct + 3d) [since the Lb is now-3d].
259- // Notice that we are queuing for reevaluation based on the inverse of the corresponding bound's offset. This is made a bit easier if
260- // the expression is a static date, rather than a relative one. As we can use that directly as the re-evaluation time.
238+ // If Ct = 2025-01-01 (left side of window):
239+ // - Expression is false
240+ // - Will always be false as window is moving forward
241+ // - Returns zero time (no re-evaluation needed)
242+ //
243+ // If Ct = 2025-01-30 (right side of window):
244+ // - Expression is false
245+ // - Will become true when window catches up (enter event)
246+ // - Returns re-evaluation time when this will enter the window
247+ //
248+ // If Ct = 2025-01-22 (inside window):
249+ // - Expression is true
250+ // - Will become false when Ct passes lower bound (exit event)
251+ // - Returns re-evaluation time when this will be exit the window
261252func findBoundaryForBetween (n * expr.TriNode ) func (d * DateConverter , ctx expr.EvalIncludeContext ) {
253+
254+ // Check if first arg is IdentityNode and other two are StringNodes
255+ _ , isFirstIdentity := n .Args [0 ].(* expr.IdentityNode )
256+ _ , isSecondString := n .Args [1 ].(* expr.StringNode )
257+ _ , isThirdString := n .Args [2 ].(* expr.StringNode )
258+
259+ if ! isFirstIdentity || ! isSecondString || ! isThirdString {
260+ return nil
261+ }
262+ arg1 , arg2 , arg3 := n .Args [0 ], n .Args [1 ], n .Args [2 ]
263+
264+ // datemath only if both date args are relative to an anchor time like "now-1d"
265+ val2 := strings .ToLower (arg2 .(* expr.StringNode ).Text )
266+ val3 := strings .ToLower (arg3 .(* expr.StringNode ).Text )
267+ if ! nowRegex .MatchString (val2 ) || ! nowRegex .MatchString (val3 ) {
268+ return nil
269+ }
270+
262271 return func (d * DateConverter , ctx expr.EvalIncludeContext ) {
263- arg1 , arg2 , arg3 := n .Args [0 ], n .Args [1 ], n .Args [2 ]
264272
265273 lhv , ok := Eval (ctx , arg1 )
266274 if ! ok {
267275 return
268276 }
269277 ct , ok := value .ValueToTime (lhv )
270278 if ! ok {
271- // may be not a time field, so ignore doing any update
279+ d . err = fmt . Errorf ( "could not convert %T: %v to time.Time" , lhv , lhv )
272280 return
273281 }
274282
275- val2 := strings .ToLower (arg2 .(* expr.StringNode ).Text )
276283 date1 , err := datemath .EvalAnchor (d .at , val2 )
277284 if err != nil {
278- // may be not a valid date expression, so ignore doing any update
285+ d . err = err
279286 return
280287 }
281288
282- val3 := strings .ToLower (arg3 .(* expr.StringNode ).Text )
283289 date2 , err := datemath .EvalAnchor (d .at , val3 )
284290 if err != nil {
285- // may be not a valid date expression, so ignore doing any update
291+ d . err = err
286292 return
287293 }
288294
0 commit comments