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

io.stargate.sgv2.api.common.cql.CqlStrings Maven / Gradle / Ivy

There is a newer version: 2.1.0-BETA-19
Show newest version
/*
 * Copyright DataStax, Inc. and/or The Stargate 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 io.stargate.sgv2.api.common.cql;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class CqlStrings {

  private static final Set BUILT_IN_TYPES =
      Collections.unmodifiableSet(
          new HashSet<>(
              Arrays.asList(
                  "ascii",
                  "bigint",
                  "blob",
                  "boolean",
                  "counter",
                  "date",
                  "decimal",
                  "double",
                  "duration",
                  "float",
                  "inet",
                  "int",
                  "smallint",
                  "text",
                  "time",
                  "timestamp",
                  "timeuuid",
                  "tinyint",
                  "tuple",
                  "uuid",
                  "varchar",
                  "varint")));

  /**
   * Quote the given string; single quotes are escaped. If the given string is null, this method
   * returns a quoted empty string ({@code ''}).
   */
  public static String quote(String value) {
    return quote(value, '\'');
  }

  /**
   * Quote the given string; double quotes are escaped. If the given string is null, this method
   * returns a quoted empty string ({@code ""}).
   */
  public static String doubleQuote(String value) {
    return quote(value, '\"');
  }

  /**
   * Quotes text and escapes any existing quotes in the text. {@code String.replace()} is a bit too
   * inefficient (see JAVA-67, JAVA-1262).
   *
   * @param text The text.
   * @param quoteChar The character to use as a quote.
   * @return The text with surrounded in quotes with all existing quotes escaped with (i.e. '
   *     becomes '')
   */
  private static String quote(String text, char quoteChar) {
    if (text == null || text.isEmpty()) return emptyQuoted(quoteChar);

    int nbMatch = 0;
    int start = -1;
    do {
      start = text.indexOf(quoteChar, start + 1);
      if (start != -1) ++nbMatch;
    } while (start != -1);

    // no quotes found that need to be escaped, simply surround in quotes and return.
    if (nbMatch == 0) return quoteChar + text + quoteChar;

    // 2 for beginning and end quotes.
    // length for original text
    // nbMatch for escape characters to add to quotes to be escaped.
    int newLength = 2 + text.length() + nbMatch;
    char[] result = new char[newLength];
    result[0] = quoteChar;
    result[newLength - 1] = quoteChar;
    int newIdx = 1;
    for (int i = 0; i < text.length(); i++) {
      char c = text.charAt(i);
      if (c == quoteChar) {
        // escape quote with another occurrence.
        result[newIdx++] = c;
        result[newIdx++] = c;
      } else {
        result[newIdx++] = c;
      }
    }
    return new String(result);
  }

  /**
   * @param quoteChar " or '
   * @return A quoted empty string.
   */
  private static String emptyQuoted(char quoteChar) {
    // don't handle non quote characters, this is done so that these are interned and don't create
    // repeated empty quoted strings.
    assert quoteChar == '"' || quoteChar == '\'';
    if (quoteChar == '"') return "\"\"";
    else return "''";
  }

  /**
   * Given the text description of a CQL type, quote any UDT names inside it. For example:
   *
   * 
{@code
   * map => map
   * }
* * The input string is assumed to be in "internal" form, ie exact case and unquoted. For * simplicity, UDTs will be systematically quoted, even when they are not case-sensitive. */ public static String doubleQuoteUdts(String dataTypeName) { dataTypeName = dataTypeName.trim(); if (dataTypeName.isEmpty()) { throw new IllegalArgumentException("Invalid empty type name"); } int lastCharIdx = dataTypeName.length() - 1; final char firstChar = dataTypeName.charAt(0); if (firstChar == '\'') { // The quote should be terminated, and we should have at least 1 character + the quotes, if (dataTypeName.charAt(lastCharIdx) != '\'' || dataTypeName.length() < 3) { throw new IllegalArgumentException( "Malformed type name (missing closing quote): " + dataTypeName); } return dataTypeName; } // Normally we shouldn't get double-quoted types in the input, but if we do handle them // correctly if (firstChar == '"') { if (dataTypeName.charAt(lastCharIdx) != '"' || dataTypeName.length() < 3) { throw new IllegalArgumentException( "Malformed type name (missing closing quote): " + dataTypeName); } return dataTypeName; } int paramsIdx = dataTypeName.indexOf('<'); if (paramsIdx < 0) { // No quoting for known scalar types OR numbers (enough to check first char since // no legal type name starts with a digit) if (BUILT_IN_TYPES.contains(dataTypeName) || Character.isDigit(firstChar)) { return dataTypeName; } return quote(dataTypeName, '"'); } else { String baseTypeName = dataTypeName.substring(0, paramsIdx).trim(); if (dataTypeName.charAt(lastCharIdx) != '>') { throw new IllegalArgumentException( String.format( "Malformed type name: parameters for type %s are missing a closing '>'", baseTypeName)); } String paramsString = dataTypeName.substring(paramsIdx + 1, lastCharIdx); List parameters = splitParameters(paramsString, dataTypeName); // No need to quote baseTypeName. We know that anything that is parameterizable with `<>` // is case-insensitive. return baseTypeName + parameters.stream() .map(CqlStrings::doubleQuoteUdts) .collect(Collectors.joining(", ", "<", ">")); } } private static List splitParameters(String parametersString, String fullTypeName) { int openParam = 0; int currentStart = 0; int idx = currentStart; List parameters = new ArrayList<>(); while (idx < parametersString.length()) { switch (parametersString.charAt(idx)) { case ',': // Ignore if we're within a sub-parameter. if (openParam == 0) { parameters.add(parametersString.substring(currentStart, idx)); currentStart = idx + 1; } break; case '<': ++openParam; break; case '>': if (--openParam < 0) { throw new IllegalArgumentException( "Malformed type name: " + fullTypeName + " (unmatched closing '>')"); } break; case '"': idx = findClosingDoubleQuote(fullTypeName, parametersString, idx + 1) - 1; break; default: // intentional fallthrough } ++idx; } parameters.add(parametersString.substring(currentStart, idx)); return parameters; } // Returns the index "just after the double quote", so possibly str.length. private static int findClosingDoubleQuote(String fullTypeName, String str, int startIdx) { int idx = startIdx; while (idx < str.length()) { if (str.charAt(idx) == '"') { // Note: 2 double-quote is a way to escape the double-quote, so move to next first and // only exit if it's not a double-quote. Otherwise, continue. ++idx; if (idx >= str.length() || str.charAt(idx) != '"') { return idx; } } ++idx; } throw new IllegalArgumentException("Malformed type name: " + fullTypeName); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy