Repository: apache/shardingsphere (2026-07-24T14:06:56Z)

Issue:
【SQL Federation】SQL Federation fails on MySQL LIMIT ? when the bound parameter is Long
## Bug Report

### Which version of ShardingSphere did you use?

Apache ShardingSphere-JDBC 5.5.3.

Related modules:

- `shardingsphere-jdbc`: 5.5.3
- `shardingsphere-jdbc-dialect-mysql`: 5.5.3
- `shardingsphere-sharding-mysql`: 5.5.3

Environment:

- Java 17
- MySQL dialect
- SQL Federation enabled
- Apache Calcite resolved as 1.40.0
- MyBatis-Plus pagination can trigger this, but plain JDBC `PreparedStatement#setLong` can also reproduce it.

### Which project did you use? ShardingSphere-JDBC or ShardingSphere-Proxy?

ShardingSphere-JDBC.

### Expected behavior

A SQL Federation query using MySQL dialect should execute successfully when `LIMIT ?` is bound with a numeric JDBC parameter such as `Long`.

For example:

```java
PreparedStatement ps = connection.prepareStatement("""
    SELECT so.id AS id, u.name AS record_user_name
    FROM ims_sales_order so
    LEFT JOIN mnt_base_user u ON so.record_user_id = u.id
    WHERE so.tenant_id = 1
    ORDER BY so.create_time DESC
    LIMIT ?
    """);
ps.setLong(1, 20L);
ps.executeQuery();
```

### Actual behavior

The query enters SQL Federation and fails with:

```text
SQL Federation execute failed, sql ... ORDER BY so.create_time DESC LIMIT ?, parameters [20],
reason class java.lang.Long cannot be cast to class java.lang.Integer
```

Stack trace excerpt:

```text
java.sql.SQLException: class java.lang.Long cannot be cast to class java.lang.Integer
    at org.apache.shardingsphere.infra.exception.external.sql.ShardingSphereSQLException.toSQLException(ShardingSphereSQLException.java:81)
    at org.apache.shardingsphere.database.exception.core.SQLExceptionTransformEngine.toSQLException(SQLExceptionTransformEngine.java:54)
    at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement.execute(ShardingSpherePreparedStatement.java:244)
```

Observed SQL log:

```text
Preparing: SELECT so.id AS id, u.name AS record_user_name
FROM ims_sales_order so
LEFT JOIN mnt_base_user u ON so.record_user_id = u.id AND u.tenant_id = 1 AND u.deleted = 0
WHERE so.tenant_id = 1 AND so.deleted = 0
ORDER BY so.create_time DESC LIMIT ?

Parameters: 20(Long)

SQL Federation Execution Plan:
EnumerableLimit(fetch=[?0])
  EnumerableSort(...)
    EnumerableHashJoin(..., joinType=[left])
```

### Reason analyze (If you can)

I checked the ShardingSphere 5.5.3 source locally.

Source location:

```text
D:\source\shardingsphere\shardingsphere-5.5.3\kernel\sql-federation\core\src\main\java\org\apache\shardingsphere\sqlfederation\engine\processor\impl\StandardSQLFederationProcessor.java
```

`createParameters` copies query parameters as-is:

```java
private Map<String, Object> createParameters(final List<Object> params) {
    Map<String, Object> result = new HashMap<>(params.size(), 1F);
    int index = 0;
    for (Object each : params) {
        result.put("?" + index++, each);
    }
    return result;
}
```

Then this source:

```text
D:\source\shardingsphere\shardingsphere-5.5.3\kernel\sql-federation\executor\src\main\java\org\apache\shardingsphere\sqlfederation\executor\context\ExecutorBindContext.java
```

returns the parameter value as-is:

```java
@Override
public Object get(final String name) {
    return parameters.get(name);
}
```

But Calcite `EnumerableLimit#getExpression` converts dynamic `LIMIT/OFFSET` parameters to `Integer`:

```text
calcite-core-1.40.0
org/apache/calcite/adapter/enumerable/EnumerableLimit.java
```

```java
return Expressions.convert_(
    Expressions.call(DataContext.ROOT,
        BuiltInMethod.DATA_CONTEXT_GET.method,
        Expressions.constant("?" + param.getIndex())),
    Integer.class);
```

So if the JDBC parameter is `Long`, SQL Federation eventually causes a `Long -> Integer` cast failure.

MyBatis-Plus MySQL pagination binds pagination parameters as `long.class`, which seems reasonable because page size/current values are long values.

Checked source:

```text
D:\source\mybatis-plus\mybatis-plus-3.5.3.2\mybatis-plus-extension\src\main\java\com\baomidou\mybatisplus\extension\plugins\pagination\DialectModel.java
```

```java
new ParameterMapping.Builder(configuration, FIRST_PARAM_NAME, long.class).build()
new ParameterMapping.Builder(configuration, SECOND_PARAM_NAME, long.class).build()
```

### Steps to reproduce the behavior, such as: SQL to execute, sharding rule configuration, when exception occur etc.

Use ShardingSphere-JDBC 5.5.3 with MySQL dialect and SQL Federation enabled.

Sanitized YAML, preserving the real rule structure:

```yaml
databaseName: logic_db

mode:
  type: Standalone
  repository:
    type: JDBC

transaction:
  defaultType: LOCAL

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_master?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: password
  ds_1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_sharding_1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: password

sqlFederation:
  sqlFederationEnabled: true
  allQueryUseSQLFederation: false
  executionPlanCache:
    initialCapacity: 2000
    maximumSize: 65535

props:
  sql-show: true

rules:
  - !SINGLE
    tables:
      - ds_0.mnt_base_user

  - !SHARDING
    tables: {}
    defaultDatabaseStrategy:
      complex:
        shardingColumns: tenant_id
        shardingAlgorithmName: custom_database_tenant_sharding_algorithm
    defaultTableStrategy:
      complex:
        shardingColumns: tenant_id
        shardingAlgorithmName: tenant_sharding_algorithm
    shardingAlgorithms:
      custom_database_tenant_sharding_algorithm:
        type: CLASS_BASED
        props:
          strategy: COMPLEX
          algorithmClassName: com.example.sharding.CustomDatabaseTenantIdShardingAlgorithm
          dbPrefix: ds_
          tenantIdFiled: tenant_id
      tenant_sharding_algorithm:
        type: CLASS_BASED
        props:
          strategy: COMPLEX
          algorithmClassName: com.example.sharding.CustomTableTenantIdShardingAlgorithm
          tenantIdFiled: tenant_id
```

Run a query joining a sharded table and a single table:

```sql
SELECT so.id AS id, u.name AS record_user_name
FROM ims_sales_order so
LEFT JOIN mnt_base_user u ON so.record_user_id = u.id
WHERE so.tenant_id = 1
ORDER BY so.create_time DESC
LIMIT ?
```

Bind the parameter as Java `Long`, for example `20L`.

### Example codes for reproduce this issue (such as a github link).

```java
try (Connection connection = dataSource.getConnection();
     PreparedStatement ps = connection.prepareStatement("""
         SELECT so.id AS id, u.name AS record_user_name
         FROM ims_sales_order so
         LEFT JOIN mnt_base_user u ON so.record_user_id = u.id
         WHERE so.tenant_id = 1
         ORDER BY so.create_time DESC
         LIMIT ?
         """)) {
    ps.setLong(1, 20L);
    ps.executeQuery();
}
```

### Suggested fix

SQL Federation should normalize dynamic parameters used by Calcite `EnumerableLimit` fetch/offset before binding, or otherwise adapt `Number` values to the type expected by Calcite.

Possible directions:

- If a dynamic parameter is used as `LIMIT/OFFSET` and the value is `Number`, convert it to `Integer` when it is within integer range.
- Or make the SQL Federation `DataContext`/parameter binding layer return a compatible type for Calcite's generated `EnumerableLimit` expression.
- Or update the generated expression path so numeric fetch/offset parameters are accepted consistently.

Application pagination frameworks should not need ShardingSphere-specific parameter conversion.


Discussion:
Hi @YM-Sky, thanks for the detailed report. This is a bug in SQL Federation’s pagination parameter binding, not invalid JDBC or MyBatis-Plus usage.

SQL Federation explicitly supports pagination, and ShardingSphere-JDBC accepts `PreparedStatement#setLong`. However, the SQL Federation compiler represents `LIMIT ?` as a dynamic parameter while the execution layer passes the original `Long` object unchanged to Calcite. Calcite’s generated `EnumerableLimit` path expects an `Integer`, producing the reported `Long`-to-`Integer` cast failure. Although SQL Federation is documented as experimental, that status does not make this valid numeric JDBC parameter unsupported.

The fix should adapt only dynamic `LIMIT`/`OFFSET` parameters at the SQL Federation binding boundary, preserve unrelated `Long` parameters, and define range behavior without silent truncation. It should include focused unit coverage and a MySQL SQL Federation E2E case using a `long` pagination parameter. I suggest labeling this `type: bug`, `feature: SQL federation`, `in: kernel`, `in: JDBC`, and `db: MySQL`. Contributors are welcome to submit a PR with the fix and regression tests.

The reply above is based on the analysis below; the detailed reasoning is kept here for reference and follow-up contributors.

### Problem Understanding

- **Issue:** ShardingSphere-JDBC 5.5.3 fails during SQL Federation execution when MySQL `LIMIT ?` is bound through `PreparedStatement#setLong`.
- **Topology:** JDBC + Standalone; MySQL; SQL Federation enabled; registry/config center N/A.
- **Fast Triage:** The issue provides the version, topology, SQL, YAML, parameter type, execution plan, exception, and a plain JDBC reproducer. The evidence is sufficient to classify the issue.
- **OBS-1:** The reproduction reaches `EnumerableLimit(fetch=[?0])` with parameter `20(Long)` and fails with `Long cannot be cast to Integer`. Source: [issue #39120](https://github.com/apache/shardingsphere/issues/39120).
- **OBS-2:** The official SQL Federation documentation lists pagination among the supported distributed-query scenarios. Its limitations page only describes SQL Federation as experimental; it does not exclude parameterized pagination or `Long` numeric values. Sources: [SQL Federation](https://shardingsphere.apache.org/document/current/en/features/sql-federation/) and [SQL Federation limitations](https://shardingsphere.apache.org/document/current/en/features/sql-federation/limitation/).
- **OBS-3:** `AbstractPreparedStatementAdapter#setLong` forwards the boxed `Long` to the parameter list without conversion: `jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/adapter/AbstractPreparedStatementAdapter.java:82` and `:263`.
- **OBS-4:** `PaginationValueSQLConverter` converts a pagination parameter marker to `SqlDynamicParam`, and `SelectStatementConverter` installs it as the `SqlOrderBy` offset/fetch node: `kernel/sql-federation/compiler/src/main/java/org/apache/shardingsphere/sqlfederation/compiler/sql/ast/converter/segment/limit/PaginationValueSQLConverter.java:46` and `kernel/sql-federation/compiler/src/main/java/org/apache/shardingsphere/sqlfederation/compiler/sql/ast/converter/statement/type/SelectStatementConverter.java:56`.
- **OBS-5:** `StandardSQLFederationProcessor#createParameters` stores every JDBC value unchanged under `?0`, `?1`, etc.; `ExecutorBindContext#get` returns that same object: `kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessor.java:128` and `kernel/sql-federation/executor/src/main/java/org/apache/shardingsphere/sqlfederation/executor/context/ExecutorBindContext.java:57`.
- **OBS-6:** Existing tests verify the pagination dynamic-parameter index and empty-parameter execution, but not `Long` pagination binding. The SQL Federation pagination E2E cases are currently commented out: `kernel/sql-federation/compiler/src/test/java/org/apache/shardingsphere/sqlfederation/compiler/sql/ast/converter/segment/limit/PaginationValueSQLConverterTest.java:44`, `kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessorTest.java:193`, and `test/e2e/sql/src/test/resources/cases/dql/cases/db_tbl_sql_federation/e2e-dql-select-pagination-for-db-tbl-sql-federation.xml:20`.
- **OBS-7:** Same-repository searches found no merged fix for this type-binding failure. [PR #21229](https://github.com/apache/shardingsphere/pull/21229) added SQL Federation `LIMIT` plan conversion, while [PR #21386](https://github.com/apache/shardingsphere/pull/21386) changed federation decider logic. Neither adapts runtime pagination parameter types. The current upstream path in `StandardSQLFederationProcessor` remains unchanged in this respect.

### Root Cause

- **Observation:** The runtime chain is `setLong` → boxed `Long` → pagination `SqlDynamicParam` → unchanged SQL Federation `DataContext` value → `EnumerableLimit` expecting `Integer` (`OBS-1`, `OBS-3`, `OBS-4`, `OBS-5`).
- **Inference (`INF-1`):** SQL Federation lacks context-aware numeric adaptation for dynamic pagination parameters at its Calcite execution boundary (`OBS-1`, `OBS-4`, `OBS-5`).
- **Inference (`INF-2`):** This is not caused by MyBatis-Plus because the same failure is reproducible with plain JDBC `setLong` (`OBS-1`, `OBS-3`).
- **Inference (`INF-3`):** This is not a duplicate or already-fixed issue; the related historical PRs address different stages of SQL Federation processing, and current upstream retains the failing pass-through behavior (`OBS-5`, `OBS-7`).
- **Confidence:** High.

### Problem Analysis

- **Issue Type:** Bug.
- **Evidence:** Pagination is an intended SQL Federation capability, the JDBC layer accepts `setLong`, and the failure results from an internal type mismatch rather than unsupported SQL or configuration (`OBS-2`, `OBS-3`, `INF-1`).
- **Duplicate Check:** No same-root-cause issue or merged fixing PR was found (`OBS-7`, `INF-3`).
- **Compatibility Checklist:** Behavior change required; no configuration, API/SPI, or SQL syntax change should be necessary.

### Code-Level Design Suggestions

- **Affected Modules:** `kernel/sql-federation/core`, potentially `kernel/sql-federation/compiler` for pagination-index metadata, and `test/e2e/sql`.
- **Key Classes:** `StandardSQLFederationProcessor`, `PaginationValueSQLConverter`, `SelectStatementConverter`, and `ExecutorBindContext`.
- **Minimum Fix Scope:** Identify parameter indexes owned by `LIMIT`/`OFFSET` and adapt compatible numeric values only at the SQL Federation binding boundary. Do not globally convert `Long` values in the JDBC adapter or all `DataContext` parameters. Define and test out-of-range behavior; do not silently truncate values.
- **Required Test Scope:** Cover MySQL `LIMIT ?` with an in-range `Long`, MySQL offset/row-count pagination with `Long` values, an `Integer` control case, preservation of non-pagination `Long` parameters, and deterministic out-of-range behavior. Add an active cross-database SQL Federation E2E sentinel using `parameters="20:long"`.
- **Rollback Hint:** Keep the adaptation isolated to pagination binding so it can be reverted without configuration, metadata, or public API changes.

### Problem Conclusion

- **Evidence Confidence:** High.
- **Severity:** S2.
- **Impact Scope:** Confirmed for MySQL SQL Federation pagination through ShardingSphere-JDBC; broader database or Proxy impact should be checked because the federation execution boundary is shared.
- **Topology:** JDBC + Standalone.
- **Issue Type:** Bug.
- **Recommended Labels:** `type: bug`, `feature: SQL federation`, `in: kernel`, `in: JDBC`, `db: MySQL`.
- **Next Action:** Keep the issue open and accept a scoped PR that fixes pagination parameter binding with focused unit and E2E regression tests.
- **Compatibility:** Behavior only; no intended Config/API-SPI/SQL syntax changes.
- **Regression Scope:** Dynamic SQL Federation `LIMIT`/`OFFSET`, existing `Integer` pagination, non-pagination `Long` parameters, and numeric range handling.



Patch:
diff --git a/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessor.java b/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessor.java
index 039d6b08cfe2f..96fe9cf3163cb 100644
--- a/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessor.java
+++ b/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessor.java
@@ -28,6 +28,7 @@
 import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
+import org.apache.shardingsphere.infra.binder.context.segment.select.pagination.PaginationContext;
 import org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection;
 import org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
 import org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectStatementContext;
@@ -52,11 +53,14 @@
 import org.apache.shardingsphere.sqlfederation.executor.enumerable.implementor.EnumerableScanImplementor;
 import org.apache.shardingsphere.sqlfederation.resultset.SQLFederationResultSet;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -66,6 +70,10 @@
 @RequiredArgsConstructor
 public final class StandardSQLFederationProcessor implements SQLFederationProcessor {
     
+    private static final BigInteger MIN_PAGINATION_PARAMETER = BigInteger.valueOf(Integer.MIN_VALUE);
+    
+    private static final BigInteger MAX_PAGINATION_PARAMETER = BigInteger.valueOf(Integer.MAX_VALUE);
+    
     private final ShardingSphereStatistics statistics;
     
     private final JDBCExecutor jdbcExecutor;
@@ -127,12 +135,11 @@ public void release(final String currentDatabaseName, final String currentSchema
     
     @Override
     public ResultSet executePlan(final DriverExecutionPrepareEngine<JDBCExecutionUnit, Connection> prepareEngine, final JDBCExecutorCallback<? extends ExecuteResult> queryCallback,
-                                 final SQLFederationExecutionPlan executionPlan, final SQLFederationRelConverter converter,
-                                 final SQLFederationContext federationContext, final SchemaPlus schemaPlus) {
+                                 final SQLFederationExecutionPlan executionPlan, final SQLFederationRelConverter converter, final SQLFederationContext federationContext, final SchemaPlus schemaPlus) {
         Bindable<Object> executablePlan = SQLFederationExecutionPlan.toBindable(executionPlan.getPhysicalPlan(), Collections.emptyMap(), null, Prefer.ARRAY);
-        Map<String, Object> params = createParameters(federationContext.getQueryContext().getParameters());
-        Enumerator<Object> enumerator = executablePlan.bind(new ExecutorBindContext(converter, params)).enumerator();
         SelectStatementContext selectStatementContext = (SelectStatementContext) federationContext.getQueryContext().getSqlStatementContext();
+        Map<String, Object> params = createParameters(selectStatementContext, federationContext.getQueryContext().getParameters());
+        Enumerator<Object> enumerator = executablePlan.bind(new ExecutorBindContext(converter, params)).enumerator();
         List<Projection> expandProjections = selectStatementContext.getProjectionsContext().getExpandProjections();
         SQLFederationResultSet result = new SQLFederationResultSet(enumerator, schemaPlus, expandProjections,
                 selectStatementContext.getSqlStatement().getDatabaseType(), executionPlan.getResultColumnType(), federationContext.getProcessId());
@@ -142,15 +149,49 @@ public ResultSet executePlan(final DriverExecutionPrepareEngine<JDBCExecutionUni
         return result;
     }
     
-    private Map<String, Object> createParameters(final List<Object> params) {
+    private Map<String, Object> createParameters(final SelectStatementContext selectStatementContext, final List<Object> params) {
+        Collection<Integer> paginationParameterIndexes = getPaginationParameterIndexes(selectStatementContext);
         Map<String, Object> result = new HashMap<>(params.size(), 1F);
         int index = 0;
         for (Object each : params) {
-            result.put("?" + index++, each);
+            result.put("?" + index, paginationParameterIndexes.contains(index) ? convertPaginationParameter(each) : each);
+            index++;
+        }
+        return result;
+    }
+    
+    private Collection<Integer> getPaginationParameterIndexes(final SelectStatementContext selectStatementContext) {
+        Collection<Integer> result = new LinkedList<>();
+        collectPaginationParameterIndexes(result, selectStatementContext.getPaginationContext());
+        for (SelectStatementContext each : selectStatementContext.getSubqueryContexts().values()) {
+            result.addAll(getPaginationParameterIndexes(each));
         }
         return result;
     }
     
+    private void collectPaginationParameterIndexes(final Collection<Integer> result, final PaginationContext paginationContext) {
+        paginationContext.getOffsetParameterIndex().ifPresent(result::add);
+        paginationContext.getRowCountParameterIndex().ifPresent(result::add);
+    }
+    
+    private Object convertPaginationParameter(final Object value) {
+        if (!(value instanceof Number)) {
+            return value;
+        }
+        BigInteger integerValue = getIntegerValue((Number) value);
+        ShardingSpherePreconditions.checkState(integerValue.compareTo(MIN_PAGINATION_PARAMETER) >= 0 && integerValue.compareTo(MAX_PAGINATION_PARAMETER) <= 0,
+                () -> new IllegalArgumentException(String.format("SQL federation pagination parameter value `%s` is out of integer range.", value)));
+        return integerValue.intValue();
+    }
+    
+    private BigInteger getIntegerValue(final Number value) {
+        try {
+            return new BigDecimal(value.toString()).toBigIntegerExact();
+        } catch (final NumberFormatException | ArithmeticException ex) {
+            throw new IllegalArgumentException(String.format("SQL federation pagination parameter value `%s` must be an integer.", value), ex);
+        }
+    }
+    
     @Override
     public Convention getConvention() {
         return EnumerableConvention.INSTANCE;


Test patch:
diff --git a/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessorTest.java b/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessorTest.java
index 034b6b5ca7b0b..cd40206ed72ca 100644
--- a/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessorTest.java
+++ b/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessorTest.java
@@ -20,8 +20,10 @@
 import org.apache.calcite.adapter.enumerable.EnumerableConvention;
 import org.apache.calcite.adapter.enumerable.EnumerableRel;
 import org.apache.calcite.adapter.enumerable.EnumerableRel.Prefer;
+import org.apache.calcite.DataContext;
 import org.apache.calcite.linq4j.Enumerable;
 import org.apache.calcite.linq4j.Enumerator;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.runtime.Bindable;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.Table;
@@ -31,15 +33,30 @@
 import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
+import org.apache.shardingsphere.infra.binder.context.segment.select.pagination.PaginationContext;
 import org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectStatementContext;
 import org.apache.shardingsphere.infra.executor.sql.context.ExecutionUnit;
 import org.apache.shardingsphere.infra.executor.sql.execute.engine.driver.jdbc.JDBCExecutionUnit;
 import org.apache.shardingsphere.infra.executor.sql.execute.engine.driver.jdbc.JDBCExecutorCallback;
 import org.apache.shardingsphere.infra.executor.sql.execute.result.ExecuteResult;
 import org.apache.shardingsphere.infra.executor.sql.prepare.driver.DriverExecutionPrepareEngine;
+import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
+import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
+import org.apache.shardingsphere.infra.rule.attribute.table.TableMapperRuleAttribute;
 import org.apache.shardingsphere.infra.session.query.QueryContext;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubquerySegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionsSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.pagination.PaginationValueSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.pagination.limit.LimitSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.pagination.limit.ParameterMarkerLimitValueSegment;
 import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.TableSegmentBoundInfo;
 import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SimpleTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SubqueryTableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.TableSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
 import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
 import org.apache.shardingsphere.sqlfederation.compiler.SQLFederationExecutionPlan;
 import org.apache.shardingsphere.sqlfederation.compiler.context.CompilerContext;
@@ -48,20 +65,29 @@
 import org.apache.shardingsphere.sqlfederation.compiler.rel.converter.SQLFederationRelConverter;
 import org.apache.shardingsphere.sqlfederation.context.SQLFederationContext;
 import org.apache.shardingsphere.sqlfederation.engine.processor.SQLFederationProcessor;
+import org.apache.shardingsphere.sqlfederation.executor.context.ExecutorBindContext;
 import org.apache.shardingsphere.sqlfederation.resultset.SQLFederationResultSet;
 import org.apache.shardingsphere.sqlfederation.resultset.converter.DialectSQLFederationColumnTypeConverter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
 import org.mockito.MockedConstruction;
 import org.mockito.MockedStatic;
 
 import java.sql.Connection;
 import java.sql.ResultSet;
-import java.util.ArrayList;
+import java.sql.SQLException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -83,6 +109,8 @@ class StandardSQLFederationProcessorTest {
     
     private final String schemaName = "foo_schema";
     
+    private final DatabaseType databaseType = TypedSPILoader.getService(DatabaseType.class, "FIXTURE");
+    
     private SQLFederationProcessor processor;
     
     @BeforeEach
@@ -196,7 +224,7 @@ void assertExecutePlanWithEmptyParameters() {
         DriverExecutionPrepareEngine<JDBCExecutionUnit, Connection> prepareEngine = mock(DriverExecutionPrepareEngine.class);
         JDBCExecutorCallback<? extends ExecuteResult> callback = mock(JDBCExecutorCallback.class);
         SQLFederationContext federationContext = createFederationContext(true, null);
-        when(federationContext.getQueryContext().getParameters()).thenReturn(new LinkedList<>());
+        when(federationContext.getQueryContext().getParameters()).thenReturn(Collections.emptyList());
         CompilerContext compilerContext = mock(CompilerContext.class);
         SchemaPlus rootSchema = mockSchemaTreeWithTable();
         DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
@@ -212,7 +240,7 @@ void assertExecutePlanWithEmptyParameters() {
         SQLFederationExecutionPlan executionPlan = mock(SQLFederationExecutionPlan.class);
         EnumerableRel physicalPlan = mock(EnumerableRel.class);
         when(executionPlan.getPhysicalPlan()).thenReturn(physicalPlan);
-        when(executionPlan.getResultColumnType()).thenReturn(mock(org.apache.calcite.rel.type.RelDataType.class));
+        when(executionPlan.getResultColumnType()).thenReturn(mock(RelDataType.class));
         SQLFederationRelConverter converter = mock(SQLFederationRelConverter.class);
         Bindable<Object> bindable = mock(Bindable.class);
         Enumerator<Object> enumerator = mock(Enumerator.class);
@@ -230,6 +258,88 @@ void assertExecutePlanWithEmptyParameters() {
         }
     }
     
+    @SuppressWarnings("unchecked")
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("getPaginationParameterArguments")
+    void assertExecutePlanBindsPaginationParameters(final String name, final int offsetParameterIndex, final int rowCountParameterIndex, final List<Object> params,
+                                                    final Map<String, Object> expectedParams) throws SQLException {
+        SQLFederationContext federationContext = createFederationContext(false, offsetParameterIndex, rowCountParameterIndex, params);
+        SQLFederationExecutionPlan executionPlan = createExecutionPlan();
+        SQLFederationRelConverter converter = mock(SQLFederationRelConverter.class);
+        Bindable<Object> bindable = mockBindable();
+        ArgumentCaptor<DataContext> dataContextCaptor = ArgumentCaptor.forClass(DataContext.class);
+        try (
+                MockedStatic<SQLFederationExecutionPlan> mockedExecutionPlan = mockStatic(SQLFederationExecutionPlan.class);
+                MockedStatic<DatabaseTypedSPILoader> mockedSpiLoader = mockStatic(DatabaseTypedSPILoader.class)) {
+            mockedExecutionPlan.when(() -> SQLFederationExecutionPlan.toBindable(executionPlan.getPhysicalPlan(), Collections.emptyMap(), null, Prefer.ARRAY)).thenReturn(bindable);
+            mockedSpiLoader.when(() -> DatabaseTypedSPILoader
+                    .getService(eq(DialectSQLFederationColumnTypeConverter.class), any(DatabaseType.class))).thenReturn(mock(DialectSQLFederationColumnTypeConverter.class));
+            ResultSet result = processor.executePlan(mock(), mock(), executionPlan, converter, federationContext, mock(SchemaPlus.class));
+            ((SQLFederationResultSet) result).close();
+        }
+        verify(bindable).bind(dataContextCaptor.capture());
+        assertThat(((ExecutorBindContext) dataContextCaptor.getValue()).getParameters(), is(expectedParams));
+    }
+    
+    private static Stream<Arguments> getPaginationParameterArguments() {
+        return Stream.of(
+                Arguments.of("row_count_long", -1, 0, Collections.singletonList((Object) 20L), Collections.singletonMap("?0", (Object) 20)),
+                Arguments.of("offset_and_row_count_long", 0, 1, Arrays.asList((Object) 5L, 20L), createExpectedParams("?0", 5, "?1", 20)),
+                Arguments.of("preserve_non_pagination_long", -1, 1, Arrays.asList((Object) 11L, 20L), createExpectedParams("?0", 11L, "?1", 20)),
+                Arguments.of("preserve_non_number_pagination_parameter", -1, 0, Collections.singletonList((Object) "20"), Collections.singletonMap("?0", (Object) "20")));
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    void assertExecutePlanBindsNestedPaginationParameter() throws SQLException {
+        SelectStatement nestedSelectStatement = createSelectStatement(-1, 1);
+        SubqueryTableSegment from = new SubqueryTableSegment(0, 0, new SubquerySegment(0, 0, nestedSelectStatement, ""));
+        SQLFederationContext federationContext = createFederationContext(false, createSelectStatement(-1, -1, from), Arrays.asList((Object) 11L, 20L));
+        SQLFederationExecutionPlan executionPlan = createExecutionPlan();
+        SQLFederationRelConverter converter = mock(SQLFederationRelConverter.class);
+        Bindable<Object> bindable = mockBindable();
+        ArgumentCaptor<DataContext> dataContextCaptor = ArgumentCaptor.forClass(DataContext.class);
+        try (
+                MockedStatic<SQLFederationExecutionPlan> mockedExecutionPlan = mockStatic(SQLFederationExecutionPlan.class);
+                MockedStatic<DatabaseTypedSPILoader> mockedSpiLoader = mockStatic(DatabaseTypedSPILoader.class)) {
+            mockedExecutionPlan.when(() -> SQLFederationExecutionPlan.toBindable(executionPlan.getPhysicalPlan(), Collections.emptyMap(), null, Prefer.ARRAY)).thenReturn(bindable);
+            mockedSpiLoader.when(() -> DatabaseTypedSPILoader
+                    .getService(eq(DialectSQLFederationColumnTypeConverter.class), any(DatabaseType.class))).thenReturn(mock(DialectSQLFederationColumnTypeConverter.class));
+            ResultSet result = processor.executePlan(mock(), mock(), executionPlan, converter, federationContext, mock(SchemaPlus.class));
+            ((SQLFederationResultSet) result).close();
+        }
+        verify(bindable).bind(dataContextCaptor.capture());
+        assertThat(((ExecutorBindContext) dataContextCaptor.getValue()).getParameters(), is(createExpectedParams("?0", 11L, "?1", 20)));
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    void assertExecutePlanRejectsOutOfRangePaginationParameter() {
+        SQLFederationContext federationContext = createFederationContext(false, -1, 0, Collections.singletonList((Object) (Integer.MAX_VALUE + 1L)));
+        SQLFederationExecutionPlan executionPlan = createExecutionPlan();
+        Bindable<Object> bindable = mockBindable();
+        try (MockedStatic<SQLFederationExecutionPlan> mockedExecutionPlan = mockStatic(SQLFederationExecutionPlan.class)) {
+            mockedExecutionPlan.when(() -> SQLFederationExecutionPlan.toBindable(executionPlan.getPhysicalPlan(), Collections.emptyMap(), null, Prefer.ARRAY)).thenReturn(bindable);
+            IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+                    () -> processor.executePlan(mock(), mock(), executionPlan, mock(SQLFederationRelConverter.class), federationContext, mock(SchemaPlus.class)));
+            assertThat(ex.getMessage(), is("SQL federation pagination parameter value `2147483648` is out of integer range."));
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    void assertExecutePlanRejectsFractionalPaginationParameter() {
+        SQLFederationContext federationContext = createFederationContext(false, -1, 0, Collections.singletonList((Object) 20.5D));
+        SQLFederationExecutionPlan executionPlan = createExecutionPlan();
+        Bindable<Object> bindable = mockBindable();
+        try (MockedStatic<SQLFederationExecutionPlan> mockedExecutionPlan = mockStatic(SQLFederationExecutionPlan.class)) {
+            mockedExecutionPlan.when(() -> SQLFederationExecutionPlan.toBindable(executionPlan.getPhysicalPlan(), Collections.emptyMap(), null, Prefer.ARRAY)).thenReturn(bindable);
+            IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+                    () -> processor.executePlan(mock(), mock(), executionPlan, mock(SQLFederationRelConverter.class), federationContext, mock(SchemaPlus.class)));
+            assertThat(ex.getMessage(), is("SQL federation pagination parameter value `20.5` must be an integer."));
+        }
+    }
+    
     @SuppressWarnings("unchecked")
     @Test
     void assertExecutePlanPreviewAndNonPreview() {
@@ -253,7 +363,7 @@ void assertExecutePlanPreviewAndNonPreview() {
         SQLFederationExecutionPlan executionPlan = mock(SQLFederationExecutionPlan.class);
         EnumerableRel physicalPlan = mock(EnumerableRel.class);
         when(executionPlan.getPhysicalPlan()).thenReturn(physicalPlan);
-        when(executionPlan.getResultColumnType()).thenReturn(mock(org.apache.calcite.rel.type.RelDataType.class));
+        when(executionPlan.getResultColumnType()).thenReturn(mock(RelDataType.class));
         SQLFederationRelConverter converter = mock(SQLFederationRelConverter.class);
         Bindable<Object> bindable = mock(Bindable.class);
         Enumerator<Object> enumerator = mock(Enumerator.class);
@@ -281,6 +391,9 @@ void assertGetConvention() {
     private SQLFederationContext createFederationContext(final boolean preview, final TableSegmentBoundInfo tableSegmentBoundInfo) {
         SelectStatementContext sqlStatementContext = mock(SelectStatementContext.class, RETURNS_DEEP_STUBS);
         when(sqlStatementContext.getTablesContext().getSchemaNames()).thenReturn(Collections.singleton("pg_catalog"));
+        when(sqlStatementContext.getSqlStatement()).thenReturn(createSelectStatement(-1, -1));
+        when(sqlStatementContext.getPaginationContext()).thenReturn(new PaginationContext(null, null, Collections.emptyList()));
+        when(sqlStatementContext.getSubqueryContexts()).thenReturn(Collections.emptyMap());
         SimpleTableSegment tableSegment = mock(SimpleTableSegment.class, RETURNS_DEEP_STUBS);
         if (null == tableSegmentBoundInfo) {
             when(tableSegment.getTableName().getTableBoundInfo()).thenReturn(Optional.empty());
@@ -288,13 +401,79 @@ private SQLFederationContext createFederationContext(final boolean preview, fina
             when(tableSegment.getTableName().getTableBoundInfo()).thenReturn(Optional.of(tableSegmentBoundInfo));
         }
         when(sqlStatementContext.getTablesContext().getSimpleTables()).thenReturn(Collections.singleton(tableSegment));
+        return createFederationContext(preview, sqlStatementContext, Collections.singletonList((Object) 1));
+    }
+    
+    private SQLFederationContext createFederationContext(final boolean preview, final int offsetParameterIndex, final int rowCountParameterIndex, final List<Object> params) {
+        return createFederationContext(preview, createSelectStatementContext(createSelectStatement(offsetParameterIndex, rowCountParameterIndex)), params);
+    }
+    
+    private SQLFederationContext createFederationContext(final boolean preview, final SelectStatement selectStatement, final List<Object> params) {
+        return createFederationContext(preview, createSelectStatementContext(selectStatement), params);
+    }
+    
+    private SQLFederationContext createFederationContext(final boolean preview, final SelectStatementContext sqlStatementContext, final List<Object> params) {
         QueryContext queryContext = mock(QueryContext.class);
         when(queryContext.getSqlStatementContext()).thenReturn(sqlStatementContext);
         when(queryContext.getSql()).thenReturn("SELECT 1");
-        when(queryContext.getParameters()).thenReturn(new ArrayList<>(Collections.singletonList(1)));
+        when(queryContext.getParameters()).thenReturn(params);
         return new SQLFederationContext(preview, queryContext, mock(), "pid");
     }
     
+    private SelectStatementContext createSelectStatementContext(final SelectStatement selectStatement) {
+        return new SelectStatementContext(selectStatement, mockMetaData(), databaseName, Collections.emptyList());
+    }
+    
+    private ShardingSphereMetaData mockMetaData() {
+        ShardingSphereDatabase database = mock(ShardingSphereDatabase.class, RETURNS_DEEP_STUBS);
+        when(database.getRuleMetaData().getAttributes(TableMapperRuleAttribute.class)).thenReturn(Collections.emptyList());
+        ShardingSphereMetaData result = mock(ShardingSphereMetaData.class);
+        when(result.getDatabase(databaseName)).thenReturn(database);
+        return result;
+    }
+    
+    private SelectStatement createSelectStatement(final int offsetParameterIndex, final int rowCountParameterIndex) {
+        return createSelectStatement(offsetParameterIndex, rowCountParameterIndex, null);
+    }
+    
+    private SelectStatement createSelectStatement(final int offsetParameterIndex, final int rowCountParameterIndex, final TableSegment from) {
+        ProjectionsSegment projections = new ProjectionsSegment(0, 0);
+        projections.getProjections().add(new ColumnProjectionSegment(new ColumnSegment(0, 0, new IdentifierValue("foo_col"))));
+        return SelectStatement.builder().databaseType(databaseType).projections(projections).from(from).limit(createLimitSegment(offsetParameterIndex, rowCountParameterIndex)).build();
+    }
+    
+    private LimitSegment createLimitSegment(final int offsetParameterIndex, final int rowCountParameterIndex) {
+        PaginationValueSegment offset = -1 == offsetParameterIndex ? null : new ParameterMarkerLimitValueSegment(0, 0, offsetParameterIndex);
+        PaginationValueSegment rowCount = -1 == rowCountParameterIndex ? null : new ParameterMarkerLimitValueSegment(0, 0, rowCountParameterIndex);
+        return null == offset && null == rowCount ? null : new LimitSegment(0, 0, offset, rowCount);
+    }
+    
+    private static Map<String, Object> createExpectedParams(final Object... values) {
+        Map<String, Object> result = new HashMap<>(values.length / 2, 1F);
+        for (int i = 0; i < values.length; i += 2) {
+            result.put((String) values[i], values[i + 1]);
+        }
+        return result;
+    }
+    
+    @SuppressWarnings("unchecked")
+    private SQLFederationExecutionPlan createExecutionPlan() {
+        SQLFederationExecutionPlan result = mock(SQLFederationExecutionPlan.class);
+        when(result.getPhysicalPlan()).thenReturn(mock(EnumerableRel.class));
+        when(result.getResultColumnType()).thenReturn(mock(RelDataType.class));
+        return result;
+    }
+    
+    @SuppressWarnings("unchecked")
+    private Bindable<Object> mockBindable() {
+        Bindable<Object> result = mock(Bindable.class);
+        Enumerator<Object> enumerator = mock(Enumerator.class);
+        Enumerable<Object> enumerable = mock(Enumerable.class);
+        when(enumerable.enumerator()).thenReturn(enumerator);
+        when(result.bind(any())).thenReturn(enumerable);
+        return result;
+    }
+    
     @SuppressWarnings({"rawtypes", "unchecked"})
     private SchemaPlus mockFlatSchemaWithTable(final Table table) {
         Lookup tableLookup = mock(Lookup.class);
diff --git a/test/e2e/sql/src/test/resources/cases/dql/cases/db_tbl_sql_federation/e2e-dql-select-pagination-group-by-order-by-for-db-tbl-sql-federation.xml b/test/e2e/sql/src/test/resources/cases/dql/cases/db_tbl_sql_federation/e2e-dql-select-pagination-group-by-order-by-for-db-tbl-sql-federation.xml
index 69884882650dc..210ea8ceb80d0 100644
--- a/test/e2e/sql/src/test/resources/cases/dql/cases/db_tbl_sql_federation/e2e-dql-select-pagination-group-by-order-by-for-db-tbl-sql-federation.xml
+++ b/test/e2e/sql/src/test/resources/cases/dql/cases/db_tbl_sql_federation/e2e-dql-select-pagination-group-by-order-by-for-db-tbl-sql-federation.xml
@@ -38,6 +38,11 @@
         <assertion parameters="10:int" expected-data-source-name="read_dataset" />
     </test-case>
     
+    <test-case sql="SELECT * FROM t_order o INNER JOIN t_merchant m ON o.merchant_id = m.merchant_id WHERE o.user_id = ? ORDER BY o.order_id LIMIT ?" db-types="MySQL" scenario-types="db_tbl_sql_federation"
+               scenario-comments="Test MySQL federation pagination accepts long row count parameter.">
+        <assertion parameters="10:int, 2:long" expected-data-source-name="read_dataset" />
+    </test-case>
+    
     <test-case sql="SELECT * FROM t_order o INNER JOIN t_merchant m USING(merchant_id) WHERE o.user_id = ? ORDER BY o.order_id LIMIT 5, 2" db-types="MySQL,openGauss" scenario-types="db_tbl_sql_federation">
         <assertion parameters="10:int" expected-data-source-name="read_dataset" />
     </test-case>