You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by di...@apache.org on 2022/05/13 05:10:08 UTC

[flink] branch master updated: [FLINK-27271][python] Introduce Globalwindows window allocator

This is an automated email from the ASF dual-hosted git repository.

dianfu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new ce6ee9cee34 [FLINK-27271][python] Introduce Globalwindows window allocator
ce6ee9cee34 is described below

commit ce6ee9cee34ff2b51acbd2928de943c6d2b26024
Author: zhangjingcun <cu...@163.com>
AuthorDate: Wed May 11 10:22:32 2022 +0800

    [FLINK-27271][python] Introduce Globalwindows window allocator
    
    This closes #19696.
---
 .../docs/dev/datastream/operators/windows.md       |  10 ++
 .../docs/dev/datastream/operators/windows.md       |  10 ++
 flink-python/pyflink/datastream/data_stream.py     |   7 +-
 .../pyflink/datastream/tests/test_window.py        |  33 +++++-
 flink-python/pyflink/datastream/window.py          | 124 ++++++++++++++++++++-
 .../pyflink/fn_execution/coder_impl_fast.pyx       |  14 ++-
 .../pyflink/fn_execution/coder_impl_slow.py        |  13 +++
 flink-python/pyflink/fn_execution/coders.py        |   9 ++
 8 files changed, 216 insertions(+), 4 deletions(-)

diff --git a/docs/content.zh/docs/dev/datastream/operators/windows.md b/docs/content.zh/docs/dev/datastream/operators/windows.md
index 0f22b6d0e27..2f9fa9393ac 100644
--- a/docs/content.zh/docs/dev/datastream/operators/windows.md
+++ b/docs/content.zh/docs/dev/datastream/operators/windows.md
@@ -470,6 +470,16 @@ input
     .<windowed transformation>(<window function>)
 ```
 {{< /tab >}}
+{{< tab "Python" >}}
+```python
+input = ...  # type: DataStream
+
+input \
+    .key_by(<key selector>) \
+    .window(GlobalWindows.create()) \
+    .<windowed transformation>(<window function>)
+```
+{{< /tab >}}
 {{< /tabs >}}
 
 ## 窗口函数(Window Functions)
diff --git a/docs/content/docs/dev/datastream/operators/windows.md b/docs/content/docs/dev/datastream/operators/windows.md
index 19cdc4426b5..a4b6a492d49 100644
--- a/docs/content/docs/dev/datastream/operators/windows.md
+++ b/docs/content/docs/dev/datastream/operators/windows.md
@@ -490,6 +490,16 @@ input
     .<windowed transformation>(<window function>)
 ```
 {{< /tab >}}
+{{< tab "Python" >}}
+```python
+input = ...  # type: DataStream
+
+input \
+    .key_by(<key selector>) \
+    .window(GlobalWindows.create()) \
+    .<windowed transformation>(<window function>)
+```
+{{< /tab >}}
 {{< /tabs >}}
 
 ## Window Functions
diff --git a/flink-python/pyflink/datastream/data_stream.py b/flink-python/pyflink/datastream/data_stream.py
index 73a0e169998..5f82dee0014 100644
--- a/flink-python/pyflink/datastream/data_stream.py
+++ b/flink-python/pyflink/datastream/data_stream.py
@@ -43,7 +43,8 @@ from pyflink.datastream.state import ValueStateDescriptor, ValueState, ListState
 from pyflink.datastream.utils import convert_to_python_obj
 from pyflink.datastream.window import (CountTumblingWindowAssigner, CountSlidingWindowAssigner,
                                        CountWindowSerializer, TimeWindowSerializer, Trigger,
-                                       WindowAssigner, WindowOperationDescriptor)
+                                       WindowAssigner, WindowOperationDescriptor,
+                                       GlobalWindowSerializer)
 from pyflink.java_gateway import get_gateway
 
 __all__ = ['CloseableIterator', 'DataStream', 'KeyedStream', 'ConnectedStreams', 'WindowedStream',
@@ -2138,6 +2139,10 @@ def _get_one_input_stream_operator(data_stream: DataStream,
         elif isinstance(window_serializer, CountWindowSerializer):
             j_namespace_serializer = \
                 gateway.jvm.org.apache.flink.table.runtime.operators.window.CountWindow.Serializer()
+        elif isinstance(window_serializer, GlobalWindowSerializer):
+            j_namespace_serializer = \
+                gateway.jvm.org.apache.flink.streaming.api.windowing.windows.GlobalWindow \
+                .Serializer()
         else:
             j_namespace_serializer = \
                 gateway.jvm.org.apache.flink.streaming.api.utils.ByteArrayWrapperSerializer()
diff --git a/flink-python/pyflink/datastream/tests/test_window.py b/flink-python/pyflink/datastream/tests/test_window.py
index 9e6004d45a5..e91e6482b65 100644
--- a/flink-python/pyflink/datastream/tests/test_window.py
+++ b/flink-python/pyflink/datastream/tests/test_window.py
@@ -27,7 +27,8 @@ from pyflink.datastream.output_tag import OutputTag
 from pyflink.datastream.window import (TumblingEventTimeWindows,
                                        SlidingEventTimeWindows, EventTimeSessionWindows,
                                        CountSlidingWindowAssigner, SessionWindowTimeGapExtractor,
-                                       CountWindow, PurgingTrigger, EventTimeTrigger, TimeWindow)
+                                       CountWindow, PurgingTrigger, EventTimeTrigger, TimeWindow,
+                                       GlobalWindows, CountTrigger)
 from pyflink.datastream.tests.test_util import DataStreamTestSinkFunction
 from pyflink.java_gateway import get_gateway
 from pyflink.testing.test_case_utils import PyFlinkStreamingTestCase
@@ -410,6 +411,36 @@ class WindowTests(PyFlinkStreamingTestCase):
         side_expected = ['+I[a, 4]']
         self.assert_equals_sorted(side_expected, side_sink.get_results())
 
+    def test_global_window_with_purging_trigger(self):
+        self.env.set_parallelism(1)
+        data_stream = self.env.from_collection([
+            ('hi', 1), ('hi', 1), ('hi', 1), ('hi', 1), ('hi', 1), ('hi', 1), ('hi', 1)],
+            type_info=Types.TUPLE([Types.STRING(), Types.INT()]))  # type: DataStream
+
+        watermark_strategy = WatermarkStrategy.for_monotonous_timestamps() \
+            .with_timestamp_assigner(SecondColumnTimestampAssigner())
+
+        class MyProcessFunction(ProcessWindowFunction):
+
+            def clear(self, context: ProcessWindowFunction.Context) -> None:
+                pass
+
+            def process(self, key, context: ProcessWindowFunction.Context,
+                        elements: Iterable[Tuple[str, int]]) -> Iterable[tuple]:
+                return [(key, len([e for e in elements]))]
+
+        data_stream.assign_timestamps_and_watermarks(watermark_strategy) \
+            .key_by(lambda x: x[0], key_type=Types.STRING()) \
+            .window(GlobalWindows.create()) \
+            .trigger(PurgingTrigger.of(CountTrigger.of(2))) \
+            .process(MyProcessFunction(), Types.TUPLE([Types.STRING(), Types.INT()])) \
+            .add_sink(self.test_sink)
+
+        self.env.execute('test_global_window_with_purging_trigger')
+        results = self.test_sink.get_results()
+        expected = ['(hi,2)', '(hi,2)', '(hi,2)']
+        self.assert_equals_sorted(expected, results)
+
 
 class SecondColumnTimestampAssigner(TimestampAssigner):
 
diff --git a/flink-python/pyflink/datastream/window.py b/flink-python/pyflink/datastream/window.py
index 9da5cab96e0..afbbc99664d 100644
--- a/flink-python/pyflink/datastream/window.py
+++ b/flink-python/pyflink/datastream/window.py
@@ -33,6 +33,7 @@ from pyflink.metrics import MetricGroup
 __all__ = ['Window',
            'TimeWindow',
            'CountWindow',
+           'GlobalWindow',
            'TumblingProcessingTimeWindows',
            'TumblingEventTimeWindows',
            'SlidingProcessingTimeWindows',
@@ -51,11 +52,13 @@ __all__ = ['Window',
            'ProcessingTimeTrigger',
            'ContinuousEventTimeTrigger',
            'ContinuousProcessingTimeTrigger',
+           'NeverTrigger',
            'PurgingTrigger',
            'CountTrigger',
            'TimeWindowSerializer',
            'CountWindowSerializer',
-           'SessionWindowTimeGapExtractor']
+           'SessionWindowTimeGapExtractor',
+           'GlobalWindows']
 
 
 def long_to_int_with_bit_mixing(x: int) -> int:
@@ -197,6 +200,30 @@ class CountWindow(Window):
         return "CountWindow(id={})".format(self.id)
 
 
+class GlobalWindow(Window):
+    """
+    The default window into which all data is placed GlobalWindows.
+    """
+    def __init__(self):
+        super(GlobalWindow, self).__init__()
+
+    @staticmethod
+    def get() -> 'GlobalWindow':
+        return GlobalWindow()
+
+    def max_timestamp(self) -> int:
+        return MAX_LONG_VALUE
+
+    def __eq__(self, other):
+        return self.__class__ == other.__class__
+
+    def __hash__(self):
+        return 0
+
+    def __repr__(self):
+        return "GlobalWindow"
+
+
 class TimeWindowSerializer(TypeSerializer[TimeWindow]):
     """
     The serializer used to write the TimeWindow type.
@@ -244,6 +271,31 @@ class CountWindowSerializer(TypeSerializer[CountWindow]):
         return coders.CountWindowCoder()
 
 
+class GlobalWindowSerializer(TypeSerializer[GlobalWindow]):
+    """
+    A TypeSerializer for GlobalWindow.
+    """
+
+    def __init__(self):
+        self._underlying_coder = None
+
+    def serialize(self, element: GlobalWindow, stream: BytesIO) -> None:
+        if self._underlying_coder is None:
+            self._underlying_coder = self._get_coder().get_impl()
+        bytes_data = self._underlying_coder.encode(element)
+        stream.write(bytes_data)
+
+    def deserialize(self, stream: BytesIO) -> GlobalWindow:
+        if self._underlying_coder is None:
+            self._underlying_coder = self._get_coder().get_impl()
+        bytes_data = stream.read(8)
+        return self._underlying_coder.decode(bytes_data)
+
+    def _get_coder(self):
+        from pyflink.fn_execution import coders
+        return coders.GlobalWindowCoder()
+
+
 T = TypeVar('T')
 W = TypeVar('W')
 W2 = TypeVar('W2')
@@ -965,6 +1017,41 @@ class CountTrigger(Trigger[T, CountWindow]):
         count_state.clear()
 
 
+class NeverTrigger(Trigger[T, GlobalWindow]):
+    """
+    A trigger that never fires, as default Trigger for GlobalWindows.
+    """
+
+    def on_element(self,
+                   element: T,
+                   timestamp: int,
+                   window: GlobalWindow,
+                   ctx: 'Trigger.TriggerContext') -> TriggerResult:
+        return TriggerResult.CONTINUE
+
+    def on_processing_time(self,
+                           time: int,
+                           window: GlobalWindow,
+                           ctx: 'Trigger.TriggerContext') -> TriggerResult:
+        return TriggerResult.CONTINUE
+
+    def on_event_time(self,
+                      time: int,
+                      window: GlobalWindow,
+                      ctx: 'Trigger.TriggerContext') -> TriggerResult:
+        return TriggerResult.CONTINUE
+
+    def on_merge(self,
+                 window: GlobalWindow,
+                 ctx: 'Trigger.OnMergeContext') -> None:
+        pass
+
+    def clear(self,
+              window: GlobalWindow,
+              ctx: 'Trigger.TriggerContext') -> None:
+        pass
+
+
 class CountTumblingWindowAssigner(WindowAssigner[T, CountWindow]):
     """
     A WindowAssigner that windows elements into fixed-size windows based on the count number
@@ -1589,3 +1676,38 @@ class DynamicEventTimeSessionWindows(MergingWindowAssigner[T, TimeWindow]):
 
     def __repr__(self):
         return "DynamicEventTimeSessionWindows(%s)" % self._session_gap
+
+
+class GlobalWindows(WindowAssigner[T, GlobalWindow]):
+    """
+    A WindowAssigner that assigns all elements to the same GlobalWindow.
+    """
+
+    def __init__(self) -> None:
+        super().__init__()
+
+    def assign_windows(self,
+                       element: T,
+                       timestamp: int,
+                       context: 'WindowAssigner.WindowAssignerContext') -> Collection[GlobalWindow]:
+        return [GlobalWindow.get()]
+
+    @staticmethod
+    def create() -> 'GlobalWindows':
+        """
+        Creates a new GlobalWindows WindowAssigner that assigns all elements to the
+        same GlobalWindow.
+        """
+        return GlobalWindows()
+
+    def get_default_trigger(self, env) -> Trigger[T, GlobalWindow]:
+        return NeverTrigger()
+
+    def get_window_serializer(self) -> TypeSerializer[GlobalWindow]:
+        return GlobalWindowSerializer()
+
+    def is_event_time(self) -> bool:
+        return False
+
+    def __repr__(self) -> str:
+        return "GlobalWindows()"
diff --git a/flink-python/pyflink/fn_execution/coder_impl_fast.pyx b/flink-python/pyflink/fn_execution/coder_impl_fast.pyx
index 518fcf826c5..4d3dd19ea19 100644
--- a/flink-python/pyflink/fn_execution/coder_impl_fast.pyx
+++ b/flink-python/pyflink/fn_execution/coder_impl_fast.pyx
@@ -31,7 +31,7 @@ import cloudpickle
 
 from pyflink.common import Row, RowKind
 from pyflink.common.time import Instant
-from pyflink.datastream.window import CountWindow, TimeWindow
+from pyflink.datastream.window import CountWindow, TimeWindow, GlobalWindow
 from pyflink.fn_execution.ResettableIO import ResettableIO
 from pyflink.table.utils import pandas_to_arrow, arrow_to_pandas
 
@@ -909,6 +909,18 @@ cdef class CountWindowCoderImpl(FieldCoderImpl):
     cpdef decode_from_stream(self, InputStream in_stream, size_t size):
         return CountWindow(in_stream.read_int64())
 
+cdef class GlobalWindowCoderImpl(FieldCoderImpl):
+    """
+    A coder for GlobalWindow.
+    """
+
+    cpdef encode_to_stream(self, value, OutputStream out_stream):
+        out_stream.write_byte(0)
+
+    cpdef decode_from_stream(self, InputStream in_stream, size_t size):
+        in_stream.read_byte()
+        return GlobalWindow()
+
 cdef class DataViewFilterCoderImpl(FieldCoderImpl):
     """
     A coder for CountWindow.
diff --git a/flink-python/pyflink/fn_execution/coder_impl_slow.py b/flink-python/pyflink/fn_execution/coder_impl_slow.py
index 75fd258d94f..9a847c417f6 100644
--- a/flink-python/pyflink/fn_execution/coder_impl_slow.py
+++ b/flink-python/pyflink/fn_execution/coder_impl_slow.py
@@ -800,6 +800,19 @@ class CountWindowCoderImpl(FieldCoderImpl):
         return CountWindow(in_stream.read_int64())
 
 
+class GlobalWindowCoderImpl(FieldCoderImpl):
+    """
+    A coder for CountWindow.
+    """
+
+    def encode_to_stream(self, value, out_stream: OutputStream):
+        out_stream.write_byte(0)
+
+    def decode_from_stream(self, in_stream: InputStream, length=0):
+        in_stream.read_byte()
+        return GlobalWindowCoderImpl()
+
+
 class DataViewFilterCoderImpl(FieldCoderImpl):
     """
     A coder for data view filter.
diff --git a/flink-python/pyflink/fn_execution/coders.py b/flink-python/pyflink/fn_execution/coders.py
index 7e43f78b2b2..fd094d77f46 100644
--- a/flink-python/pyflink/fn_execution/coders.py
+++ b/flink-python/pyflink/fn_execution/coders.py
@@ -585,6 +585,15 @@ class CountWindowCoder(FieldCoder):
         return coder_impl.CountWindowCoderImpl()
 
 
+class GlobalWindowCoder(FieldCoder):
+    """
+    Coder for GlobalWindow.
+    """
+
+    def get_impl(self):
+        return coder_impl.GlobalWindowCoderImpl()
+
+
 class DataViewFilterCoder(FieldCoder):
     """
     Coder for data view filter.