4242public class QueryInfo {
4343
4444 private static final String OPENING_MARKERS = "`'\" " ;
45+ private static final String OPENING_MARKERS_WITH_PARENS = "`'\" (" ;
4546 private static final String CLOSING_MARKERS = "`'\" " ;
47+ private static final String CLOSING_MARKERS_WITH_PARENS = "`'\" )" ;
4648 private static final String OVERRIDING_MARKERS = "" ;
4749
4850 private static final String SELECT_STATEMENT = "SELECT" ;
@@ -57,6 +59,7 @@ public class QueryInfo {
5759 private static final String AS_CLAUSE = "AS" ;
5860 private static final String [] ODKU_CLAUSE = new String [] { "ON" , "DUPLICATE" , "KEY" , "UPDATE" };
5961 private static final String LAST_INSERT_ID_FUNC = "LAST_INSERT_ID" ;
62+ private static final String INTO_CLAUSE = "INTO" ;
6063
6164 private QueryInfo baseQueryInfo = null ;
6265
@@ -814,15 +817,16 @@ public static boolean isReadOnlySafeQuery(String sql, boolean noBackslashEscapes
814817 public static QueryReturnType getQueryReturnType (String sql , boolean noBackslashEscapes ) {
815818 /*
816819 * Statements that return results:
817- * - ANALYZE; CHECK/CHECKSUM; DESC/DESCRIBE; EXPLAIN; HELP; OPTIMIZE; REPAIR; SELECT; SHOW; TABLE; VALUES; WITH ... SELECT|TABLE|VALUES ...; XA RECOVER;
820+ * - ANALYZE; CHECK/CHECKSUM; DESC/DESCRIBE; EXPLAIN; HELP; OPTIMIZE; REPAIR; SELECT (exc. [... INTO]); SHOW; TABLE (exc. [... INTO]); VALUES; WITH ...
821+ * SELECT|TABLE|VALUES ...; XA RECOVER;
818822 *
819823 * Statements that may return results:
820824 * - CALL; EXECUTE;
821825 *
822826 * Statements that do not return results:
823827 * - ALTER; BINLOG; CACHE; CHANGE; CLONE; COMMIT; CREATE; DEALLOCATE; DELETE; DO; DROP; FLUSH; GET; GRANT; HANDLER; IMPORT; INSERT; INSTALL; KILL; LOAD;
824- * - LOCK; PREPARE; PURGE; RELEASE; RENAME; REPLACE; RESET; RESIGNAL; RESTART; REVOKE; ROLLBACK; SAVEPOINT; SET; SHUTDOWN; SIGNAL; START; STOP ;
825- * - TRUNCATE; UNINSTALL; UNLOCK; UPDATE; USE; WITH ... DELETE|UPDATE ...; XA [!RECOVER];
828+ * LOCK; PREPARE; PURGE; RELEASE; RENAME; REPLACE; RESET; RESIGNAL; RESTART; REVOKE; ROLLBACK; SAVEPOINT; SELECT ... INTO; SET; SHUTDOWN; SIGNAL; START;
829+ * STOP; TABLE ... INTO; TRUNCATE; UNINSTALL; UNLOCK; UPDATE; USE; WITH ... DELETE|UPDATE ...; XA [!RECOVER];
826830 */
827831 int statementKeywordPos = indexOfStatementKeyword (sql , noBackslashEscapes );
828832 if (statementKeywordPos == -1 ) {
@@ -848,9 +852,11 @@ public static QueryReturnType getQueryReturnType(String sql, boolean noBackslash
848852 } else if (firstStatementChar == 'R' && StringUtils .startsWithIgnoreCaseAndWs (sql , "REPAIR" , statementKeywordPos )) {
849853 return QueryReturnType .PRODUCES_RESULT_SET ;
850854 } else if (firstStatementChar == 'S' && (StringUtils .startsWithIgnoreCaseAndWs (sql , "SELECT" , statementKeywordPos )
855+ && !containsIntoClause (statementKeywordPos , sql , noBackslashEscapes )
851856 || StringUtils .startsWithIgnoreCaseAndWs (sql , "SHOW" , statementKeywordPos ))) {
852857 return QueryReturnType .PRODUCES_RESULT_SET ;
853- } else if (firstStatementChar == 'T' && StringUtils .startsWithIgnoreCaseAndWs (sql , "TABLE" , statementKeywordPos )) {
858+ } else if (firstStatementChar == 'T' && StringUtils .startsWithIgnoreCaseAndWs (sql , "TABLE" , statementKeywordPos )
859+ && !containsIntoClause (statementKeywordPos , sql , noBackslashEscapes )) {
854860 return QueryReturnType .PRODUCES_RESULT_SET ;
855861 } else if (firstStatementChar == 'V' && StringUtils .startsWithIgnoreCaseAndWs (sql , "VALUES" , statementKeywordPos )) {
856862 return QueryReturnType .PRODUCES_RESULT_SET ;
@@ -870,6 +876,23 @@ public static QueryReturnType getQueryReturnType(String sql, boolean noBackslash
870876 return QueryReturnType .DOES_NOT_PRODUCE_RESULT_SET ;
871877 }
872878
879+ /**
880+ * Checks whether the given input string contains an "INTO" clause, starting from the specified position, while optionally considering MySQL's
881+ * NO_BACKSLASH_ESCAPES SQL mode for escape sequence handling.
882+ *
883+ * @param startingPosition
884+ * the position in the string to begin the search
885+ * @param searchIn
886+ * the string to search for the "INTO" clause
887+ * @param noBackslashEscapes
888+ * whether backslash escapes should be ignored (true for NO_BACKSLASH_ESCAPES mode)
889+ * @return {@code true} if the "INTO" clause is found in the input string from the given position, {@code false} otherwise
890+ */
891+ private static boolean containsIntoClause (int startingPosition , String searchIn , boolean noBackslashEscapes ) {
892+ return StringUtils .indexOfIgnoreCase (startingPosition , searchIn , INTO_CLAUSE , OPENING_MARKERS_WITH_PARENS , CLOSING_MARKERS_WITH_PARENS ,
893+ noBackslashEscapes ? SearchMode .__MRK_COM_MYM_HNT_WS : SearchMode .__FULL ) != -1 ;
894+ }
895+
873896 /**
874897 * Returns the context of the WITH statement. The context can be: SELECT, TABLE, VALUES, UPDATE or DELETE. This operation does not take into consideration
875898 * the multiplicity of queries in the specified SQL.
@@ -885,7 +908,7 @@ private static String getContextForWithStatement(String sql, boolean noBackslash
885908 String commentsFreeSql = StringUtils .stripCommentsAndHints (sql , OPENING_MARKERS , CLOSING_MARKERS , !noBackslashEscapes );
886909
887910 // Iterate through statement words, skipping all sub-queries sections enclosed by parens.
888- StringInspector strInspector = new StringInspector (commentsFreeSql , OPENING_MARKERS + "(" , CLOSING_MARKERS + ")" , OPENING_MARKERS ,
911+ StringInspector strInspector = new StringInspector (commentsFreeSql , OPENING_MARKERS_WITH_PARENS , CLOSING_MARKERS_WITH_PARENS , OPENING_MARKERS ,
889912 noBackslashEscapes ? SearchMode .__MRK_COM_MYM_HNT_WS : SearchMode .__BSE_MRK_COM_MYM_HNT_WS );
890913 boolean asFound = false ;
891914 while (true ) {
0 commit comments