All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.miku.r2dbc.mysql.Query Maven / Gradle / Ivy

/*
 * Copyright 2018-2020 the original author or authors.
 *
 * Licensed 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 dev.miku.r2dbc.mysql;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Base class for statement parse, parametrize and mapping. It is a sealed class, has
 * implementations {@link SimpleQuery}, {@link PrepareQuery} and {@link TextQuery}.
 */
abstract class Query {

    abstract int getParameters();

    abstract ParameterIndex getIndexes(String identifier);

    static Query parse(String sql, boolean prepare) {
        int offset = findParamMark(sql, 0);

        if (offset < 0) {
            return SimpleQuery.INSTANCE;
        }

        Map nameKeyedParams = new HashMap<>();
        // An parameter name, used by singleton map, if SQL does not contains named-parameter, it will always be empty.
        String anyName = "";
        // The last named parameter end index of sql.
        int lastNamedEnd = 0;
        // The last parameter end index (whatever named or not) of sql.
        int lastParamEnd = 0;
        int length = sql.length();
        int paramCount = 0;
        // ONLY FOR PREPARE: Parsed SQL builder, if SQL does not contains named-parameter, it will always be null.
        StringBuilder sqlBuilder = null;
        // ONLY FOR TEXT: SQL parts split by parameters.
        List sqlParts;

        if (prepare) {
            // Prepare parsing, no need collect parts of SQL.
            sqlParts = Collections.emptyList();
        } else {
            sqlParts = new ArrayList<>();
        }

        while (offset >= 0 && offset < length) {
            if (!prepare) {
                sqlParts.add(subStr(sql, lastParamEnd, offset));
            }
            ++paramCount;
            // Assuming it has no name, then change later if it has, see command #Change.
            lastParamEnd = ++offset;

            if (offset < length) {
                char now = sql.charAt(offset);

                // Java style parameter name follow the '?'.
                if (Character.isJavaIdentifierStart(now)) {
                    int start = offset++;

                    while (offset < length) {
                        if (!Character.isJavaIdentifierPart(sql.charAt(offset))) {
                            break;
                        }

                        ++offset;
                    }

                    if (prepare) {
                        if (sqlBuilder == null) {
                            sqlBuilder = new StringBuilder(sql.length() - offset + start);
                        }

                        sqlBuilder.append(sql, lastNamedEnd, start);
                    }

                    // #Change: last parameter and last named parameter end index.
                    lastParamEnd = lastNamedEnd = offset;

                    String name = subStr(sql, start, offset);
                    int paramIndex = paramCount - 1;
                    ParameterIndex value = nameKeyedParams.get(name);

                    anyName = name;

                    if (value == null) {
                        nameKeyedParams.put(name, new ParameterIndex(paramIndex));
                    } else {
                        value.push(paramIndex);
                    }
                }
            } // offset is length or end of a parameter.

            if (offset < length) {
                offset = findParamMark(sql, offset);
            }
        }

        if (prepare) {
            String parsedSql;

            if (sqlBuilder == null) {
                parsedSql = sql;
            } else if (lastNamedEnd < length) {
                // Contains more plain sql.
                parsedSql = sqlBuilder.append(sql, lastNamedEnd, length).toString();
            } else {
                parsedSql = sqlBuilder.toString();
            }

            return new PrepareQuery(parsedSql, wrap(nameKeyedParams, anyName), paramCount);
        } else {
            sqlParts.add(subStr(sql, lastParamEnd, length));
            return new TextQuery(wrap(nameKeyedParams, anyName), sqlParts);
        }
    }

    /**
     * Locates the first occurrence of {@literal ?} return true in {@code sql} starting at {@code offset}.
     * 

* The SQL string may contain: * *

    *
  • Literals, enclosed in single quotes ({@literal '})
  • *
  • Literals, enclosed in double quotes ({@literal "})
  • *
  • Literals, enclosed in backtick quotes ({@literal `})
  • *
  • Escaped escapes or literal delimiters (i.e. {@literal ''}, {@literal ""} or {@literal ``)
  • *
  • Single-line comments beginning with {@literal --}
  • *
  • Multi-line comments beginning enclosed
  • *
* * @param sql the SQL string to search in. * @param offset the offset to start searching. * @return the offset or a negative integer if not found. */ private static int findParamMark(CharSequence sql, int offset) { int length = sql.length(); char ch; while (offset < length && offset >= 0) { ch = sql.charAt(offset++); switch (ch) { case '/': if (offset == length) { break; } if (sql.charAt(offset) == '*') { // Consume if '/* ... */' comment. while (++offset < length) { if (sql.charAt(offset) == '*' && offset + 1 < length && sql.charAt(offset + 1) == '/') { // If end of comment. offset += 2; break; } } break; } break; case '-': if (offset == length) { break; } if (sql.charAt(offset) == '-') { // Consume if '-- ... \n' comment. while (++offset < length) { char now = sql.charAt(offset); if (now == '\n' || now == '\r') { // If end of comment offset++; break; } } break; } break; case '`': case '\'': case '"': // Quote cases, should find same quote while (offset < length) { if (sql.charAt(offset++) == ch) { if (length == offset || sql.charAt(offset) != ch) { break; } ++offset; } } break; default: if (ch == '?') { return offset - 1; } break; } } return -1; } private static Map wrap(Map map, String anyKey) { switch (map.size()) { case 0: return Collections.emptyMap(); case 1: return Collections.singletonMap(anyKey, map.get(anyKey)); default: return map; } } private static String subStr(String sql, int start, int end) { if (start == end) { return ""; } else { return sql.substring(start, end); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy