Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.datastax.driver.core.querybuilder.Utils Maven / Gradle / Ivy
Go to download
Shaded version of DataStax Java Driver for Apache Cassandra
/*
* Copyright (C) 2012-2015 DataStax Inc.
*
* 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 com.datastax.driver.core.querybuilder;
import com.datastax.driver.core.*;
import com.datastax.driver.core.exceptions.InvalidTypeException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import static com.datastax.driver.$internal.com.google.common.base.Preconditions.checkNotNull;
// Static utilities private to the query builder
abstract class Utils {
private static final Pattern alphanumeric = Pattern.compile("\\w+"); // this includes _
private static final Pattern cnamePattern = Pattern.compile("\\w+(?:\\[.+\\])?");
/**
* Deal with case sensitivity for a given element id (keyspace, table, column, etc.)
*
* This method is used to convert identifiers provided by the client (through methods such as getKeyspace(String)),
* to the format used internally by the driver.
*
* We expect client-facing APIs to behave like cqlsh, that is:
* - identifiers that are mixed-case or contain special characters should be quoted.
* - unquoted identifiers will be lowercased: getKeyspace("Foo") will look for a keyspace named "foo"
*
* Copied from {@link Metadata#handleId(String)} to avoid making it public.
*/
static String handleId(String id) {
// Shouldn't really happen for this method, but no reason to fail here
if (id == null)
return null;
if (alphanumeric.matcher(id).matches())
return id.toLowerCase();
// Check if it's enclosed in quotes. If it is, remove them and unescape internal double quotes
if (!id.isEmpty() && id.charAt(0) == '"' && id.charAt(id.length() - 1) == '"')
return id.substring(1, id.length() - 1).replaceAll("\"\"", "\"");
// Otherwise, just return the id.
// Note that this is a bit at odds with the rules explained above, because the client can pass an
// identifier that contains special characters, without the need to quote it.
// Still it's better to be lenient here rather than throwing an exception.
return id;
}
static StringBuilder joinAndAppend(StringBuilder sb, CodecRegistry codecRegistry, String separator, List extends Appendeable> values, List variables) {
for (int i = 0; i < values.size(); i++) {
if (i > 0)
sb.append(separator);
values.get(i).appendTo(sb, variables, codecRegistry);
}
return sb;
}
static StringBuilder joinAndAppendNames(StringBuilder sb, CodecRegistry codecRegistry, String separator, List> values) {
for (int i = 0; i < values.size(); i++) {
if (i > 0)
sb.append(separator);
appendName(values.get(i), codecRegistry, sb);
}
return sb;
}
static StringBuilder joinAndAppendValues(StringBuilder sb, CodecRegistry codecRegistry, String separator, List> values, List variables) {
for (int i = 0; i < values.size(); i++) {
if (i > 0)
sb.append(separator);
appendValue(values.get(i), codecRegistry, sb, variables);
}
return sb;
}
static StringBuilder appendValue(Object value, CodecRegistry codecRegistry, StringBuilder sb, List variables) {
if (value == null) {
sb.append("null");
} else if (value instanceof BindMarker) {
sb.append(value);
} else if (value instanceof FCall) {
FCall fcall = (FCall) value;
sb.append(fcall.name).append('(');
for (int i = 0; i < fcall.parameters.length; i++) {
if (i > 0)
sb.append(',');
appendValue(fcall.parameters[i], codecRegistry, sb, variables);
}
sb.append(')');
} else if (value instanceof Cast) {
Cast cast = (Cast) value;
sb.append("CAST(");
appendName(cast.column, codecRegistry, sb);
sb.append(" AS ").append(cast.targetType).append(")");
} else if (value instanceof CName) {
appendName(((CName) value).name, codecRegistry, sb);
} else if (value instanceof RawString) {
sb.append(value.toString());
} else if (value instanceof List && !isSerializable(value)) {
// bind variables are not supported inside collection literals
appendList((List>) value, codecRegistry, sb, null);
} else if (value instanceof Set && !isSerializable(value)) {
// bind variables are not supported inside collection literals
appendSet((Set>) value, codecRegistry, sb, null);
} else if (value instanceof Map && !isSerializable(value)) {
// bind variables are not supported inside collection literals
appendMap((Map, ?>) value, codecRegistry, sb, null);
} else if (variables == null || !isSerializable(value)) {
// we are not collecting statement values (variables == null)
// or the value is meant to be forcefully appended to the query string:
// format it with the appropriate codec and append it now
TypeCodec codec = codecRegistry.codecFor(value);
sb.append(codec.format(value));
} else {
// Do not format the value nor append it to the query string:
// use a bind marker instead,
// but add the value the the statement's variables list
sb.append('?');
variables.add(value);
return sb;
}
return sb;
}
private static StringBuilder appendList(List> l, CodecRegistry codecRegistry, StringBuilder sb, List variables) {
sb.append('[');
for (int i = 0; i < l.size(); i++) {
if (i > 0)
sb.append(',');
appendValue(l.get(i), codecRegistry, sb, variables);
}
sb.append(']');
return sb;
}
private static StringBuilder appendSet(Set> s, CodecRegistry codecRegistry, StringBuilder sb, List variables) {
sb.append('{');
boolean first = true;
for (Object elt : s) {
if (first) first = false;
else sb.append(',');
appendValue(elt, codecRegistry, sb, variables);
}
sb.append('}');
return sb;
}
private static StringBuilder appendMap(Map, ?> m, CodecRegistry codecRegistry, StringBuilder sb, List variables) {
sb.append('{');
boolean first = true;
for (Map.Entry, ?> entry : m.entrySet()) {
if (first)
first = false;
else
sb.append(',');
appendValue(entry.getKey(), codecRegistry, sb, variables);
sb.append(':');
appendValue(entry.getValue(), codecRegistry, sb, variables);
}
sb.append('}');
return sb;
}
static boolean containsBindMarker(Object value) {
if (value instanceof BindMarker)
return true;
if (value instanceof FCall)
for (Object param : ((FCall) value).parameters)
if (containsBindMarker(param))
return true;
if (value instanceof Collection)
for (Object elt : (Collection) value)
if (containsBindMarker(elt))
return true;
if (value instanceof Map)
for (Map.Entry, ?> entry : ((Map, ?>) value).entrySet())
if (containsBindMarker(entry.getKey()) || containsBindMarker(entry.getValue()))
return true;
return false;
}
static boolean containsSpecialValue(Object value) {
if (value instanceof BindMarker || value instanceof FCall || value instanceof CName || value instanceof RawString)
return true;
if (value instanceof Collection)
for (Object elt : (Collection) value)
if (containsSpecialValue(elt))
return true;
if (value instanceof Map)
for (Map.Entry, ?> entry : ((Map, ?>) value).entrySet())
if (containsSpecialValue(entry.getKey()) || containsSpecialValue(entry.getValue()))
return true;
return false;
}
/**
* Return true if the given value is likely to find a suitable codec
* to be serialized as a query parameter.
* If the value is not serializable, it must be included in the query string.
* Non serializable values include special values such as function calls,
* column names and bind markers, and collections thereof.
* We also don't serialize fixed size number types. The reason is that if we do it, we will
* force a particular size (4 bytes for ints, ...) and for the query builder, we don't want
* users to have to bother with that.
*
* @param value the value to inspect.
* @return true if the value is serializable, false otherwise.
*/
static boolean isSerializable(Object value) {
if (containsSpecialValue(value))
return false;
if (value instanceof Number && !(value instanceof BigInteger || value instanceof BigDecimal))
return false;
if (value instanceof Collection)
for (Object elt : (Collection) value)
if (!isSerializable(elt))
return false;
if (value instanceof Map)
for (Map.Entry, ?> entry : ((Map, ?>) value).entrySet())
if (!isSerializable(entry.getKey()) || !isSerializable(entry.getValue()))
return false;
return true;
}
static boolean isIdempotent(Object value) {
if (value == null) {
return true;
} else if (value instanceof Assignment) {
Assignment assignment = (Assignment) value;
return assignment.isIdempotent();
} else if (value instanceof FCall) {
return false;
} else if (value instanceof RawString) {
return false;
} else if (value instanceof Collection) {
for (Object elt : ((Collection) value)) {
if (!isIdempotent(elt))
return false;
}
return true;
} else if (value instanceof Map) {
for (Map.Entry, ?> entry : ((Map, ?>) value).entrySet()) {
if (!isIdempotent(entry.getKey()) || !isIdempotent(entry.getValue()))
return false;
}
} else if (value instanceof Clause) {
Object clauseValue = ((Clause) value).firstValue();
return isIdempotent(clauseValue);
}
return true;
}
static StringBuilder appendName(String name, StringBuilder sb) {
name = name.trim();
// FIXME: checking for token( specifically is uber ugly, we'll need some better solution.
if (cnamePattern.matcher(name).matches() || name.startsWith("\"") || name.startsWith("token("))
sb.append(name);
else
sb.append('"').append(name).append('"');
return sb;
}
static StringBuilder appendName(Object name, CodecRegistry codecRegistry, StringBuilder sb) {
if (name instanceof String) {
appendName((String) name, sb);
} else if (name instanceof CName) {
appendName(((CName) name).name, codecRegistry, sb);
} else if (name instanceof FCall) {
FCall fcall = (FCall) name;
sb.append(fcall.name).append('(');
for (int i = 0; i < fcall.parameters.length; i++) {
if (i > 0)
sb.append(',');
appendValue(fcall.parameters[i], codecRegistry, sb, null);
}
sb.append(')');
} else if (name instanceof Alias) {
Alias alias = (Alias) name;
appendName(alias.column, codecRegistry, sb);
sb.append(" AS ").append(alias.alias);
} else if (name instanceof Cast) {
Cast cast = (Cast) name;
sb.append("CAST(");
appendName(cast.column, codecRegistry, sb);
sb.append(" AS ").append(cast.targetType).append(")");
} else if (name instanceof RawString) {
sb.append(((RawString) name).str);
} else {
throw new IllegalArgumentException(String.format("Invalid column %s of type unknown of the query builder", name));
}
return sb;
}
/**
* Utility method to serialize user-provided values.
*
* This method is a copy of the one declared in {@link com.datastax.driver.core.SimpleStatement}, it was duplicated
* to avoid having to make it public.
*
* It is useful in situations where there is no metadata available and the underlying CQL
* type for the values is not known.
*
* This situation happens when a {@link com.datastax.driver.core.SimpleStatement}
* or a {@link com.datastax.driver.core.querybuilder.BuiltStatement} (Query Builder) contain values;
* in these places, the driver has no way to determine the right CQL type to use.
*
* This method performs a best-effort heuristic to guess which codec to use.
* Note that this is not particularly efficient as the codec registry needs to iterate over
* the registered codecs until it finds a suitable one.
*
* @param values The values to convert.
* @param protocolVersion The protocol version to use.
* @param codecRegistry The {@link CodecRegistry} to use.
* @return The converted values.
*/
static ByteBuffer[] convert(Object[] values, ProtocolVersion protocolVersion, CodecRegistry codecRegistry) {
ByteBuffer[] serializedValues = new ByteBuffer[values.length];
for (int i = 0; i < values.length; i++) {
Object value = values[i];
if (value == null) {
// impossible to locate the right codec when object is null,
// so forcing the result to null
serializedValues[i] = null;
} else {
if (value instanceof Token) {
// bypass CodecRegistry for Token instances
serializedValues[i] = ((Token) value).serialize(protocolVersion);
} else {
try {
TypeCodec codec = codecRegistry.codecFor(value);
serializedValues[i] = codec.serialize(value, protocolVersion);
} catch (Exception e) {
// Catch and rethrow to provide a more helpful error message (one that include which value is bad)
throw new InvalidTypeException(String.format("Value %d of type %s does not correspond to any CQL3 type", i, value.getClass()), e);
}
}
}
}
return serializedValues;
}
/**
* Utility method to assemble different routing key components into a single {@link ByteBuffer}.
* Mainly intended for statements that need to generate a routing key out of their current values.
*
* This method is a copy of the one declared in {@link com.datastax.driver.core.SimpleStatement}, it was duplicated
* to avoid having to make it public.
*
* @param buffers the components of the routing key.
* @return A ByteBuffer containing the serialized routing key
*/
static ByteBuffer compose(ByteBuffer... buffers) {
if (buffers.length == 1)
return buffers[0];
int totalLength = 0;
for (ByteBuffer bb : buffers)
totalLength += 2 + bb.remaining() + 1;
ByteBuffer out = ByteBuffer.allocate(totalLength);
for (ByteBuffer buffer : buffers) {
ByteBuffer bb = buffer.duplicate();
putShortLength(out, bb.remaining());
out.put(bb);
out.put((byte) 0);
}
out.flip();
return out;
}
static void putShortLength(ByteBuffer bb, int length) {
bb.put((byte) ((length >> 8) & 0xFF));
bb.put((byte) (length & 0xFF));
}
static abstract class Appendeable {
abstract void appendTo(StringBuilder sb, List values, CodecRegistry codecRegistry);
abstract boolean containsBindMarker();
}
static class RawString {
private final String str;
RawString(String str) {
this.str = str;
}
@Override
public String toString() {
return str;
}
}
static class FCall {
private final String name;
private final Object[] parameters;
FCall(String name, Object... parameters) {
checkNotNull(name);
this.name = name;
this.parameters = parameters;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(name).append('(');
for (int i = 0; i < parameters.length; i++) {
if (i > 0)
sb.append(',');
sb.append(parameters[i]);
}
sb.append(')');
return sb.toString();
}
}
static class CName {
private final String name;
CName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
static class Alias {
private final Object column;
private final String alias;
Alias(Object column, String alias) {
this.column = column;
this.alias = alias;
}
@Override
public String toString() {
return String.format("%s AS %s", column, alias);
}
}
static class Cast {
private final Object column;
private final DataType targetType;
Cast(Object column, DataType targetType) {
this.column = column;
this.targetType = targetType;
}
@Override
public String toString() {
return String.format("CAST(%s AS %s)", column, targetType);
}
}
}