You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2023/12/05 12:25:51 UTC

(shardingsphere) branch master updated: Throw exception when placeholders exceed 65535 in MySQL prepared statement (#29296)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new c9906e96b3c Throw exception when placeholders exceed 65535 in MySQL prepared statement (#29296)
c9906e96b3c is described below

commit c9906e96b3c4a0657a58b48f6b7d45c9f9a4aed4
Author: Zhengqiang Duan <du...@apache.org>
AuthorDate: Tue Dec 5 20:25:42 2023 +0800

    Throw exception when placeholders exceed 65535 in MySQL prepared statement (#29296)
    
    * Throw exception when placeholders exceed 65535 in MySQL prepared statement
    
    * Update license
---
 .../exception/TooManyPlaceholdersException.java    | 28 ++++++++++++++++++++++
 .../mysql/mapper/MySQLDialectExceptionMapper.java  |  8 +++++--
 .../exception/mysql/vendor/MySQLVendorError.java   |  2 ++
 .../prepare/MySQLComStmtPrepareExecutor.java       |  5 ++++
 .../prepare/MySQLComStmtPrepareExecutorTest.java   | 23 ++++++++++++++++++
 5 files changed, 64 insertions(+), 2 deletions(-)

diff --git a/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/exception/TooManyPlaceholdersException.java b/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/exception/TooManyPlaceholdersException.java
new file mode 100644
index 00000000000..5115a9901e7
--- /dev/null
+++ b/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/exception/TooManyPlaceholdersException.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.infra.exception.mysql.exception;
+
+import org.apache.shardingsphere.infra.exception.dialect.exception.SQLDialectException;
+
+/**
+ * Too many placeholders exception.
+ */
+public final class TooManyPlaceholdersException extends SQLDialectException {
+    
+    private static final long serialVersionUID = -220866708278903418L;
+}
diff --git a/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/mapper/MySQLDialectExceptionMapper.java b/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/mapper/MySQLDialectExceptionMapper.java
index ecd3b7132ce..0b61b515265 100644
--- a/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/mapper/MySQLDialectExceptionMapper.java
+++ b/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/mapper/MySQLDialectExceptionMapper.java
@@ -17,6 +17,8 @@
 
 package org.apache.shardingsphere.infra.exception.mysql.mapper;
 
+import org.apache.shardingsphere.infra.exception.core.external.sql.type.generic.UnknownSQLException;
+import org.apache.shardingsphere.infra.exception.core.external.sql.vendor.VendorError;
 import org.apache.shardingsphere.infra.exception.dialect.exception.SQLDialectException;
 import org.apache.shardingsphere.infra.exception.dialect.exception.connection.TooManyConnectionsException;
 import org.apache.shardingsphere.infra.exception.dialect.exception.data.InsertColumnsAndValuesMismatchedException;
@@ -38,9 +40,8 @@ import org.apache.shardingsphere.infra.exception.mysql.exception.UnknownCharsetE
 import org.apache.shardingsphere.infra.exception.mysql.exception.UnknownCollationException;
 import org.apache.shardingsphere.infra.exception.mysql.exception.UnknownSystemVariableException;
 import org.apache.shardingsphere.infra.exception.mysql.exception.UnsupportedPreparedStatementException;
+import org.apache.shardingsphere.infra.exception.mysql.exception.TooManyPlaceholdersException;
 import org.apache.shardingsphere.infra.exception.mysql.vendor.MySQLVendorError;
-import org.apache.shardingsphere.infra.exception.core.external.sql.type.generic.UnknownSQLException;
-import org.apache.shardingsphere.infra.exception.core.external.sql.vendor.VendorError;
 
 import java.sql.SQLException;
 
@@ -83,6 +84,9 @@ public final class MySQLDialectExceptionMapper implements SQLDialectExceptionMap
         if (sqlDialectException instanceof UnsupportedPreparedStatementException) {
             return toSQLException(MySQLVendorError.ER_UNSUPPORTED_PS);
         }
+        if (sqlDialectException instanceof TooManyPlaceholdersException) {
+            return toSQLException(MySQLVendorError.ER_PS_MANY_PARAM);
+        }
         if (sqlDialectException instanceof UnknownCharsetException) {
             return toSQLException(MySQLVendorError.ER_UNKNOWN_CHARACTER_SET, ((UnknownCharsetException) sqlDialectException).getCharset());
         }
diff --git a/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/vendor/MySQLVendorError.java b/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/vendor/MySQLVendorError.java
index ace2d4f5f9a..67a9489bc81 100644
--- a/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/vendor/MySQLVendorError.java
+++ b/infra/exception/dialect/type/mysql/src/main/java/org/apache/shardingsphere/infra/exception/mysql/vendor/MySQLVendorError.java
@@ -70,6 +70,8 @@ public enum MySQLVendorError implements VendorError {
     
     ER_UNKNOWN_COLLATION(XOpenSQLState.GENERAL_ERROR, 1273, "Unknown collation: '%s'"),
     
+    ER_PS_MANY_PARAM(XOpenSQLState.GENERAL_ERROR, 1390, "Prepared statement contains too many placeholders"),
+    
     ER_ERROR_ON_MODIFYING_GTID_EXECUTED_TABLE(XOpenSQLState.GENERAL_ERROR, 3176,
             "Please do not modify the %s table with an XA transaction. This is an internal system table used to store GTIDs for committed transactions. "
                     + "Although modifying it can lead to an inconsistent GTID state, if necessary you can modify it with a non-XA transaction.");
diff --git a/proxy/frontend/type/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLComStmtPrepareExecutor.java b/proxy/frontend/type/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLComStmtPrepareExecutor.java
index 8e63f755af3..ee174ed6857 100644
--- a/proxy/frontend/type/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLComStmtPrepareExecutor.java
+++ b/proxy/frontend/type/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLComStmtPrepareExecutor.java
@@ -35,6 +35,8 @@ import org.apache.shardingsphere.infra.binder.context.statement.dml.SelectStatem
 import org.apache.shardingsphere.infra.binder.engine.SQLBindEngine;
 import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
 import org.apache.shardingsphere.infra.database.core.type.DatabaseTypeRegistry;
+import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions;
+import org.apache.shardingsphere.infra.exception.mysql.exception.TooManyPlaceholdersException;
 import org.apache.shardingsphere.infra.exception.mysql.exception.UnsupportedPreparedStatementException;
 import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn;
@@ -66,6 +68,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
 @RequiredArgsConstructor
 public final class MySQLComStmtPrepareExecutor implements CommandExecutor {
     
+    private static final int MAX_PARAMETER_COUNT = 65535;
+    
     private final MySQLComStmtPreparePacket packet;
     
     private final ConnectionSession connectionSession;
@@ -101,6 +105,7 @@ public final class MySQLComStmtPrepareExecutor implements CommandExecutor {
         Collection<DatabasePacket> result = new LinkedList<>();
         Collection<Projection> projections = getProjections(sqlStatementContext);
         int parameterCount = sqlStatementContext.getSqlStatement().getParameterCount();
+        ShardingSpherePreconditions.checkState(parameterCount <= MAX_PARAMETER_COUNT, TooManyPlaceholdersException::new);
         result.add(new MySQLComStmtPrepareOKPacket(statementId, projections.size(), parameterCount, 0));
         int characterSet = connectionSession.getAttributeMap().attr(MySQLConstants.MYSQL_CHARACTER_SET_ATTRIBUTE_KEY).get().getId();
         int statusFlags = ServerStatusFlagCalculator.calculateFor(connectionSession);
diff --git a/proxy/frontend/type/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLComStmtPrepareExecutorTest.java b/proxy/frontend/type/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLComStmtPrepareExecutorTest.java
index 6b1a36ed78d..dd011c7bee0 100644
--- a/proxy/frontend/type/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLComStmtPrepareExecutorTest.java
+++ b/proxy/frontend/type/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLComStmtPrepareExecutorTest.java
@@ -32,6 +32,7 @@ import org.apache.shardingsphere.infra.binder.context.statement.dml.InsertStatem
 import org.apache.shardingsphere.infra.binder.context.statement.dml.SelectStatementContext;
 import org.apache.shardingsphere.infra.binder.context.statement.dml.UpdateStatementContext;
 import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
+import org.apache.shardingsphere.infra.exception.mysql.exception.TooManyPlaceholdersException;
 import org.apache.shardingsphere.infra.exception.mysql.exception.UnsupportedPreparedStatementException;
 import org.apache.shardingsphere.infra.hint.HintValueContext;
 import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
@@ -165,6 +166,28 @@ class MySQLComStmtPrepareExecutorTest {
         return byteBuf.getUnsignedShortLE(17);
     }
     
+    @Test
+    void assertPrepareInsertStatementWithTooManyPlaceholders() {
+        String sql = createTooManyPlaceholdersSQL();
+        when(packet.getSQL()).thenReturn(sql);
+        when(packet.getHintValueContext()).thenReturn(new HintValueContext());
+        int connectionId = 2;
+        when(connectionSession.getConnectionId()).thenReturn(connectionId);
+        when(connectionSession.getDefaultDatabaseName()).thenReturn("foo_db");
+        MySQLStatementIdGenerator.getInstance().registerConnection(connectionId);
+        ContextManager contextManager = mockContextManager();
+        when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+        assertThrows(TooManyPlaceholdersException.class, () -> new MySQLComStmtPrepareExecutor(packet, connectionSession).execute());
+    }
+    
+    private String createTooManyPlaceholdersSQL() {
+        StringBuilder builder = new StringBuilder("INSERT INTO USER (ID, NAME, AGE) VALUES (?, ?, ?)");
+        for (int index = 0; index < Short.MAX_VALUE; index++) {
+            builder.append(", (?, ?, ?)");
+        }
+        return builder.toString();
+    }
+    
     @Test
     void assertPrepareUpdateStatement() {
         String sql = "update user set name = ?, age = ? where id = ?";