aboutsummaryrefslogtreecommitdiff
path: root/core/operator/window.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/operator/window.go')
-rw-r--r--core/operator/window.go110
1 files changed, 110 insertions, 0 deletions
diff --git a/core/operator/window.go b/core/operator/window.go
new file mode 100644
index 0000000..610f6e3
--- /dev/null
+++ b/core/operator/window.go
@@ -0,0 +1,110 @@
1package operator
2
3import (
4 "context"
5 "fmt"
6
7 "flux/core"
8)
9
10// WindowFunction is for user interface implements the behaviour
11type WindowFunction[IN, OUT any] interface {
12 Apply(ctx context.Context, key string, window core.Window, in []IN, out Emitter[OUT]) error
13}
14
15type WindowOperator[IN, OUT any] struct {
16 assigner core.WindowAssigner[IN]
17 userfunc WindowFunction[IN, OUT]
18 backend core.StateBackend
19 timeSerivce core.InternalTimerService
20
21 windowState core.ValueState[[]IN]
22}
23
24func NewWindowOperator[IN, OUT any](assigner core.WindowAssigner[IN], userfunc WindowFunction[IN, OUT]) *WindowOperator[IN, OUT] {
25 return &WindowOperator[IN, OUT]{
26 assigner: assigner,
27 userfunc: userfunc,
28 timeSerivce: core.NewTimerService(),
29 }
30}
31
32func (o *WindowOperator[IN, OUT]) Open(ctx context.Context) error {
33 be, ok := core.ExtractStateBackend(ctx)
34 if !ok {
35 return fmt.Errorf("cannot extract state backend, got: %v", be)
36 }
37 o.backend = be
38 windowState, err := be.GetValueState(ctx, "window-elements")
39 if err != nil {
40 return fmt.Errorf("cannot get value state window-elements error: %w", err)
41 }
42
43 o.windowState = core.CastValueType[[]IN](windowState)
44
45 return nil
46}
47
48func (o *WindowOperator[IN, OUT]) Process(ctx context.Context, record core.StreamRecord[IN], out Emitter[OUT]) error {
49 switch record.Type {
50 case core.RecordData:
51 // current windows state will hold all record in window
52 // assign and get list window
53 windows := o.assigner.AssignWindows(record.Value, record.Timestamp)
54 for _, w := range windows {
55 o.backend.SetCurrentNamespace(w)
56 o.backend.SetCurrentKey(record.Key)
57 o.timeSerivce.Register(w.MaxTimestamp(), record.Key, w)
58
59 currentWindowState, err := o.windowState.Value(ctx)
60 if err != nil {
61 return err
62 }
63
64 currentWindowState = append(currentWindowState, record.Value)
65
66 o.windowState.Update(ctx, currentWindowState)
67 }
68
69 return nil
70 case core.RecordWatermark:
71 deadlines := o.timeSerivce.AdvanceWatermark(record.Timestamp)
72
73 for _, timer := range deadlines {
74 o.backend.SetCurrentKey(timer.Key)
75 o.backend.SetCurrentNamespace(timer.Namespace)
76
77 currentWindowState, err := o.windowState.Value(ctx)
78 if err != nil {
79 return err
80 }
81
82 // NOTE: this is workaround create inline window, refactor later
83
84 if err := o.userfunc.Apply(ctx, timer.Key, timer.Namespace.(core.Window), currentWindowState, out); err != nil {
85 return err
86 }
87
88 if err := o.windowState.Clear(ctx); err != nil {
89 return err
90 }
91
92 }
93
94 return out.Emit(ctx, core.StreamRecord[OUT]{
95 Timestamp: record.Timestamp,
96 Type: core.RecordWatermark,
97 })
98 default:
99 return out.Emit(ctx, core.StreamRecord[OUT]{
100 BarrierID: record.BarrierID,
101 Timestamp: record.Timestamp,
102 Key: record.Key,
103 Type: record.Type,
104 })
105 }
106}
107
108func (o *WindowOperator[IN, OUT]) Close(ctx context.Context) error {
109 return nil
110}