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

gaborgsomogyi 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 2045cb68ca3 [FLINK-39987][state] update State Processor API doc to 
include filter pushdown examples
2045cb68ca3 is described below

commit 2045cb68ca3f94037d5bd45c1eb40a6927e3fb2e
Author: Ilya Soin <[email protected]>
AuthorDate: Tue Jun 30 17:31:52 2026 +0300

    [FLINK-39987][state] update State Processor API doc to include filter 
pushdown examples
---
 docs/content.zh/docs/libs/state_processor_api.md | 280 ++++++++++++++++++++++-
 docs/content/docs/libs/state_processor_api.md    | 140 +++++++++++-
 2 files changed, 412 insertions(+), 8 deletions(-)

diff --git a/docs/content.zh/docs/libs/state_processor_api.md 
b/docs/content.zh/docs/libs/state_processor_api.md
index ae061755326..f70398fb81f 100644
--- a/docs/content.zh/docs/libs/state_processor_api.md
+++ b/docs/content.zh/docs/libs/state_processor_api.md
@@ -212,6 +212,110 @@ public class ReaderFunction extends 
KeyedStateReaderFunction<Integer, KeyedState
 
 **注意:** 当使用 `KeyedStateReaderFunction` 时,所有状态描述符必须在 `open` 函数中注册。 否则任何尝试调用 
`RuntimeContext#get*State` 将导致 `RuntimeException`。
 
+#### Filtering Keys
+
+When you are only interested in state for a subset of keys, you can push a 
`SavepointKeyFilter` into the read instead of reading the whole keyed state and 
filtering afterward.
+
+Depending on its type, a filter skips unnecessary reads at two levels:
+
+* **Split pruning** — when the filter resolves to an exact set of keys 
(`SavepointKeyFilter.exact(...)`), Flink can determine up front which key 
groups own those keys and skips every other input split without ever opening 
it. This is the largest saving, but it only works for exact keys.
+* **Key-level filtering** — within each split, every key is tested and 
non-matching keys are skipped before their state is read. This applies to all 
filters; a range filter relies on this level alone, since it cannot prune 
splits.
+
+```java
+import org.apache.flink.state.api.filter.SavepointKeyFilter;
+
+// Read the state for a single key
+DataStream<KeyedState> singleKey = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    SavepointKeyFilter.exact(42));
+
+// Read the state for a finite set of keys
+DataStream<KeyedState> someKeys = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    SavepointKeyFilter.exact(Set.of(1, 2, 3)));
+
+// Read the state for a key range: 10 <= key < 100
+DataStream<KeyedState> keyRange = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    SavepointKeyFilter.range(10, true, 100, false));
+```
+
+`SavepointKeyFilter` provides the following factory methods:
+
+* `SavepointKeyFilter.exact(Object key)` / 
`SavepointKeyFilter.exact(Set<Object> keys)` — match a single key or a finite 
set of keys.
+* `SavepointKeyFilter.range(Comparable<?> lower, boolean lowerInclusive, 
Comparable<?> upper, boolean upperInclusive)` — match a range. Either bound may 
be `null` to leave that side unbounded.
+* `SavepointKeyFilter.empty()` — match no keys. Not intended for direct use — 
it only serves as an internal building block for the Table API filter pushdown.
+
+When the built-in filters are not enough, you can implement the 
`SavepointKeyFilter` interface yourself.
+For use with the DataStream API, only `test(Object key)` has to be 
implemented; it is called for every key in each opened split and decides 
whether that key will be read.
+
+```java
+// Reads the state only for even keys
+public class EvenKeyFilter implements SavepointKeyFilter {
+
+    @Override
+    public boolean test(Object key) {
+        return ((Integer) key) % 2 == 0;
+    }
+}
+
+DataStream<KeyedState> evenState = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    new EvenKeyFilter());
+```
+
+By default, a custom filter only enables key-level filtering — every split is 
still opened.
+If your filter resolves to a finite set of keys, additionally override 
`getExactKeys()` to return them; Flink then prunes the splits whose key groups 
cannot contain any of those keys, just like the built-in `exact(...)` filter.
+
+```java
+// Reads the state only for keys 0..max
+public class UpToKeyFilter implements SavepointKeyFilter {
+
+    private final int max;
+
+    public UpToKeyFilter(int max) {
+        this.max = max;
+    }
+
+    @Override
+    public boolean test(Object key) {
+        int k = (Integer) key;
+        return k >= 0 && k <= max;
+    }
+
+    @Override
+    public Set<Object> getExactKeys() {
+        // Enumerate the finite key set so Flink can prune splits up front
+        Set<Object> keys = new HashSet<>();
+        for (int k = 0; k <= max; k++) {
+            keys.add(k);
+        }
+        return keys;
+    }
+}
+
+DataStream<KeyedState> firstKeys = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    new UpToKeyFilter(100));
+```
+
+The remaining interface methods can be left at their defaults for DataStream 
API usage, as they are only used internally in the Table API during push-down 
handling.
+
 ### 窗口状态 Window State
 
 State Processor API 支持读取[窗口算子]({{< ref "docs/dev/datastream/operators/windows" 
>}})的状态,当读取窗口状态时,需要指定算子 id,窗口分配器和聚合类型。
@@ -298,7 +402,7 @@ SavepointReader savepoint = SavepointReader.read(env, 
"hdfs://checkpoint-dir", n
 
 savepoint
     .window(TumblingEventTimeWindows.of(Duration.ofMinutes(1)))
-    .aggregate("click-window", new ClickCounter(), new ClickReader(), 
Types.String, Types.INT, Types.INT)
+    .aggregate("click-window", new ClickCounter(), new ClickReader(), 
Types.STRING, Types.INT, Types.INT)
     .print();
 
 ```
@@ -472,7 +576,7 @@ SavepointWriter
 
 ### 更改 UID (hashes)
 
-`SavepointWriter#changeOperatorIdenfifier` 可以用来修改一个算子的 [UIDs]({{< ref 
"docs/concepts/glossary" >}}#uid) 或 [UID hash]({{< ref "docs/concepts/glossary" 
>}}#uid-hash)。
+`SavepointWriter#changeOperatorIdentifier` 可以用来修改一个算子的 [UIDs]({{< ref 
"docs/concepts/glossary" >}}#uid) 或 [UID hash]({{< ref "docs/concepts/glossary" 
>}}#uid-hash)。
 
 如果一个 `UID` 没有被显式地设置(自动生成的并且是未知的),那么你可以通过提供 `UID hash` (通过解析 logs 获得 `UID 
hash`) 来为算子赋予一个 `UID`:
 ```java
@@ -491,3 +595,175 @@ savepointWriter
         OperatorIdentifier.forUid("new-uid"))
     ...
 ```
+
+## Table API
+
+### Getting started
+
+Before you interrogate state using the Table API, make sure to review our 
[Flink SQL]({{< ref "docs/sql/reference/overview" >}}) guidelines.
+
+IMPORTANT NOTE: State Table API only supports keyed state.
+
+### Metadata
+
+The following SQL table function allows users to read the metadata of 
savepoints and checkpoints in the following way:
+```SQL
+LOAD MODULE state;
+SELECT * FROM savepoint_metadata('/root/dir/of/checkpoint-data/chk-1');
+```
+
+The new table function creates a table with the following fixed schema:
+
+| Key                                      | Data type       | Description     
                                                                                
                                                                                
                |
+|------------------------------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| checkpoint-id                            | BIGINT NOT NULL | Checkpoint ID.  
                                                                                
                                                                                
                |
+| operator-name                            | STRING          | Operator Name.  
                                                                                
                                                                                
                |
+| operator-uid                             | STRING          | Operator UID.   
                                                                                
                                                                                
                |
+| operator-uid-hash                        | STRING NOT NULL | Operator UID 
hash.                                                                           
                                                                                
                   |
+| operator-parallelism                     | INT NOT NULL    | Parallelism of 
the operator.                                                                   
                                                                                
                 |
+| operator-max-parallelism                 | INT NOT NULL    | Maximum 
parallelism of the operator.                                                    
                                                                                
                        |
+| operator-subtask-state-count             | INT NOT NULL    | Number of 
operator subtask states. It represents the state partition count divided by the 
operator's parallelism and might be 0 if the state is not partitioned (for 
example broadcast source). |
+| operator-coordinator-state-size-in-bytes | BIGINT NOT NULL | The operator’s 
coordinator state size in bytes, or zero if no coordinator state.               
                                                                                
                 |
+| operator-total-size-in-bytes             | BIGINT NOT NULL | Total operator 
state size in bytes.                                                            
                                                                                
                 |
+
+### Keyed State
+
+[Keyed state]({{< ref "docs/dev/datastream/fault-tolerance/state" 
>}}#keyed-state), also known as partitioned state, is any state that is 
partitioned relative to a key.
+
+The SQL connector allows users to read arbitrary columns as ValueState and 
complex state types such as ListState, MapState.
+This means if an operator contains a stateful process function such as:
+```java
+eventStream
+  .keyBy(e -> (Integer)e.key)
+  .process(new StatefulFunction())
+  .uid("my-uid");
+
+...
+
+public class Account {
+    private Integer id;
+    public Double amount;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+}
+
[email protected]
+public class AvroRecord ... {
+    // Generated record which contains at least the following field: long 
longData
+}
+
+public class StatefulFunction extends KeyedProcessFunction<Integer, Integer, 
Void> {
+  private ValueState<Integer> myValueState;
+  private ValueState<Account> myAccountValueState;
+  private ListState<Integer> myListState;
+  private MapState<Integer, Integer> myMapState;
+  private ValueState<AvroRecord> myAvroState;
+
+  @Override
+  public void open(OpenContext openContext) {
+    myValueState = getRuntimeContext().getState(new 
ValueStateDescriptor<>("MyValueState", Integer.class));
+    myAccountValueState = getRuntimeContext().getState(new 
ValueStateDescriptor<>("MyAccountValueState", Account.class));
+    myValueState = getRuntimeContext().getListState(new 
ListStateDescriptor<>("MyListState", Integer.class));
+    myMapState = getRuntimeContext().getMapState(new 
MapStateDescriptor<>("MyMapState", Integer.class, Integer.class));
+    myAvroState = getRuntimeContext().getMapState(new 
ValueStateDescriptor<>("MyAvroState", new AvroTypeInfo<>(AvroRecord.class)));
+  }
+  ...
+}
+```
+
+Then it can read by querying a table created using the following SQL statement:
+```SQL
+CREATE TABLE state_table (
+  k INTEGER,
+  MyValueState INTEGER,
+  MyAccountValueState ROW<id INTEGER, amount DOUBLE>,
+  MyListState ARRAY<INTEGER>,
+  MyMapState MAP<INTEGER, INTEGER>,
+  MyAvroState ROW<longData bigint>,
+  PRIMARY KEY (k) NOT ENFORCED
+) WITH (
+  'connector' = 'savepoint',
+  'state.backend.type' = 'rocksdb',
+  'state.path' = '/root/dir/of/checkpoint-data/chk-1',
+  'operator.uid' = 'my-uid'
+);
+```
+
+#### Filter Pushdown on the Key Column
+
+When a query filters on the key column (the column declared as `PRIMARY KEY`), 
the savepoint connector automatically pushes the predicate down into the 
savepoint scan.
+As with the DataStream `SavepointKeyFilter`, point and `IN` lookups prune 
whole key groups, while range predicates narrow the per-split key iteration, so 
only the relevant state is read.
+
+```SQL
+-- Point lookup: reads only the key group that contains key 42
+SELECT * FROM state_table WHERE k = 42;
+
+-- Point lookups on a finite set of keys, only key groups that contain 1, 2, 
or 3 are read
+SELECT * FROM state_table WHERE k IN (1, 2, 3);
+
+-- Range scan, reads all key groups but filters out irrelevant keys before 
state is read
+SELECT * FROM state_table WHERE k >= 10 AND k < 100;
+SELECT * FROM state_table WHERE k BETWEEN 10 AND 100;
+```
+
+The following predicates on the key column can be pushed down:
+
+* `=` and `IN` — resolve to an exact set of keys and enable key-group pruning.
+* `<`, `<=`, `>`, `>=`, and `BETWEEN` — resolve to a key range and enable 
key-level filtering within each split.
+* `AND` — pushed down when **all** of its operands are range predicates; the 
ranges are intersected into a single, tighter range. If any operand is an 
equality/IN predicate, the entire AND expression is not pushed down — neither 
split pruning nor key-level filtering applies.
+* `OR` — pushed down when **every** branch resolves to exact keys (the 
branches are unioned into one key set, which is equivalent to an `IN`). If any 
branch is a range predicate, the entire OR expression is not pushed down.
+
+### Connector options
+
+#### General options
+| Option             | Required | Default | Type                               
    | Description                                                               
                                                                                
                                                                           |
+|--------------------|----------|---------|----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| connector          | required | (none)  | String                             
    | Specify what connector to use, here should be 'savepoint'.                
                                                                                
                                                                           |
+| state.backend.type | optional | (none)  | Enum Possible values: hashmap, 
rocksdb | Defines the state backend which must be used for state reading. This 
must match with the value which was defined in Flink job which created the 
savepoint or checkpoint. If not provided then it falls back to 
`state.backend.type` in flink configuration. |
+| state.path         | required | (none)  | String                             
    | Defines the state path which must be used for state reading. All file 
system that are supported by Flink can be used here.                            
                                                                               |
+| operator.uid       | optional | (none)  | String                             
    | Defines the operator UID which must be used for state reading (can't be 
used together with `operator.uid.hash`). Either `operator.uid` or 
`operator.uid.hash` must be specified.                                          
           |
+| operator.uid.hash  | optional | (none)  | String                             
    | Defines the operator UID hash which must be used for state reading (can't 
be used together with `operator.uid`). Either `operator.uid` or 
`operator.uid.hash` must be specified.                                          
           |
+
+#### Connector options for column ‘#’
+| Option                           | Required | Default | Type                 
                  | Description                                                 
                                                                                
                                                                                
                                                     |
+|----------------------------------|----------|---------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| fields.#.state-name              | optional | (none)  | String               
                  | Overrides the state name which must be used for state 
reading. This can be useful when the state name contains characters which are 
not compliant with SQL column names.                                            
                                                             |
+| fields.#.state-type              | optional | (none)  | Enum Possible 
values: list, map, value | Defines the state type which must be used for state 
reading, including value, list and map. When it's not provided then it tries to 
infer from the SQL type (ARRAY=list, MAP=map, all others=value).                
                                                             |
+| fields.#.key-class               | optional | (none)  | String               
                  | Defines the format class scheme for decoding map key data 
(for ex. java.lang.Long). Either key-class or key-type-factory can be 
specified. When none of them are provided then the format class scheme tries to 
infer from the SQL type (only primitive types supported).        |
+| fields.#.key-type-factory        | optional | (none)  | String               
                  | Defines the type information factory for decoding map key 
data. Either key-class or key-type-factory can be specified. When none of them 
are provided then the format class scheme tries to infer from the SQL type 
(only primitive types supported).                            |
+| fields.#.value-class             | optional | (none)  | String               
                  | Defines the format class scheme for decoding value data 
(for ex. java.lang.Long). Either value-class or value-info-factory can be 
specified. When none of them are provided then the format class scheme tries to 
infer from the SQL type (only primitive types supported).      |
+| fields.#.value-type-factory      | optional | (none)  | String               
                  | Defines the type information factory for decoding value 
data. Either value-class or value-type-factory can be specified. When none of 
them are provided then the format class scheme tries to infer from the SQL type 
(only primitive types supported).                          |
+
+### Default Data Type Mapping
+
+The state SQL connector infers the data type for primitive types when 
`fields.#.value-class` and `fields.#.key-class`
+are not defined. The following table shows the `Flink SQL type` -> `Java type` 
default mapping. If the mapping is not calculated properly
+then it can be overridden with the two mentioned config parameters on a 
per-column basis.
+
+| Flink SQL type          | Java type                                          
                     |
+|-------------------------|-------------------------------------------------------------------------|
+| CHAR / VARCHAR / STRING | java.lang.String                                   
                     |
+| BOOLEAN                 | boolean                                            
                     |
+| BINARY / VARBINARY      | byte[]                                             
                     |
+| DECIMAL                 | org.apache.flink.table.data.DecimalData            
                     |
+| TINYINT                 | byte                                               
                     |
+| SMALLINT                | short                                              
                     |
+| INTEGER                 | int                                                
                     |
+| BIGINT                  | long                                               
                     |
+| FLOAT                   | float                                              
                     |
+| DOUBLE                  | double                                             
                     |
+| DATE                    | int                                                
                     |
+| INTERVAL_YEAR_MONTH     | long                                               
                     |
+| INTERVAL_DAY_TIME       | long                                               
                     |
+| ARRAY                   | java.util.List                                     
                     |
+| MAP                     | java.util.Map                                      
                     |
+| ROW                     | 
java.util.List\<org.apache.flink.table.types.logical.RowType.RowField\> |
+
+SHORTCUT: When a complex java class is defined in a column with STRING SQL 
type then the class instance
+`toString` method result will be the column value. This can be useful when a 
quick explanatory query is required.
diff --git a/docs/content/docs/libs/state_processor_api.md 
b/docs/content/docs/libs/state_processor_api.md
index 3690de6be58..e92e4fcf9e0 100644
--- a/docs/content/docs/libs/state_processor_api.md
+++ b/docs/content/docs/libs/state_processor_api.md
@@ -134,7 +134,7 @@ DataStream<Tuple2<Integer, Integer>> broadcastState = 
savepoint.readBroadcastSta
 
 ##### Using Custom Serializers
 
-Each of the operator state readers support using custom `TypeSerializers` if 
one was used to define the `StateDescriptor` that wrote out the state. 
+Each of the operator state readers supports using custom `TypeSerializers` if 
one was used to define the `StateDescriptor` that wrote out the state. 
 
 ```java
 DataStream<Integer> listState = savepoint.readListState<>(
@@ -226,9 +226,113 @@ Along with reading registered state values, each key has 
access to a `Context` w
 
 **Note:** When using a `KeyedStateReaderFunction`, all state descriptors must 
be registered eagerly inside of open. Any attempt to call a 
`RuntimeContext#get*State` will result in a `RuntimeException`.
 
+##### Filtering Keys
+
+When you are only interested in state for a subset of keys, you can push a 
`SavepointKeyFilter` into the read instead of reading the whole keyed state and 
filtering afterward.
+
+Depending on its type, a filter skips unnecessary reads at two levels:
+
+* **Split pruning** — when the filter resolves to an exact set of keys 
(`SavepointKeyFilter.exact(...)`), Flink can determine up front which key 
groups own those keys and skips every other input split without ever opening 
it. This is the largest saving, but it only works for exact keys.
+* **Key-level filtering** — within each split, every key is tested and 
non-matching keys are skipped before their state is read. This applies to all 
filters; a range filter relies on this level alone, since it cannot prune 
splits.
+
+```java
+import org.apache.flink.state.api.filter.SavepointKeyFilter;
+
+// Read the state for a single key
+DataStream<KeyedState> singleKey = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    SavepointKeyFilter.exact(42));
+
+// Read the state for a finite set of keys
+DataStream<KeyedState> someKeys = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    SavepointKeyFilter.exact(Set.of(1, 2, 3)));
+
+// Read the state for a key range: 10 <= key < 100
+DataStream<KeyedState> keyRange = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    SavepointKeyFilter.range(10, true, 100, false));
+```
+
+`SavepointKeyFilter` provides the following factory methods:
+
+* `SavepointKeyFilter.exact(Object key)` / 
`SavepointKeyFilter.exact(Set<Object> keys)` — match a single key or a finite 
set of keys.
+* `SavepointKeyFilter.range(Comparable<?> lower, boolean lowerInclusive, 
Comparable<?> upper, boolean upperInclusive)` — match a range. Either bound may 
be `null` to leave that side unbounded.
+* `SavepointKeyFilter.empty()` — match no keys. Not intended for direct use — 
it only serves as an internal building block for the Table API filter pushdown.
+
+When the built-in filters are not enough, you can implement the 
`SavepointKeyFilter` interface yourself.
+For use with the DataStream API, only `test(Object key)` has to be 
implemented; it is called for every key in each opened split and decides 
whether that key will be read.
+
+```java
+// Reads the state only for even keys
+public class EvenKeyFilter implements SavepointKeyFilter {
+
+    @Override
+    public boolean test(Object key) {
+        return ((Integer) key) % 2 == 0;
+    }
+}
+
+DataStream<KeyedState> evenState = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    new EvenKeyFilter());
+```
+
+By default, a custom filter only enables key-level filtering — every split is 
still opened.
+If your filter resolves to a finite set of keys, additionally override 
`getExactKeys()` to return them; Flink then prunes the splits whose key groups 
cannot contain any of those keys, just like the built-in `exact(...)` filter.
+
+```java
+// Reads the state only for keys 0..max
+public class UpToKeyFilter implements SavepointKeyFilter {
+
+    private final int max;
+
+    public UpToKeyFilter(int max) {
+        this.max = max;
+    }
+
+    @Override
+    public boolean test(Object key) {
+        int k = (Integer) key;
+        return k >= 0 && k <= max;
+    }
+
+    @Override
+    public Set<Object> getExactKeys() {
+        // Enumerate the finite key set so Flink can prune splits up front
+        Set<Object> keys = new HashSet<>();
+        for (int k = 0; k <= max; k++) {
+            keys.add(k);
+        }
+        return keys;
+    }
+}
+
+DataStream<KeyedState> firstKeys = savepoint.readKeyedState(
+    OperatorIdentifier.forUid("my-uid"),
+    new ReaderFunction(),
+    Types.INT,
+    TypeInformation.of(KeyedState.class),
+    new UpToKeyFilter(100));
+```
+
+The remaining interface methods can be left at their defaults for DataStream 
API usage, as they are only used internally in the Table API during push-down 
handling.
+
 #### Window State
 
-The state processor api supports reading state from a [window operator]({{< 
ref "docs/dev/datastream/operators/windows" >}}).
+The state processor API supports reading state from a [window operator]({{< 
ref "docs/dev/datastream/operators/windows" >}}).
 When reading a window state, users specify the operator id, window assigner, 
and aggregation type.
 
 Additionally, a `WindowReaderFunction` can be specified to enrich each read 
with additional information similar
@@ -314,7 +418,7 @@ SavepointReader savepoint = SavepointReader.read(env, 
"hdfs://checkpoint-dir", n
 
 savepoint
     .window(TumblingEventTimeWindows.of(Duration.ofMinutes(1)))
-    .aggregate("click-window", new ClickCounter(), new ClickReader(), 
Types.String, Types.INT, Types.INT)
+    .aggregate("click-window", new ClickCounter(), new ClickReader(), 
Types.STRING, Types.INT, Types.INT)
     .print();
 ```
 
@@ -487,7 +591,7 @@ SavepointWriter
 
 #### Changing UID (hashes)
 
-`SavepointWriter#changeOperatorIdenfifier` can be used to modify the 
[UIDs]({{< ref "docs/concepts/glossary" >}}#uid) or [UID hash]({{< ref 
"docs/concepts/glossary" >}}#uid-hash) of an operator.
+`SavepointWriter#changeOperatorIdentifier` can be used to modify the 
[UIDs]({{< ref "docs/concepts/glossary" >}}#uid) or [UID hash]({{< ref 
"docs/concepts/glossary" >}}#uid-hash) of an operator.
 
 If a `UID` was not explicitly set (and was thus auto-generated and is 
effectively unknown), you can assign a `UID` provided that you know the `UID 
hash` (e.g., by parsing the logs):
 ```java
@@ -511,7 +615,7 @@ savepointWriter
 
 ### Getting started
 
-Before you interrogate state using the table API, make sure to review our 
[Flink SQL]({{< ref "docs/sql/reference/overview" >}}) guidelines.
+Before you interrogate state using the Table API, make sure to review our 
[Flink SQL]({{< ref "docs/sql/reference/overview" >}}) guidelines.
 
 IMPORTANT NOTE: State Table API only supports keyed state.
 
@@ -555,7 +659,7 @@ public class Account {
     private Integer id;
     public Double amount;
 
-    public Integer geId() {
+    public Integer getId() {
         return id;
     }
 
@@ -606,6 +710,30 @@ CREATE TABLE state_table (
 );
 ```
 
+#### Filter Pushdown on the Key Column
+
+When a query filters on the key column (the column declared as `PRIMARY KEY`), 
the savepoint connector automatically pushes the predicate down into the 
savepoint scan.
+As with the DataStream `SavepointKeyFilter`, point and `IN` lookups prune 
whole key groups, while range predicates narrow the per-split key iteration, so 
only the relevant state is read.
+
+```SQL
+-- Point lookup: reads only the key group that contains key 42
+SELECT * FROM state_table WHERE k = 42;
+
+-- Point lookups on a finite set of keys, only key groups that contain 1, 2, 
or 3 are read
+SELECT * FROM state_table WHERE k IN (1, 2, 3);
+
+-- Range scan, reads all key groups but filters out irrelevant keys before 
state is read
+SELECT * FROM state_table WHERE k >= 10 AND k < 100;
+SELECT * FROM state_table WHERE k BETWEEN 10 AND 100;
+```
+
+The following predicates on the key column can be pushed down:
+
+* `=` and `IN` — resolve to an exact set of keys and enable key-group pruning.
+* `<`, `<=`, `>`, `>=`, and `BETWEEN` — resolve to a key range and enable 
key-level filtering within each split.
+* `AND` — pushed down when **all** of its operands are range predicates; the 
ranges are intersected into a single, tighter range. If any operand is an 
equality/IN predicate, the entire AND expression is not pushed down — neither 
split pruning nor key-level filtering applies.
+* `OR` — pushed down when **every** branch resolves to exact keys (the 
branches are unioned into one key set, which is equivalent to an `IN`). If any 
branch is a range predicate, the entire OR expression is not pushed down.
+
 ### Connector options
 
 #### General options

Reply via email to