/*
* 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.CodecRegistry;
import java.util.List;
public abstract class Clause extends Utils.Appendeable {
abstract String name();
abstract Object firstValue();
private static abstract class AbstractClause extends Clause {
final String name;
private AbstractClause(String name) {
this.name = name;
}
@Override
String name() {
return name;
}
}
static class SimpleClause extends AbstractClause {
private final String op;
private final Object value;
SimpleClause(String name, String op, Object value) {
super(name);
this.op = op;
this.value = value;
}
@Override
void appendTo(StringBuilder sb, List<Object> variables, CodecRegistry codecRegistry) {
Utils.appendName(name, sb).append(op);
Utils.appendValue(value, codecRegistry, sb, variables);
}
@Override
Object firstValue() {
return value;
}
@Override
boolean containsBindMarker() {
return Utils.containsBindMarker(value);
}
}
static class InClause extends AbstractClause {
private final List<?> values;
InClause(String name, List<?> values) {
super(name);
this.values = values;
if (values == null)
throw new IllegalArgumentException("Missing values for IN clause");
if (values.size() > 65535)
throw new IllegalArgumentException("Too many values for IN clause, the maximum allowed is 65535");
}
@Override
void appendTo(StringBuilder sb, List<Object> variables, CodecRegistry codecRegistry) {
// We special case the case of just one bind marker because there is little
// reasons to do:
// ... IN (?) ...
// since in that case it's more elegant to use an equal. On the other side,
// it is a lot more useful to do:
// ... IN ? ...
// which binds the variable to the full list the IN is on.
if (values.size() == 1 && values.get(0) instanceof BindMarker) {
Utils.appendName(name, sb).append(" IN ").append(values.get(0));
return;
}
Utils.appendName(name, sb).append(" IN (");
Utils.joinAndAppendValues(sb, codecRegistry, values, variables).append(')');
}
@Override
Object firstValue() {
return values.isEmpty() ? null : values.get(0);
}
@Override
boolean containsBindMarker() {
for (Object value : values)
if (Utils.containsBindMarker(value))
return true;
return false;
}
}
static class ContainsClause extends AbstractClause {
private final Object value;
ContainsClause(String name, Object value) {
super(name);
this.value = value;
if (value == null)
throw new IllegalArgumentException("Missing value for CONTAINS clause");
}
@Override
void appendTo(StringBuilder sb, List<Object> variables, CodecRegistry codecRegistry) {
Utils.appendName(name, sb).append(" CONTAINS ");
Utils.appendValue(value, codecRegistry, sb, variables);
}
@Override
Object firstValue() {
return value;
}
@Override
boolean containsBindMarker() {
return Utils.containsBindMarker(value);
}
}
static class ContainsKeyClause extends AbstractClause {
private final Object value;
ContainsKeyClause(String name, Object value) {
super(name);
this.value = value;
if (value == null)
throw new IllegalArgumentException("Missing value for CONTAINS KEY clause");
}
@Override
void appendTo(StringBuilder sb, List<Object> variables, CodecRegistry codecRegistry) {
Utils.appendName(name, sb).append(" CONTAINS KEY ");
Utils.appendValue(value, codecRegistry, sb, variables);
}
@Override
Object firstValue() {
return value;
}
@Override
boolean containsBindMarker() {
return Utils.containsBindMarker(value);
}
}
static class CompoundClause extends Clause {
private String op;
private final List<String> names;
private final List<?> values;
CompoundClause(List<String> names, String op, List<?> values) {
assert names.size() == values.size();
this.op = op;
this.names = names;
this.values = values;
}
@Override
String name() {
// This is only used for routing key purpose, and so far CompoundClause
// are not allowed for the partitionKey anyway
return null;
}
@Override
Object firstValue() {
// This is only used for routing key purpose, and so far CompoundClause
// are not allowed for the partitionKey anyway
return null;
}
@Override
boolean containsBindMarker() {
for (Object value : values)
if (Utils.containsBindMarker(value))
return true;
return false;
}
@Override
void appendTo(StringBuilder sb, List<Object> variables, CodecRegistry codecRegistry) {
sb.append("(");
for (int i = 0; i < names.size(); i++) {
if (i > 0)
sb.append(",");
Utils.appendName(names.get(i), sb);
}
sb.append(")").append(op).append("(");
for (int i = 0; i < values.size(); i++) {
if (i > 0)
sb.append(",");
Utils.appendValue(values.get(i), codecRegistry, sb, variables);
}
sb.append(")");
}
}
static class CompoundInClause extends Clause {
private final List<String> names;
private final List<?> valueLists;
public CompoundInClause(List<String> names, List<?> valueLists) {
if (valueLists == null)
throw new IllegalArgumentException("Missing values for IN clause");
if (valueLists.size() > 65535)
throw new IllegalArgumentException("Too many values for IN clause, the maximum allowed is 65535");
for (Object value : valueLists) {
if (value instanceof List) {
List<?> tuple = (List<?>) value;
if (tuple.size() != names.size()) {
throw new IllegalArgumentException(String.format("The number of names (%d) and values (%d) don't match", names.size(), tuple.size()));
}
} else if (!(value instanceof BindMarker)) {
throw new IllegalArgumentException(String.format("Wrong element type for values list, expected List or BindMarker, got %s", value.getClass().getName()));
}
}
this.names = names;
this.valueLists = valueLists;
}
@Override
String name() {
// This is only used for routing key purpose, and so far CompoundClause
// are not allowed for the partitionKey anyway
return null;
}
@Override
Object firstValue() {
// This is only used for routing key purpose, and so far CompoundClause
// are not allowed for the partitionKey anyway
return null;
}
@Override
boolean containsBindMarker() {
return Utils.containsBindMarker(valueLists);
}
@Override
void appendTo(StringBuilder sb, List<Object> variables, CodecRegistry codecRegistry) {
sb.append("(");
for (int i = 0; i < names.size(); i++) {
if (i > 0)
sb.append(",");
Utils.appendName(names.get(i), sb);
}
sb.append(")").append(" IN ").append("(");
for (int i = 0; i < valueLists.size(); i++) {
if (i > 0)
sb.append(",");
Object elt = valueLists.get(i);
if (elt instanceof BindMarker) {
sb.append(elt);
} else {
List<?> tuple = (List<?>) elt;
if (tuple.size() == 1 && tuple.get(0) instanceof BindMarker) {
// Special case when there is only one bind marker: "IN ?" instead of "IN (?)"
sb.append(tuple.get(0));
} else {
sb.append("(");
Utils.joinAndAppendValues(sb, codecRegistry, (List<?>) tuple, variables).append(')');
}
}
}
sb.append(")");
}
}
}