binary-signal commented on PR #3383:
URL: https://github.com/apache/fluss/pull/3383#issuecomment-4742907159

   @polyzos I had a look at the PR and can confirm that bounded reads via the 
Table API work as expected.
   That said, there is an issue when performing bounded reads via the 
DataStream API. It looks like the corresponding logic was missed in 
FlussSourceBuilder.java. The required changes were already implemented in 
[flink] Add union read support to datastream (#3432) which allows 
`setBounded()` to be set in a Fluss Flink Source. The following  guard throws: 
   
   
   ```java
   # FlussSourceBuilder.java
   
   if (bounded && !(lakeEnabled && fullStartup)) {
       ...
   }
   ```
   The following patch addresses the issue and also adds a test case to cover 
this scenario.
   
   ```
   diff --git 
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSourceBuilder.java
 
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSourceBuilder.java
   index 2e80067f..eb95e731 100644
   --- 
a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSourceBuilder.java
   +++ 
b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/FlussSourceBuilder.java
   @@ -176,7 +176,10 @@ public class FlussSourceBuilder<OUT> {
         * Builds a bounded source for batch execution. The source reads up to 
the latest offsets at job
         * startup and then finishes; combined with the default {@link 
OffsetsInitializer#full()} on a
         * datalake-enabled table this performs a bounded union read of the 
lake snapshot and the Fluss
   -     * log. If not called, the source is unbounded (streaming).
   +     * log. Alternatively, a bounded read of a primary-key table is 
supported via the server-side KV
   +     * scan by setting {@code client.scanner.kv.server-side.enabled = true} 
(see {@link
   +     * ConfigOptions#CLIENT_SCANNER_KV_SERVER_SIDE_ENABLED}). If not 
called, the source is unbounded
   +     * (streaming).
         *
         * @return this builder
         */
   @@ -353,13 +356,24 @@ public class FlussSourceBuilder<OUT> {
            boolean lakeEnabled = 
tableInfo.getTableConfig().isDataLakeEnabled();
            boolean fullStartup = offsetsInitializer instanceof 
SnapshotOffsetsInitializer;
    
   -        if (bounded && !(lakeEnabled && fullStartup)) {
   +        // bounded read via the server-side KV scan applies to primary-key 
tables when the master
   +        // switch is enabled, mirroring the SQL connector (see 
FlinkTableSource).
   +        boolean kvBatchEnabled = 
flussConf.get(ConfigOptions.CLIENT_SCANNER_KV_SERVER_SIDE_ENABLED);
   +        boolean kvBatchAllowed = hasPrimaryKey && kvBatchEnabled;
   +
   +        if (bounded && !(lakeEnabled && fullStartup) && !kvBatchAllowed) {
                throw new IllegalArgumentException(
                        String.format(
   -                            "Bounded (batch) read requires a 
datalake-enabled table started in "
   -                                    + "full mode 
(OffsetsInitializer.full()), but table '%s' has "
   -                                    + "datalake enabled=%s and full startup 
mode=%s.",
   -                            tablePath, lakeEnabled, fullStartup));
   +                            "Bounded (batch) read requires either a 
datalake-enabled table started "
   +                                    + "in full mode 
(OffsetsInitializer.full()), or a primary-key "
   +                                    + "table with '%s' = 'true'. Table '%s' 
has datalake enabled=%s, "
   +                                    + "full startup mode=%s, primary 
key=%s, server-side KV scan=%s.",
   +                            
ConfigOptions.CLIENT_SCANNER_KV_SERVER_SIDE_ENABLED.key(),
   +                            tablePath,
   +                            lakeEnabled,
   +                            fullStartup,
   +                            hasPrimaryKey,
   +                            kvBatchEnabled));
            }
    
            LakeSource<LakeSplit> lakeSource = null;
   diff --git 
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/FlussSourceITCase.java
 
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/FlussSourceITCase.java
   index f4b112c4..df211643 100644
   --- 
a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/FlussSourceITCase.java
   +++ 
b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/source/FlussSourceITCase.java
   @@ -21,6 +21,8 @@ import 
org.apache.fluss.client.initializer.OffsetsInitializer;
    import org.apache.fluss.client.table.Table;
    import org.apache.fluss.client.table.writer.AppendWriter;
    import org.apache.fluss.client.table.writer.UpsertWriter;
   +import org.apache.fluss.config.ConfigOptions;
   +import org.apache.fluss.config.Configuration;
    import 
org.apache.fluss.flink.source.deserializer.RowDataDeserializationSchema;
    import org.apache.fluss.flink.source.testutils.MockDataUtils;
    import org.apache.fluss.flink.source.testutils.Order;
   @@ -113,6 +115,37 @@ public class FlussSourceITCase extends FlinkTestBase {
            assertThat(collectedElements).hasSameElementsAs(ORDERS);
        }
    
   +    @Test
   +    public void testBoundedKvBatchPKSource() throws Exception {
   +        createTable(ordersPKTablePath, pkTableDescriptor);
   +        writeRowsToTable(ordersPKTablePath);
   +
   +        // Enable the server-side KV scan so a bounded read is allowed on a 
primary-key table
   +        // without the data-lake integration (mirrors the SQL connector's 
behavior).
   +        Configuration flussConf = new Configuration();
   +        flussConf.set(ConfigOptions.CLIENT_SCANNER_KV_SERVER_SIDE_ENABLED, 
true);
   +
   +        FlussSource<Order> flussSource =
   +                FlussSource.<Order>builder()
   +                        .setBootstrapServers(bootstrapServers)
   +                        .setDatabase(DEFAULT_DB)
   +                        .setTable(pkTableName)
   +                        .setStartingOffsets(OffsetsInitializer.full())
   +                        .setScanPartitionDiscoveryIntervalMs(1000L)
   +                        .setDeserializationSchema(new 
MockDataUtils.OrderDeserializationSchema())
   +                        .setFlussConfig(flussConf)
   +                        .setBounded()
   +                        .build();
   +
   +        // env.setRuntimeMode(RuntimeExecutionMode.BATCH);
   +        DataStreamSource<Order> stream =
   +                env.fromSource(flussSource, 
WatermarkStrategy.noWatermarks(), "Fluss Source");
   +
   +        List<Order> collectedElements = 
stream.executeAndCollect(ORDERS.size());
   +
   +        assertThat(collectedElements).hasSameElementsAs(ORDERS);
   +    }
   +
        @Test
        public void testTablePKSourceWithProjectionPushdown() throws Exception {
            createTable(ordersPKTablePath, pkTableDescriptor);
   ```
   
   
   
   
   
   Quick example
   
   ```java
   import org.apache.flink.api.common.RuntimeExecutionMode;
   import org.apache.flink.api.common.eventtime.WatermarkStrategy;
   import org.apache.flink.streaming.api.datastream.DataStream;
   import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
   import org.apache.flink.table.data.RowData;
   import org.apache.fluss.config.Configuration;
   import org.apache.fluss.flink.source.FlussSource;
   import 
org.apache.fluss.flink.source.deserializer.RowDataDeserializationSchema;
   
   public class FlussDataStreamBoundedReadKvBatch {
       public static void main(String[] args) throws Exception {
   
           StreamExecutionEnvironment env = 
StreamExecutionEnvironment.getExecutionEnvironment();
   
   
           // env.setRuntimeMode(RuntimeExecutionMode.STREAMING);
           Configuration conf = new Configuration();
           conf.setBoolean("client.scanner.kv.server-side.enabled", true);
           var source = FlussSource.<RowData>builder()
                   .setBootstrapServers("localhost:9122")
                   .setDatabase("cdstream_db")
                   .setTable("timeseries_norm")
                   .setDeserializationSchema(new RowDataDeserializationSchema())
                   .setBounded()
                   .setFlussConfig(conf)
                   .build();
   
           DataStream<RowData> stream = env.fromSource(source, 
WatermarkStrategy.noWatermarks(), "BoundedFlussKvSource");
   
           stream.print();
           env.execute("BoundedFlussKvJob");
       }
   }
   ```
   


-- 
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