diff --git a/CHANGES.txt b/CHANGES.txt index fe8242b578..2cb2f5db75 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -29,6 +29,7 @@ * Handle overlapping MultiSlices (CASSANDRA-7279) * Fix DataOutputTest on Windows (CASSANDRA-7265) * Embedded sets in user defined data-types are not updating (CASSANDRA-7267) + * Add tuple type to CQL/native protocol (CASSANDRA-7248) Merged from 2.0: * Copy compaction options to make sure they are reloaded (CASSANDRA-7290) * Add option to do more aggressive tombstone compactions (CASSANDRA-6563) diff --git a/build.xml b/build.xml index 680efc11b6..574057712b 100644 --- a/build.xml +++ b/build.xml @@ -61,6 +61,7 @@ + @@ -92,6 +93,9 @@ + + + @@ -444,8 +448,6 @@ artifactId="cassandra-parent" version="${version}"/> - - @@ -1202,6 +1204,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/native_protocol_v3.spec b/doc/native_protocol_v3.spec index 671983864e..400868c4b6 100644 --- a/doc/native_protocol_v3.spec +++ b/doc/native_protocol_v3.spec @@ -38,7 +38,7 @@ Table of Contents 4.2.8. AUTH_SUCCESS 5. Compression 6. Collection types - 7. User Defined types + 7. User Defined and tuple types 8. Result paging 9. Error codes 10. Changes from v2 @@ -306,13 +306,13 @@ Table of Contents Section 4.2.5.2). 0x04: Page_size. In that case, is an [int] controlling the desired page size of the result (in CQL3 rows). - See the section on paging (Section 7) for more details. + See the section on paging (Section 8) for more details. 0x08: With_paging_state. If present, should be present. is a [bytes] value that should have been returned in a result set (Section 4.2.5.2). If provided, the query will be executed but starting from a given paging state. This also to continue paging on a different node from the one it has been - started (See Section 7 for more details). + started (See Section 8 for more details). 0x10: With serial consistency. If present, should be present. is the [consistency] level for the serial phase of conditional updates. That consitency can only be @@ -529,7 +529,7 @@ Table of Contents will be present. The is a [bytes] value that should be used in QUERY/EXECUTE to continue paging and retrieve the remained of the result for - this query (See Section 7 for more details). + this query (See Section 8 for more details). 0x0004 No_metadata: if set, the is only composed of these , the and optionally the (depending on the Has_more_pages flage) but @@ -592,6 +592,10 @@ Table of Contents i_th field of the UDT. - is an [option] representing the type of the i_th field of the UDT. + 0x0031 Tuple: the value is ... where is a [short] + representing the number of value in the type, and + are [option] representing the type of the i_th component + of the tuple - is an [int] representing the number of rows present in this result. Those rows are serialized in the part. @@ -756,16 +760,20 @@ Table of Contents value. -7. User defined types +7. User defined and tuple types - This section describes the serialization format for User defined types (UDT) values. - UDT values are the values of the User Defined Types as defined in section 4.2.5.2. + This section describes the serialization format for User defined types (UDT) and + tuple values. UDT (resp. tuple) values are the values of the User Defined Types + (resp. tuple type) as defined in section 4.2.5.2. A UDT value is composed of successive [bytes] values, one for each field of the UDT value (in the order defined by the type). A UDT value will generally have one value for each field of the type it represents, but it is allowed to have less values than the type has fields. + A tuple value has the exact same serialization format, i.e. a succession of + [bytes] values representing the components of the tuple. + 8. Result paging @@ -896,8 +904,8 @@ Table of Contents 10. Changes from v2 * BATCH messages now have (like QUERY and EXECUTE) and a corresponding optional parameters (see Section 4.1.7). - * User Defined Types have to added to ResultSet metadata (see 4.2.5.2) and a new section - on the serialization format of UDT values has been added to the documentation + * User Defined Types and tuple types have to added to ResultSet metadata (see 4.2.5.2) and a + new section on the serialization format of UDT and tuple values has been added to the documentation (Section 7). * The serialization format for collection has changed (both the collection size and the length of each argument is now 4 bytes long). See Section 6. diff --git a/src/java/org/apache/cassandra/config/UTMetaData.java b/src/java/org/apache/cassandra/config/UTMetaData.java index 178e6530e5..ee653a85d3 100644 --- a/src/java/org/apache/cassandra/config/UTMetaData.java +++ b/src/java/org/apache/cassandra/config/UTMetaData.java @@ -100,11 +100,11 @@ public final class UTMetaData adder.resetCollection("field_names"); adder.resetCollection("field_types"); - for (ByteBuffer name : newType.fieldNames) - adder.addListEntry("field_names", name); - for (AbstractType type : newType.fieldTypes) - adder.addListEntry("field_types", type.toString()); - + for (int i = 0; i < newType.size(); i++) + { + adder.addListEntry("field_names", newType.fieldName(i)); + adder.addListEntry("field_types", newType.fieldType(i).toString()); + } return mutation; } diff --git a/src/java/org/apache/cassandra/cql3/CQL3Type.java b/src/java/org/apache/cassandra/cql3/CQL3Type.java index f07bc195e7..a26a9032e8 100644 --- a/src/java/org/apache/cassandra/cql3/CQL3Type.java +++ b/src/java/org/apache/cassandra/cql3/CQL3Type.java @@ -17,6 +17,9 @@ */ package org.apache.cassandra.cql3; +import java.util.ArrayList; +import java.util.List; + import org.apache.cassandra.config.KSMetaData; import org.apache.cassandra.config.Schema; import org.apache.cassandra.db.marshal.*; @@ -221,6 +224,62 @@ public interface CQL3Type } } + public static class Tuple implements CQL3Type + { + private final TupleType type; + + private Tuple(TupleType type) + { + this.type = type; + } + + public static Tuple create(TupleType type) + { + return new Tuple(type); + } + + public boolean isCollection() + { + return false; + } + + public AbstractType getType() + { + return type; + } + + @Override + public final boolean equals(Object o) + { + if(!(o instanceof Tuple)) + return false; + + Tuple that = (Tuple)o; + return type.equals(that.type); + } + + @Override + public final int hashCode() + { + return type.hashCode(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("tuple<"); + for (int i = 0; i < type.size(); i++) + { + if (i > 0) + sb.append(", "); + sb.append(type.type(i).asCQL3Type()); + } + sb.append(">"); + return sb.toString(); + } + } + // For UserTypes, we need to know the current keyspace to resolve the // actual type used, so Raw is a "not yet prepared" CQL3Type. public abstract class Raw @@ -277,6 +336,15 @@ public interface CQL3Type return new RawCollection(CollectionType.Kind.SET, null, t); } + public static Raw tuple(List ts) throws InvalidRequestException + { + for (int i = 0; i < ts.size(); i++) + if (ts.get(i).isCounter()) + throw new InvalidRequestException("counters are not allowed inside tuples"); + + return new RawTuple(ts); + } + private static class RawType extends Raw { private CQL3Type type; @@ -386,5 +454,43 @@ public interface CQL3Type return name.toString(); } } + + private static class RawTuple extends Raw + { + private final List types; + + private RawTuple(List types) + { + this.types = types; + } + + public boolean isCollection() + { + return false; + } + + public CQL3Type prepare(String keyspace) throws InvalidRequestException + { + List> ts = new ArrayList<>(types.size()); + for (CQL3Type.Raw t : types) + ts.add(t.prepare(keyspace).getType()); + return new Tuple(new TupleType(ts)); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("tuple<"); + for (int i = 0; i < types.size(); i++) + { + if (i > 0) + sb.append(", "); + sb.append(types.get(i)); + } + sb.append(">"); + return sb.toString(); + } + } } } diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g index 57b61a53ec..f3681c4dcf 100644 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@ -846,13 +846,13 @@ constant returns [Constants.Literal constant] | { String sign=""; } ('-' {sign = "-"; } )? t=(K_NAN | K_INFINITY) { $constant = Constants.Literal.floatingPoint(sign + $t.text); } ; -map_literal returns [Maps.Literal map] +mapLiteral returns [Maps.Literal map] : '{' { List> m = new ArrayList>(); } ( k1=term ':' v1=term { m.add(Pair.create(k1, v1)); } ( ',' kn=term ':' vn=term { m.add(Pair.create(kn, vn)); } )* )? '}' { $map = new Maps.Literal(m); } ; -set_or_map[Term.Raw t] returns [Term.Raw value] +setOrMapLiteral[Term.Raw t] returns [Term.Raw value] : ':' v=term { List> m = new ArrayList>(); m.add(Pair.create(t, v)); } ( ',' kn=term ':' vn=term { m.add(Pair.create(kn, vn)); } )* { $value = new Maps.Literal(m); } @@ -861,27 +861,34 @@ set_or_map[Term.Raw t] returns [Term.Raw value] { $value = new Sets.Literal(s); } ; -collection_literal returns [Term.Raw value] +collectionLiteral returns [Term.Raw value] : '[' { List l = new ArrayList(); } ( t1=term { l.add(t1); } ( ',' tn=term { l.add(tn); } )* )? ']' { $value = new Lists.Literal(l); } - | '{' t=term v=set_or_map[t] { $value = v; } '}' + | '{' t=term v=setOrMapLiteral[t] { $value = v; } '}' // Note that we have an ambiguity between maps and set for "{}". So we force it to a set literal, // and deal with it later based on the type of the column (SetLiteral.java). | '{' '}' { $value = new Sets.Literal(Collections.emptyList()); } ; -usertype_literal returns [UserTypes.Literal ut] +usertypeLiteral returns [UserTypes.Literal ut] @init{ Map m = new HashMap(); } @after{ $ut = new UserTypes.Literal(m); } // We don't allow empty literals because that conflicts with sets/maps and is currently useless since we don't allow empty user types : '{' k1=cident ':' v1=term { m.put(k1, v1); } ( ',' kn=cident ':' vn=term { m.put(kn, vn); } )* '}' ; +tupleLiteral returns [Tuples.Literal tt] + @init{ List l = new ArrayList(); } + @after{ $tt = new Tuples.Literal(l); } + : '(' t1=term { l.add(t1); } ( ',' tn=term { l.add(tn); } )* ')' + ; + value returns [Term.Raw value] : c=constant { $value = c; } - | l=collection_literal { $value = l; } - | u=usertype_literal { $value = u; } + | l=collectionLiteral { $value = l; } + | u=usertypeLiteral { $value = u; } + | t=tupleLiteral { $value = t; } | K_NULL { $value = Constants.NULL_LITERAL; } | ':' id=cident { $value = newBindVariables(id); } | QMARK { $value = newBindVariables(null); } @@ -959,7 +966,7 @@ properties[PropertyDefinitions props] property[PropertyDefinitions props] : k=cident '=' (simple=propertyValue { try { $props.addProperty(k.toString(), simple); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } } - | map=map_literal { try { $props.addProperty(k.toString(), convertPropertyMap(map)); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } }) + | map=mapLiteral { try { $props.addProperty(k.toString(), convertPropertyMap(map)); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } }) ; propertyValue returns [String str] @@ -1026,11 +1033,6 @@ singleColumnInValues returns [List terms] : '(' ( t1 = term { $terms.add(t1); } (',' ti=term { $terms.add(ti); })* )? ')' ; -tupleLiteral returns [Tuples.Literal literal] - @init { List terms = new ArrayList<>(); } - : '(' t1=term { terms.add(t1); } (',' ti=term { terms.add(ti); })* ')' { $literal = new Tuples.Literal(terms); } - ; - tupleOfTupleLiterals returns [List literals] @init { $literals = new ArrayList<>(); } : '(' t1=tupleLiteral { $literals.add(t1); } (',' ti=tupleLiteral { $literals.add(ti); })* ')' @@ -1054,7 +1056,8 @@ inMarkerForTuple returns [Tuples.INRaw marker] comparatorType returns [CQL3Type.Raw t] : n=native_type { $t = CQL3Type.Raw.from(n); } | c=collection_type { $t = c; } - | id=userTypeName { $t = CQL3Type.Raw.userType(id); } + | tt=tuple_type { $t = tt; } + | id=userTypeName { $t = CQL3Type.Raw.userType(id); } | s=STRING_LITERAL { try { @@ -1099,6 +1102,12 @@ collection_type returns [CQL3Type.Raw pt] { try { if (t != null) $pt = CQL3Type.Raw.set(t); } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); } } ; +tuple_type returns [CQL3Type.Raw t] + : K_TUPLE '<' { List types = new ArrayList<>(); } + t1=comparatorType { types.add(t1); } (',' tn=comparatorType { types.add(tn); })* + '>' { try { $t = CQL3Type.Raw.tuple(types); } catch (InvalidRequestException e) { addRecognitionError(e.getMessage()); }} + ; + username : IDENT | STRING_LITERAL @@ -1110,11 +1119,12 @@ non_type_ident returns [ColumnIdentifier id] : t=IDENT { if (reservedTypeNames.contains($t.text)) addRecognitionError("Invalid (reserved) user type name " + $t.text); $id = new ColumnIdentifier($t.text, false); } | t=QUOTED_NAME { $id = new ColumnIdentifier($t.text, true); } | k=basic_unreserved_keyword { $id = new ColumnIdentifier(k, false); } + | kk=K_KEY { $id = new ColumnIdentifier($kk.text, false); } ; unreserved_keyword returns [String str] : u=unreserved_function_keyword { $str = u; } - | k=(K_TTL | K_COUNT | K_WRITETIME) { $str = $k.text; } + | k=(K_TTL | K_COUNT | K_WRITETIME | K_KEY) { $str = $k.text; } ; unreserved_function_keyword returns [String str] @@ -1123,8 +1133,7 @@ unreserved_function_keyword returns [String str] ; basic_unreserved_keyword returns [String str] - : k=( K_KEY - | K_KEYS + : k=( K_KEYS | K_AS | K_CLUSTERING | K_COMPACT @@ -1251,6 +1260,7 @@ K_MAP: M A P; K_LIST: L I S T; K_NAN: N A N; K_INFINITY: I N F I N I T Y; +K_TUPLE: T U P L E; K_TRIGGER: T R I G G E R; K_STATIC: S T A T I C; diff --git a/src/java/org/apache/cassandra/cql3/Tuples.java b/src/java/org/apache/cassandra/cql3/Tuples.java index 384633d837..e1ce551d2a 100644 --- a/src/java/org/apache/cassandra/cql3/Tuples.java +++ b/src/java/org/apache/cassandra/cql3/Tuples.java @@ -17,17 +17,15 @@ */ package org.apache.cassandra.cql3; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.CollectionType; -import org.apache.cassandra.db.marshal.ListType; -import org.apache.cassandra.db.marshal.TupleType; -import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.serializers.MarshalException; +import java.nio.ByteBuffer; +import java.util.*; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.ByteBuffer; -import java.util.*; +import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.serializers.MarshalException; /** * Static helper methods and classes for tuples. @@ -36,6 +34,16 @@ public class Tuples { private static final Logger logger = LoggerFactory.getLogger(Tuples.class); + private Tuples() {} + + public static ColumnSpecification componentSpecOf(ColumnSpecification column, int component) + { + return new ColumnSpecification(column.ksName, + column.cfName, + new ColumnIdentifier(String.format("%s[%d]", column.name, component), true), + ((TupleType)column.type).type(component)); + } + /** * A raw, literal tuple. When prepared, this will become a Tuples.Value or Tuples.DelayedValue, depending * on whether the tuple holds NonTerminals. @@ -49,6 +57,24 @@ public class Tuples this.elements = elements; } + public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException + { + validateAssignableTo(keyspace, receiver); + + List values = new ArrayList<>(elements.size()); + boolean allTerminal = true; + for (int i = 0; i < elements.size(); i++) + { + Term value = elements.get(i).prepare(keyspace, componentSpecOf(receiver, i)); + if (value instanceof Term.NonTerminal) + allTerminal = false; + + values.add(value); + } + DelayedValue value = new DelayedValue(values); + return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; + } + public Term prepare(String keyspace, List receivers) throws InvalidRequestException { if (elements.size() != receivers.size()) @@ -68,15 +94,36 @@ public class Tuples return allTerminal ? value.bind(QueryOptions.DEFAULT) : value; } - public Term prepare(String keyspace, ColumnSpecification receiver) + private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - throw new AssertionError("Tuples.Literal instances require a list of receivers for prepare()"); + if (!(receiver.type instanceof TupleType)) + throw new InvalidRequestException(String.format("Invalid tuple type literal for %s of type %s", receiver.name, receiver.type.asCQL3Type())); + + TupleType tt = (TupleType)receiver.type; + for (int i = 0; i < elements.size(); i++) + { + if (i >= tt.size()) + throw new InvalidRequestException(String.format("Invalid tuple literal for %s: too many elements. Type %s expects %d but got %d", + receiver.name, tt.asCQL3Type(), tt.size(), elements.size())); + + Term.Raw value = elements.get(i); + ColumnSpecification spec = componentSpecOf(receiver, i); + if (!value.isAssignableTo(keyspace, spec)) + throw new InvalidRequestException(String.format("Invalid tuple literal for %s: component %d is not of type %s", receiver.name, i, spec.type.asCQL3Type())); + } } public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) { - // tuples shouldn't be assignable to anything right now - return false; + try + { + validateAssignableTo(keyspace, receiver); + return true; + } + catch (InvalidRequestException e) + { + return false; + } } @Override @@ -105,7 +152,7 @@ public class Tuples public ByteBuffer get(QueryOptions options) { - throw new UnsupportedOperationException(); + return TupleType.buildValue(elements); } public List getElements() @@ -141,18 +188,28 @@ public class Tuples term.collectMarkerSpecification(boundNames); } + private ByteBuffer[] bindInternal(QueryOptions options) throws InvalidRequestException + { + // Inside tuples, we must force the serialization of collections whatever the protocol version is in + // use since we're going to store directly that serialized value. + options = options.withProtocolVersion(3); + + ByteBuffer[] buffers = new ByteBuffer[elements.size()]; + for (int i = 0; i < elements.size(); i++) + buffers[i] = elements.get(i).bindAndGet(options); + return buffers; + } + public Value bind(QueryOptions options) throws InvalidRequestException { - ByteBuffer[] buffers = new ByteBuffer[elements.size()]; - for (int i=0; i < elements.size(); i++) - { - ByteBuffer bytes = elements.get(i).bindAndGet(options); - if (bytes == null) - throw new InvalidRequestException("Tuples may not contain null values"); + return new Value(bindInternal(options)); + } - buffers[i] = elements.get(i).bindAndGet(options); - } - return new Value(buffers); + @Override + public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException + { + // We don't "need" that override but it saves us the allocation of a Value object if used + return TupleType.buildValue(bindInternal(options)); } @Override @@ -343,4 +400,4 @@ public class Tuples sb.append(')'); return sb.toString(); } -} \ No newline at end of file +} diff --git a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java index 7e0f15a808..42d0cb8258 100644 --- a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java +++ b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java @@ -55,6 +55,9 @@ public abstract class UntypedResultSet implements Iterable public abstract int size(); public abstract Row one(); + // No implemented by all subclasses, but we use it when we know it's there (for tests) + public abstract List metadata(); + private static class FromResultSet extends UntypedResultSet { private final ResultSet cqlRows; @@ -90,6 +93,11 @@ public abstract class UntypedResultSet implements Iterable } }; } + + public List metadata() + { + return cqlRows.metadata.names; + } } private static class FromResultList extends UntypedResultSet @@ -127,6 +135,11 @@ public abstract class UntypedResultSet implements Iterable } }; } + + public List metadata() + { + throw new UnsupportedOperationException(); + } } private static class FromPager extends UntypedResultSet @@ -176,6 +189,11 @@ public abstract class UntypedResultSet implements Iterable } }; } + + public List metadata() + { + return metadata; + } } public static class Row diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/UserTypes.java index ecffe31d44..c5469d29d6 100644 --- a/src/java/org/apache/cassandra/cql3/UserTypes.java +++ b/src/java/org/apache/cassandra/cql3/UserTypes.java @@ -36,7 +36,7 @@ public abstract class UserTypes return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier(column.name + "." + field, true), - ((UserType)column.type).fieldTypes.get(field)); + ((UserType)column.type).fieldType(field)); } public static class Literal implements Term.Raw @@ -55,9 +55,9 @@ public abstract class UserTypes UserType ut = (UserType)receiver.type; boolean allTerminal = true; List values = new ArrayList<>(entries.size()); - for (int i = 0; i < ut.fieldTypes.size(); i++) + for (int i = 0; i < ut.size(); i++) { - ColumnIdentifier field = new ColumnIdentifier(ut.fieldNames.get(i), UTF8Type.instance); + ColumnIdentifier field = new ColumnIdentifier(ut.fieldName(i), UTF8Type.instance); Term.Raw raw = entries.get(field); if (raw == null) raw = Constants.NULL_LITERAL; @@ -78,9 +78,9 @@ public abstract class UserTypes throw new InvalidRequestException(String.format("Invalid user type literal for %s of type %s", receiver, receiver.type.asCQL3Type())); UserType ut = (UserType)receiver.type; - for (int i = 0; i < ut.fieldTypes.size(); i++) + for (int i = 0; i < ut.size(); i++) { - ColumnIdentifier field = new ColumnIdentifier(ut.fieldNames.get(i), UTF8Type.instance); + ColumnIdentifier field = new ColumnIdentifier(ut.fieldName(i), UTF8Type.instance); Term.Raw value = entries.get(field); if (value == null) continue; @@ -144,7 +144,7 @@ public abstract class UserTypes public void collectMarkerSpecification(VariableSpecifications boundNames) { - for (int i = 0; i < type.fieldTypes.size(); i++) + for (int i = 0; i < type.size(); i++) values.get(i).collectMarkerSpecification(boundNames); } @@ -155,7 +155,7 @@ public abstract class UserTypes options = options.withProtocolVersion(3); ByteBuffer[] buffers = new ByteBuffer[values.size()]; - for (int i = 0; i < type.fieldTypes.size(); i++) + for (int i = 0; i < type.size(); i++) buffers[i] = values.get(i).bindAndGet(options); return buffers; } diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java index eac936f129..1996457918 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java @@ -138,8 +138,8 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement private static int getIdxOfField(UserType type, ColumnIdentifier field) { - for (int i = 0; i < type.fieldTypes.size(); i++) - if (field.bytes.equals(type.fieldNames.get(i))) + for (int i = 0; i < type.size(); i++) + if (field.bytes.equals(type.fieldName(i))) return i; return -1; } @@ -183,8 +183,8 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement return updated; // Otherwise, check for nesting - List> updatedTypes = updateTypes(ut.fieldTypes, keyspace, toReplace, updated); - return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.fieldNames), updatedTypes); + List> updatedTypes = updateTypes(ut.fieldTypes(), keyspace, toReplace, updated); + return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.fieldNames()), updatedTypes); } else if (type instanceof CompositeType) { @@ -275,12 +275,12 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement if (getIdxOfField(toUpdate, fieldName) >= 0) throw new InvalidRequestException(String.format("Cannot add new field %s to type %s: a field of the same name already exists", fieldName, name)); - List newNames = new ArrayList<>(toUpdate.fieldNames.size() + 1); - newNames.addAll(toUpdate.fieldNames); + List newNames = new ArrayList<>(toUpdate.size() + 1); + newNames.addAll(toUpdate.fieldNames()); newNames.add(fieldName.bytes); - List> newTypes = new ArrayList<>(toUpdate.fieldTypes.size() + 1); - newTypes.addAll(toUpdate.fieldTypes); + List> newTypes = new ArrayList<>(toUpdate.size() + 1); + newTypes.addAll(toUpdate.fieldTypes()); newTypes.add(type.prepare(keyspace()).getType()); return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes); @@ -292,12 +292,12 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement if (idx < 0) throw new InvalidRequestException(String.format("Unknown field %s in type %s", fieldName, name)); - AbstractType previous = toUpdate.fieldTypes.get(idx); + AbstractType previous = toUpdate.fieldType(idx); if (!type.prepare(keyspace()).getType().isCompatibleWith(previous)) throw new InvalidRequestException(String.format("Type %s is incompatible with previous type %s of field %s in user type %s", type, previous.asCQL3Type(), fieldName, name)); - List newNames = new ArrayList<>(toUpdate.fieldNames); - List> newTypes = new ArrayList<>(toUpdate.fieldTypes); + List newNames = new ArrayList<>(toUpdate.fieldNames()); + List> newTypes = new ArrayList<>(toUpdate.fieldTypes()); newTypes.set(idx, type.prepare(keyspace()).getType()); return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes); @@ -321,8 +321,8 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement protected UserType makeUpdatedType(UserType toUpdate) throws InvalidRequestException { - List newNames = new ArrayList<>(toUpdate.fieldNames); - List> newTypes = new ArrayList<>(toUpdate.fieldTypes); + List newNames = new ArrayList<>(toUpdate.fieldNames()); + List> newTypes = new ArrayList<>(toUpdate.fieldTypes()); for (Map.Entry entry : renames.entrySet()) { diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java index cd3e3e59bc..cc5447ecd5 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTypeStatement.java @@ -74,12 +74,12 @@ public class CreateTypeStatement extends SchemaAlteringStatement public static void checkForDuplicateNames(UserType type) throws InvalidRequestException { - for (int i = 0; i < type.fieldTypes.size() - 1; i++) + for (int i = 0; i < type.size() - 1; i++) { - ByteBuffer fieldName = type.fieldNames.get(i); - for (int j = i+1; j < type.fieldTypes.size(); j++) + ByteBuffer fieldName = type.fieldName(i); + for (int j = i+1; j < type.size(); j++) { - if (fieldName.equals(type.fieldNames.get(j))) + if (fieldName.equals(type.fieldName(j))) throw new InvalidRequestException(String.format("Duplicate field name %s in type %s", UTF8Type.instance.getString(fieldName), UTF8Type.instance.getString(type.name))); diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java index 10fc366201..1e1ded5546 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java @@ -97,7 +97,7 @@ public class DropTypeStatement extends SchemaAlteringStatement if (name.getKeyspace().equals(ut.keyspace) && name.getUserTypeName().equals(ut.name)) return true; - for (AbstractType subtype : ut.fieldTypes) + for (AbstractType subtype : ut.fieldTypes()) if (isUsedBy(subtype)) return true; } diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index 4a84b7bd5e..9d6338908e 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -778,7 +778,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache } private static List buildBound(Bound bound, - Collection defs, + List defs, Restriction[] restrictions, boolean isReversed, CType type, @@ -797,7 +797,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache else if (firstRestriction.isIN()) return buildMultiColumnInBound(bound, defs, (MultiColumnRestriction.IN) firstRestriction, isReversed, builder, type, options); else - return buildMultiColumnEQBound(bound, (MultiColumnRestriction.EQ) firstRestriction, isReversed, builder, options); + return buildMultiColumnEQBound(bound, defs, (MultiColumnRestriction.EQ) firstRestriction, isReversed, builder, options); } } @@ -886,7 +886,7 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache } private static List buildMultiColumnSliceBound(Bound bound, - Collection defs, + List defs, MultiColumnRestriction.Slice slice, boolean isReversed, CBuilder builder, @@ -911,22 +911,29 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache } List vals = slice.componentBounds(firstComponentBound, options); - builder.add(vals.get(firstName.position())); - while(iter.hasNext()) + ByteBuffer v = vals.get(firstName.position()); + if (v == null) + throw new InvalidRequestException("Invalid null value in condition for column " + firstName.name); + builder.add(v); + + while (iter.hasNext()) { ColumnDefinition def = iter.next(); if (def.position() >= vals.size()) break; - builder.add(vals.get(def.position())); + v = vals.get(def.position()); + if (v == null) + throw new InvalidRequestException("Invalid null value in condition for column " + def.name); + builder.add(v); } Relation.Type relType = slice.getRelation(eocBound, firstComponentBound); return Collections.singletonList(builder.build().withEOC(eocForRelation(relType))); } private static List buildMultiColumnInBound(Bound bound, - Collection defs, + List defs, MultiColumnRestriction.IN restriction, boolean isReversed, CBuilder builder, @@ -941,6 +948,10 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache Iterator iter = defs.iterator(); for (List components : splitInValues) { + for (int i = 0; i < components.size(); i++) + if (components.get(i) == null) + throw new InvalidRequestException("Invalid null value in condition for column " + defs.get(i)); + Composite prefix = builder.buildWith(components); Bound b = isReversed == isReversedType(iter.next()) ? bound : Bound.reverse(bound); inValues.add(b == Bound.END && builder.remainingCount() - components.size() > 0 @@ -951,14 +962,21 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache } private static List buildMultiColumnEQBound(Bound bound, + List defs, MultiColumnRestriction.EQ restriction, boolean isReversed, CBuilder builder, QueryOptions options) throws InvalidRequestException { Bound eocBound = isReversed ? Bound.reverse(bound) : bound; - for (ByteBuffer component : restriction.values(options)) + List values = restriction.values(options); + for (int i = 0; i < values.size(); i++) + { + ByteBuffer component = values.get(i); + if (component == null) + throw new InvalidRequestException("Invalid null value in condition for column " + defs.get(i)); builder.add(component); + } Composite prefix = builder.build(); return Collections.singletonList(builder.remainingCount() > 0 && eocBound == Bound.END diff --git a/src/java/org/apache/cassandra/cql3/statements/Selection.java b/src/java/org/apache/cassandra/cql3/statements/Selection.java index 4990e11d5e..f4e08852ce 100644 --- a/src/java/org/apache/cassandra/cql3/statements/Selection.java +++ b/src/java/org/apache/cassandra/cql3/statements/Selection.java @@ -142,13 +142,13 @@ public abstract class Selection throw new InvalidRequestException(String.format("Invalid field selection: %s of type %s is not a user type", withField.selected, type.asCQL3Type())); UserType ut = (UserType)type; - for (int i = 0; i < ut.fieldTypes.size(); i++) + for (int i = 0; i < ut.size(); i++) { - if (!ut.fieldNames.get(i).equals(withField.field.bytes)) + if (!ut.fieldName(i).equals(withField.field.bytes)) continue; if (metadata != null) - metadata.add(makeFieldSelectSpec(cfm, withField, ut.fieldTypes.get(i), raw.alias)); + metadata.add(makeFieldSelectSpec(cfm, withField, ut.fieldType(i), raw.alias)); return new FieldSelector(ut, i, selected); } throw new InvalidRequestException(String.format("%s of type %s has no field %s", withField.selected, type.asCQL3Type(), withField.field)); @@ -472,13 +472,13 @@ public abstract class Selection public AbstractType getType() { - return type.fieldTypes.get(field); + return type.fieldType(field); } @Override public String toString() { - return String.format("%s.%s", selected, UTF8Type.instance.getString(type.fieldNames.get(field))); + return String.format("%s.%s", selected, UTF8Type.instance.getString(type.fieldName(field))); } } diff --git a/src/java/org/apache/cassandra/db/marshal/TupleType.java b/src/java/org/apache/cassandra/db/marshal/TupleType.java index 74211c8c45..e754b514c7 100644 --- a/src/java/org/apache/cassandra/db/marshal/TupleType.java +++ b/src/java/org/apache/cassandra/db/marshal/TupleType.java @@ -120,9 +120,10 @@ public class TupleType extends AbstractType throw new MarshalException(String.format("Not enough bytes to read size of %dth component", i)); int size = input.getInt(); - // We don't handle null just yet, but we should fix that soon (CASSANDRA-7206) + + // size < 0 means null value if (size < 0) - throw new MarshalException("Nulls are not yet supported inside tuple values"); + continue; if (input.remaining() < size) throw new MarshalException(String.format("Not enough bytes to read %dth component", i)); @@ -158,13 +159,20 @@ public class TupleType extends AbstractType { int totalLength = 0; for (ByteBuffer component : components) - totalLength += 4 + component.remaining(); + totalLength += 4 + (component == null ? 0 : component.remaining()); ByteBuffer result = ByteBuffer.allocate(totalLength); for (ByteBuffer component : components) { - result.putInt(component.remaining()); - result.put(component.duplicate()); + if (component == null) + { + result.putInt(-1); + } + else + { + result.putInt(component.remaining()); + result.put(component.duplicate()); + } } result.rewind(); return result; @@ -183,12 +191,17 @@ public class TupleType extends AbstractType if (i > 0) sb.append(":"); + AbstractType type = type(i); int size = input.getInt(); - assert size >= 0; // We don't support nulls yet, but we will likely do with #7206 and we'll need - // a way to represent it as a string (without it conflicting with a user value) + if (size < 0) + { + sb.append("@"); + continue; + } + ByteBuffer field = ByteBufferUtil.readBytes(input, size); - // We use ':' as delimiter so escape it if it's in the generated string - sb.append(field == null ? "null" : type(i).getString(value).replaceAll(":", "\\\\:")); + // We use ':' as delimiter, and @ to represent null, so escape them in the generated string + sb.append(type.getString(field).replaceAll(":", "\\\\:").replaceAll("@", "\\\\@")); } return sb.toString(); } @@ -196,15 +209,19 @@ public class TupleType extends AbstractType public ByteBuffer fromString(String source) { // Split the input on non-escaped ':' characters - List strings = AbstractCompositeType.split(source); - ByteBuffer[] components = new ByteBuffer[strings.size()]; - for (int i = 0; i < strings.size(); i++) + List fieldStrings = AbstractCompositeType.split(source); + ByteBuffer[] fields = new ByteBuffer[fieldStrings.size()]; + for (int i = 0; i < fieldStrings.size(); i++) { - // TODO: we'll need to handle null somehow here once we support them - String str = strings.get(i).replaceAll("\\\\:", ":"); - components[i] = type(i).fromString(str); + String fieldString = fieldStrings.get(i); + // We use @ to represent nulls + if (fieldString.equals("@")) + continue; + + AbstractType type = type(i); + fields[i] = type.fromString(fieldString.replaceAll("\\\\:", ":").replaceAll("\\\\@", "@")); } - return buildValue(components); + return buildValue(fields); } public TypeSerializer getSerializer() @@ -270,10 +287,15 @@ public class TupleType extends AbstractType return types.equals(that.types); } + @Override + public CQL3Type asCQL3Type() + { + return CQL3Type.Tuple.create(this); + } + @Override public String toString() { return getClass().getName() + TypeParser.stringifyTypeParameters(types); } } - diff --git a/src/java/org/apache/cassandra/db/marshal/UserType.java b/src/java/org/apache/cassandra/db/marshal/UserType.java index 6656fd64f1..44c208f32e 100644 --- a/src/java/org/apache/cassandra/db/marshal/UserType.java +++ b/src/java/org/apache/cassandra/db/marshal/UserType.java @@ -33,21 +33,22 @@ import org.apache.cassandra.utils.Pair; /** * A user defined type. + * + * A user type is really just a tuple type on steroids. */ -public class UserType extends AbstractType +public class UserType extends TupleType { public final String keyspace; public final ByteBuffer name; - public final List fieldNames; - public final List> fieldTypes; + private final List fieldNames; public UserType(String keyspace, ByteBuffer name, List fieldNames, List> fieldTypes) { + super(fieldTypes); assert fieldNames.size() == fieldTypes.size(); this.keyspace = keyspace; this.name = name; this.fieldNames = fieldNames; - this.fieldTypes = fieldTypes; } public static UserType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException @@ -65,65 +66,44 @@ public class UserType extends AbstractType return new UserType(keyspace, name, columnNames, columnTypes); } + public AbstractType fieldType(int i) + { + return type(i); + } + + public List> fieldTypes() + { + return types; + } + + public ByteBuffer fieldName(int i) + { + return fieldNames.get(i); + } + + public List fieldNames() + { + return fieldNames; + } + public String getNameAsString() { return UTF8Type.instance.compose(name); } - public int compare(ByteBuffer o1, ByteBuffer o2) - { - if (!o1.hasRemaining() || !o2.hasRemaining()) - return o1.hasRemaining() ? 1 : o2.hasRemaining() ? -1 : 0; - - ByteBuffer bb1 = o1.duplicate(); - ByteBuffer bb2 = o2.duplicate(); - - int i = 0; - while (bb1.remaining() > 0 && bb2.remaining() > 0) - { - AbstractType comparator = fieldTypes.get(i); - - int size1 = bb1.getInt(); - int size2 = bb2.getInt(); - - // Handle nulls - if (size1 < 0) - { - if (size2 < 0) - continue; - return -1; - } - if (size2 < 0) - return 1; - - ByteBuffer value1 = ByteBufferUtil.readBytes(bb1, size1); - ByteBuffer value2 = ByteBufferUtil.readBytes(bb2, size2); - int cmp = comparator.compare(value1, value2); - if (cmp != 0) - return cmp; - - ++i; - } - - if (bb1.remaining() == 0) - return bb2.remaining() == 0 ? 0 : -1; - - // bb1.remaining() > 0 && bb2.remaining() == 0 - return 1; - } - + // Note: the only reason we override this is to provide nicer error message, but since that's not that much code... @Override public void validate(ByteBuffer bytes) throws MarshalException { ByteBuffer input = bytes.duplicate(); - for (int i = 0; i < fieldTypes.size(); i++) + for (int i = 0; i < size(); i++) { // we allow the input to have less fields than declared so as to support field addition. if (!input.hasRemaining()) return; if (input.remaining() < 4) - throw new MarshalException(String.format("Not enough bytes to read size of %dth field %s", i, fieldNames.get(i))); + throw new MarshalException(String.format("Not enough bytes to read size of %dth field %s", i, fieldName(i))); int size = input.getInt(); @@ -132,10 +112,10 @@ public class UserType extends AbstractType continue; if (input.remaining() < size) - throw new MarshalException(String.format("Not enough bytes to read %dth field %s", i, fieldNames.get(i))); + throw new MarshalException(String.format("Not enough bytes to read %dth field %s", i, fieldName(i))); ByteBuffer field = ByteBufferUtil.readBytes(input, size); - fieldTypes.get(i).validate(field); + types.get(i).validate(field); } // We're allowed to get less fields than declared, but not more @@ -143,112 +123,20 @@ public class UserType extends AbstractType throw new MarshalException("Invalid remaining data after end of UDT value"); } - /** - * Split a UDT value into its fields values. - */ - public ByteBuffer[] split(ByteBuffer value) + @Override + public int hashCode() { - ByteBuffer[] fields = new ByteBuffer[fieldTypes.size()]; - ByteBuffer input = value.duplicate(); - for (int i = 0; i < fieldTypes.size(); i++) - { - if (!input.hasRemaining()) - return Arrays.copyOfRange(fields, 0, i); - - int size = input.getInt(); - fields[i] = size < 0 ? null : ByteBufferUtil.readBytes(input, size); - } - return fields; - } - - public static ByteBuffer buildValue(ByteBuffer[] fields) - { - int totalLength = 0; - for (ByteBuffer field : fields) - totalLength += 4 + (field == null ? 0 : field.remaining()); - - ByteBuffer result = ByteBuffer.allocate(totalLength); - for (ByteBuffer field : fields) - { - if (field == null) - { - result.putInt(-1); - } - else - { - result.putInt(field.remaining()); - result.put(field.duplicate()); - } - } - result.rewind(); - return result; + return Objects.hashCode(keyspace, name, fieldNames, types); } @Override - public String getString(ByteBuffer value) - { - StringBuilder sb = new StringBuilder(); - ByteBuffer input = value.duplicate(); - for (int i = 0; i < fieldTypes.size(); i++) - { - if (!input.hasRemaining()) - return sb.toString(); - - if (i > 0) - sb.append(":"); - - AbstractType type = fieldTypes.get(i); - int size = input.getInt(); - if (size < 0) - { - sb.append("@"); - continue; - } - - ByteBuffer field = ByteBufferUtil.readBytes(input, size); - // We use ':' as delimiter, and @ to represent null, so escape them in the generated string - sb.append(type.getString(field).replaceAll(":", "\\\\:").replaceAll("@", "\\\\@")); - } - return sb.toString(); - } - - public ByteBuffer fromString(String source) - { - // Split the input on non-escaped ':' characters - List fieldStrings = AbstractCompositeType.split(source); - ByteBuffer[] fields = new ByteBuffer[fieldStrings.size()]; - for (int i = 0; i < fieldStrings.size(); i++) - { - String fieldString = fieldStrings.get(i); - // We use @ to represent nulls - if (fieldString.equals("@")) - continue; - - AbstractType type = fieldTypes.get(i); - fields[i] = type.fromString(fieldString.replaceAll("\\\\:", ":").replaceAll("\\\\@", "@")); - } - return buildValue(fields); - } - - public TypeSerializer getSerializer() - { - return BytesSerializer.instance; - } - - @Override - public final int hashCode() - { - return Objects.hashCode(keyspace, name, fieldNames, fieldTypes); - } - - @Override - public final boolean equals(Object o) + public boolean equals(Object o) { if(!(o instanceof UserType)) return false; UserType that = (UserType)o; - return keyspace.equals(that.keyspace) && name.equals(that.name) && fieldNames.equals(that.fieldNames) && fieldTypes.equals(that.fieldTypes); + return keyspace.equals(that.keyspace) && name.equals(that.name) && fieldNames.equals(that.fieldNames) && types.equals(that.types); } @Override @@ -260,6 +148,6 @@ public class UserType extends AbstractType @Override public String toString() { - return getClass().getName() + TypeParser.stringifyUserTypeParameters(keyspace, name, fieldNames, fieldTypes); + return getClass().getName() + TypeParser.stringifyUserTypeParameters(keyspace, name, fieldNames, types); } } diff --git a/src/java/org/apache/cassandra/transport/DataType.java b/src/java/org/apache/cassandra/transport/DataType.java index 24103783af..0710645e31 100644 --- a/src/java/org/apache/cassandra/transport/DataType.java +++ b/src/java/org/apache/cassandra/transport/DataType.java @@ -52,7 +52,8 @@ public enum DataType implements OptionCodec.Codecable LIST (32, null), MAP (33, null), SET (34, null), - UDT (48, null); + UDT (48, null), + TUPLE (49, null); public static final OptionCodec codec = new OptionCodec(DataType.class); @@ -107,6 +108,12 @@ public enum DataType implements OptionCodec.Codecable fieldTypes.add(DataType.toType(codec.decodeOne(cb, version))); } return new UserType(ks, name, fieldNames, fieldTypes); + case TUPLE: + n = cb.readUnsignedShort(); + List> types = new ArrayList<>(n); + for (int i = 0; i < n; i++) + types.add(DataType.toType(codec.decodeOne(cb, version))); + return new TupleType(types); default: return null; } @@ -135,13 +142,19 @@ public enum DataType implements OptionCodec.Codecable UserType udt = (UserType)value; CBUtil.writeString(udt.keyspace, cb); CBUtil.writeString(UTF8Type.instance.compose(udt.name), cb); - cb.writeShort(udt.fieldNames.size()); - for (int i = 0; i < udt.fieldNames.size(); i++) + cb.writeShort(udt.size()); + for (int i = 0; i < udt.size(); i++) { - CBUtil.writeString(UTF8Type.instance.compose(udt.fieldNames.get(i)), cb); - codec.writeOne(DataType.fromType(udt.fieldTypes.get(i), version), cb, version); + CBUtil.writeString(UTF8Type.instance.compose(udt.fieldName(i)), cb); + codec.writeOne(DataType.fromType(udt.fieldType(i), version), cb, version); } break; + case TUPLE: + TupleType tt = (TupleType)value; + cb.writeShort(tt.size()); + for (int i = 0; i < tt.size(); i++) + codec.writeOne(DataType.fromType(tt.type(i), version), cb, version); + break; } } @@ -166,12 +179,18 @@ public enum DataType implements OptionCodec.Codecable size += CBUtil.sizeOfString(udt.keyspace); size += CBUtil.sizeOfString(UTF8Type.instance.compose(udt.name)); size += 2; - for (int i = 0; i < udt.fieldNames.size(); i++) + for (int i = 0; i < udt.size(); i++) { - size += CBUtil.sizeOfString(UTF8Type.instance.compose(udt.fieldNames.get(i))); - size += codec.oneSerializedSize(DataType.fromType(udt.fieldTypes.get(i), version), version); + size += CBUtil.sizeOfString(UTF8Type.instance.compose(udt.fieldName(i))); + size += codec.oneSerializedSize(DataType.fromType(udt.fieldType(i), version), version); } return size; + case TUPLE: + TupleType tt = (TupleType)value; + size = 2; + for (int i = 0; i < tt.size(); i++) + size += codec.oneSerializedSize(DataType.fromType(tt.type(i), version), version); + return size; default: return 0; } @@ -211,6 +230,9 @@ public enum DataType implements OptionCodec.Codecable if (type instanceof UserType && version >= 3) return Pair.create(UDT, type); + if (type instanceof TupleType && version >= 3) + return Pair.create(TUPLE, type); + return Pair.create(CUSTOM, type.toString()); } else @@ -236,6 +258,8 @@ public enum DataType implements OptionCodec.Codecable return MapType.getInstance(l.get(0), l.get(1)); case UDT: return (AbstractType)entry.right; + case TUPLE: + return (AbstractType)entry.right; default: return entry.left.type; } diff --git a/test/conf/logback-test.xml b/test/conf/logback-test.xml index a7aa34c63f..535e4fe41b 100644 --- a/test/conf/logback-test.xml +++ b/test/conf/logback-test.xml @@ -30,7 +30,7 @@ 20MB - %-5level [%thread] %date{ISO8601} %caller{1} - %msg%n + %-5level [%thread] %date{ISO8601} %msg%n @@ -52,7 +52,7 @@ - + diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java index cbff31e54b..7f637a46d2 100644 --- a/test/unit/org/apache/cassandra/SchemaLoader.java +++ b/test/unit/org/apache/cassandra/SchemaLoader.java @@ -51,6 +51,16 @@ public class SchemaLoader @BeforeClass public static void loadSchema() throws ConfigurationException + { + prepareServer(); + + // if you're messing with low-level sstable stuff, it can be useful to inject the schema directly + // Schema.instance.load(schemaDefinition()); + for (KSMetaData ksm : schemaDefinition()) + MigrationManager.announceNewKeyspace(ksm); + } + + public static void prepareServer() { // Cleanup first cleanupAndLeaveDirs(); @@ -69,10 +79,6 @@ public class SchemaLoader // Migrations aren't happy if gossiper is not started. Even if we don't use migrations though, // some tests now expect us to start gossip for them. startGossiper(); - // if you're messing with low-level sstable stuff, it can be useful to inject the schema directly - // Schema.instance.load(schemaDefinition()); - for (KSMetaData ksm : schemaDefinition()) - MigrationManager.announceNewKeyspace(ksm); } public static void startGossiper() diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java new file mode 100644 index 0000000000..06119ed142 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -0,0 +1,472 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.cql3; + +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.base.Objects; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.utils.ByteBufferUtil; + +/** + * Base class for CQL tests. + */ +public abstract class CQLTester +{ + protected static final Logger logger = LoggerFactory.getLogger(CQLTester.class); + + private static final String KEYSPACE = "cql_test_keyspace"; + + private static final boolean USE_PREPARED_VALUES = Boolean.valueOf(System.getProperty("cassandra.test.use_prepared", "true")); + + private static final AtomicInteger seqNumber = new AtomicInteger(); + + private String currentTable; + + @BeforeClass + public static void setUpClass() throws Throwable + { + // This start gossiper for the sake of schema migrations. We might be able to get rid of that with some work. + SchemaLoader.prepareServer(); + + schemaChange(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", KEYSPACE)); + } + + @AfterClass + public static void tearDownClass() + { + SchemaLoader.stopGossiper(); + } + + protected void createTable(String query) + { + currentTable = "table_" + seqNumber.getAndIncrement(); + String fullQuery = String.format(query, KEYSPACE + "." + currentTable); + logger.info(fullQuery); + schemaChange(fullQuery); + } + + private static void schemaChange(String query) + { + try + { + // executeOnceInternal don't work for schema changes + QueryProcessor.process(query, ConsistencyLevel.ONE); + } + catch (Exception e) + { + throw new RuntimeException("Error setting schema for test (query was: " + query + ")", e); + } + } + + protected UntypedResultSet execute(String query, Object... values) throws Throwable + { + if (currentTable == null) + throw new RuntimeException("You must create a table first with createTable"); + + try + { + query = String.format(query, KEYSPACE + "." + currentTable); + + UntypedResultSet rs; + if (USE_PREPARED_VALUES) + { + logger.info("Executing: {} with values {}", query, formatAllValues(values)); + rs = QueryProcessor.executeOnceInternal(query, transformValues(values)); + } + else + { + query = replaceValues(query, values); + logger.info("Executing: {}", query); + rs = QueryProcessor.executeOnceInternal(query); + } + if (rs != null) + logger.info("Got {} rows", rs.size()); + return rs; + } + catch (RuntimeException e) + { + Throwable cause = e.getCause() != null ? e.getCause() : e; + logger.info("Got error: {}", cause.getMessage() == null ? cause.toString() : cause.getMessage()); + throw cause; + } + } + + protected void assertRows(UntypedResultSet result, Object[]... rows) + { + if (result == null) + { + if (rows.length > 0) + Assert.fail(String.format("No rows returned by query but %d expected", rows.length)); + return; + } + + List meta = result.metadata(); + Iterator iter = result.iterator(); + int i = 0; + while (iter.hasNext() && i < rows.length) + { + Object[] expected = rows[i++]; + UntypedResultSet.Row actual = iter.next(); + + Assert.assertEquals(String.format("Invalid number of (expected) values provided for row %d", i), meta.size(), expected.length); + + for (int j = 0; j < meta.size(); j++) + { + ColumnSpecification column = meta.get(j); + Object expectedValue = expected[j]; + ByteBuffer expectedByteValue = makeByteBuffer(expected[j], (AbstractType)column.type); + ByteBuffer actualValue = actual.getBytes(column.name.toString()); + + if (!Objects.equal(expectedByteValue, actualValue)) + Assert.fail(String.format("Invalid value for row %d column %d (%s), expected <%s> but got <%s>", + i, j, column.name, formatValue(expectedByteValue, column.type), formatValue(actualValue, column.type))); + } + } + + if (iter.hasNext()) + { + while (iter.hasNext()) + { + iter.next(); + i++; + } + Assert.fail(String.format("Got less rows than expected. Expected %d but got %d.", rows.length, i)); + } + + Assert.assertTrue(String.format("Got more rows than expected. Expected %d but got %d", rows.length, i), i == rows.length); + } + + protected void assertAllRows(Object[]... rows) throws Throwable + { + assertRows(execute("SELECT * FROM %s"), rows); + } + + protected Object[] row(Object... expected) + { + return expected; + } + + protected void assertEmpty(UntypedResultSet result) throws Throwable + { + if (result != null && result.size() != 0) + throw new InvalidRequestException(String.format("Expected empty result but got %d rows", result.size())); + } + + protected void assertInvalid(String query, Object... values) throws Throwable + { + try + { + execute(query, values); + Assert.fail("Query should be invalid but no error was thrown. Query is: " + query); + } + catch (SyntaxException | InvalidRequestException e) + { + // This is what we expect + } + } + + private static String replaceValues(String query, Object[] values) + { + StringBuilder sb = new StringBuilder(); + int last = 0; + int i = 0; + int idx; + while ((idx = query.indexOf('?', last)) > 0) + { + if (i >= values.length) + throw new IllegalArgumentException(String.format("Not enough values provided. The query has at least %d variables but only %d values provided", i, values.length)); + + sb.append(query.substring(last, idx)); + + Object value = values[i++]; + + // When we have a .. IN ? .., we use a list for the value because that's what's expected when the value is serialized. + // When we format as string however, we need to special case to use parenthesis. Hackish but convenient. + if (idx >= 3 && value instanceof List && query.substring(idx - 3, idx).equalsIgnoreCase("IN ")) + { + List l = (List)value; + sb.append("("); + for (int j = 0; j < l.size(); j++) + { + if (j > 0) + sb.append(", "); + sb.append(formatForCQL(l.get(j))); + } + sb.append(")"); + } + else + { + sb.append(formatForCQL(value)); + } + last = idx + 1; + } + sb.append(query.substring(last)); + return sb.toString(); + } + + // We're rellly only returning ByteBuffers but this make the type system happy + private static Object[] transformValues(Object[] values) + { + // We could partly rely on QueryProcessor.executeOnceInternal doing type conversion for us, but + // it would complain with ClassCastException if we pass say a string where an int is excepted (since + // it bases conversion on what the value should be, not what it is). For testing, we sometimes + // want to pass value of the wrong type and assert that this properly raise an InvalidRequestException + // and executeOnceInternal goes into way. So instead, we pre-convert everything to bytes here base + // on the value. + // Besides, we need to handle things like TupleValue that executeOnceInternal don't know about. + + Object[] buffers = new ByteBuffer[values.length]; + for (int i = 0; i < values.length; i++) + { + Object value = values[i]; + if (value == null) + { + buffers[i] = null; + continue; + } + + buffers[i] = typeFor(value).decompose(serializeTuples(value)); + } + return buffers; + } + + private static Object serializeTuples(Object value) + { + if (value instanceof TupleValue) + { + return ((TupleValue)value).toByteBuffer(); + } + + // We need to reach inside collections for TupleValue and transform them to ByteBuffer + // since otherwise the decompose method of the collection AbstractType won't know what + // to do with them + if (value instanceof List) + { + List l = (List)value; + List n = new ArrayList(l.size()); + for (Object o : l) + n.add(serializeTuples(o)); + return n; + } + + if (value instanceof Set) + { + Set s = (Set)value; + Set n = new LinkedHashSet(s.size()); + for (Object o : s) + n.add(serializeTuples(o)); + return n; + } + + if (value instanceof Map) + { + Map m = (Map)value; + Map n = new LinkedHashMap(m.size()); + for (Object entry : m.entrySet()) + n.put(serializeTuples(((Map.Entry)entry).getKey()), serializeTuples(((Map.Entry)entry).getValue())); + return n; + } + return value; + } + + private static String formatAllValues(Object[] values) + { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0; i < values.length; i++) + { + if (i > 0) + sb.append(", "); + sb.append(formatForCQL(values[i])); + } + sb.append("]"); + return sb.toString(); + } + + private static String formatForCQL(Object value) + { + if (value == null) + return "null"; + + if (value instanceof TupleValue) + return ((TupleValue)value).toCQLString(); + + // We need to reach inside collections for TupleValue. Besides, for some reason the format + // of collection that CollectionType.getString gives us is not at all 'CQL compatible' + if (value instanceof Collection) + { + StringBuilder sb = new StringBuilder(); + if (value instanceof List) + { + List l = (List)value; + sb.append("["); + for (int i = 0; i < l.size(); i++) + { + if (i > 0) + sb.append(", "); + sb.append(formatForCQL(l.get(i))); + } + sb.append("["); + } + else if (value instanceof Set) + { + Set s = (Set)value; + sb.append("{"); + Iterator iter = s.iterator(); + while (iter.hasNext()) + { + sb.append(formatForCQL(iter.next())); + if (iter.hasNext()) + sb.append(", "); + } + sb.append("}"); + } + else + { + Map m = (Map)value; + sb.append("{"); + Iterator iter = m.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry)iter.next(); + sb.append(formatForCQL(entry.getKey())).append(": ").append(formatForCQL(entry.getValue())); + if (iter.hasNext()) + sb.append(", "); + } + sb.append("}"); + } + return sb.toString(); + } + + AbstractType type = typeFor(value); + String s = type.getString(type.decompose(value)); + + if (type instanceof UTF8Type) + return String.format("'%s'", s.replaceAll("'", "''")); + + if (type instanceof BytesType) + return "0x" + s; + + return s; + } + + private static ByteBuffer makeByteBuffer(Object value, AbstractType type) + { + if (value == null) + return null; + + if (value instanceof TupleValue) + return ((TupleValue)value).toByteBuffer(); + + if (value instanceof ByteBuffer) + return (ByteBuffer)value; + + return type.decompose(value); + } + + private static String formatValue(ByteBuffer bb, AbstractType type) + { + return bb == null ? "null" : type.getString(bb); + } + + protected Object tuple(Object...values) + { + return new TupleValue(values); + } + + protected Object list(Object...values) + { + return Arrays.asList(values); + } + + // Attempt to find an AbstracType from a value (for serialization/printing sake). + // Will work as long as we use types we know of, which is good enough for testing + private static AbstractType typeFor(Object value) + { + if (value instanceof ByteBuffer || value instanceof TupleValue || value == null) + return BytesType.instance; + + if (value instanceof Integer) + return Int32Type.instance; + + if (value instanceof Long) + return LongType.instance; + + if (value instanceof Float) + return FloatType.instance; + + if (value instanceof Double) + return DoubleType.instance; + + if (value instanceof String) + return UTF8Type.instance; + + if (value instanceof List) + { + List l = (List)value; + AbstractType elt = l.isEmpty() ? BytesType.instance : typeFor(l.get(0)); + return ListType.getInstance(elt); + } + + throw new IllegalArgumentException("Unsupported value type (value is " + value + ")"); + } + + private static class TupleValue + { + private final Object[] values; + + TupleValue(Object[] values) + { + this.values = values; + } + + public ByteBuffer toByteBuffer() + { + ByteBuffer[] bbs = new ByteBuffer[values.length]; + for (int i = 0; i < values.length; i++) + bbs[i] = makeByteBuffer(values[i], typeFor(values[i])); + return TupleType.buildValue(bbs); + } + + public String toCQLString() + { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < values.length; i++) + { + if (i > 0) + sb.append(", "); + sb.append(formatForCQL(values[i])); + } + sb.append(")"); + return sb.toString(); + } + } +} diff --git a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java index 35646be993..e37759d27d 100644 --- a/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java +++ b/test/unit/org/apache/cassandra/cql3/MultiColumnRelationTest.java @@ -31,1084 +31,476 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; -import org.apache.cassandra.utils.MD5Digest; -import org.junit.AfterClass; -import org.junit.BeforeClass; import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.*; -import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal; -import static org.apache.cassandra.cql3.QueryProcessor.process; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; import static com.google.common.collect.Lists.newArrayList; -import static org.junit.Assert.fail; -public class MultiColumnRelationTest +public class MultiColumnRelationTest extends CQLTester { - private static final Logger logger = LoggerFactory.getLogger(MultiColumnRelationTest.class); - static ClientState clientState; - static String keyspace = "multi_column_relation_test"; - - @BeforeClass - public static void setUpClass() throws Throwable + @Test + public void testSingleClusteringInvalidQueries() throws Throwable { - SchemaLoader.loadSchema(); - executeSchemaChange("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}"); - executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.single_partition (a int PRIMARY KEY, b int)"); - executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.compound_partition (a int, b int, c int, PRIMARY KEY ((a, b)))"); - executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.single_clustering (a int, b int, c int, PRIMARY KEY (a, b))"); - executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.multiple_clustering (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d))"); - executeSchemaChange("CREATE TABLE IF NOT EXISTS %s.multiple_clustering_reversed (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d)) WITH CLUSTERING ORDER BY (b DESC, c ASC, d DESC)"); - clientState = ClientState.forInternalCalls(); - } + createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))"); - @AfterClass - public static void stopGossiper() - { - Gossiper.instance.stop(); - } - - private static void executeSchemaChange(String query) throws Throwable - { - try - { - process(String.format(query, keyspace), ConsistencyLevel.ONE); - } catch (RuntimeException exc) - { - throw exc.getCause(); - } - } - - private static UntypedResultSet execute(String query) throws Throwable - { - try - { - return executeOnceInternal(String.format(query, keyspace)); - } catch (RuntimeException exc) - { - if (exc.getCause() != null) - throw exc.getCause(); - throw exc; - } - } - - private MD5Digest prepare(String query) throws RequestValidationException - { - ResultMessage.Prepared prepared = QueryProcessor.prepare(String.format(query, keyspace), clientState, false); - return prepared.statementId; - } - - private UntypedResultSet executePrepared(MD5Digest statementId, QueryOptions options) throws RequestValidationException, RequestExecutionException - { - ParsedStatement.Prepared prepared = QueryProcessor.instance.getPrepared(statementId); - ResultMessage message = prepared.statement.executeInternal(QueryState.forInternalCalls(), options); - - if (message instanceof ResultMessage.Rows) - return UntypedResultSet.create(((ResultMessage.Rows)message).result); - else - return null; - } - - @Test(expected=SyntaxException.class) - public void testEmptyIdentifierTuple() throws Throwable - { - execute("SELECT * FROM %s.single_clustering WHERE () = (1, 2)"); - } - - @Test(expected=SyntaxException.class) - public void testEmptyValueTuple() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE (b, c) > ()"); - } - - @Test(expected=InvalidRequestException.class) - public void testDifferentTupleLengths() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE (b, c) > (1, 2, 3)"); - } - - @Test(expected=InvalidRequestException.class) - public void testNullInTuple() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE (b, c) > (1, null)"); + assertInvalid("SELECT * FROM %s WHERE () = (?, ?)", 1, 2); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b) = (?) AND (b) > (?)", 0, 0); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b) > (?) AND (b) > (?)", 0, 1); + assertInvalid("SELECT * FROM %s WHERE (a, b) = (?, ?)", 0, 0); } @Test - public void testEmptyIN() throws Throwable + public void testMultiClusteringInvalidQueries() throws Throwable { - UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ()"); - assertTrue(results.isEmpty()); - } + createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d))"); - @Test(expected=InvalidRequestException.class) - public void testNullInINValues() throws Throwable - { - UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((1, 2, null))"); - assertTrue(results.isEmpty()); - } + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b, c) > ()"); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b, c) > (?, ?, ?)", 1, 2, 3); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b, c) > (?, ?)", 1, null); - @Test(expected=InvalidRequestException.class) - public void testPartitionKeyInequality() throws Throwable - { - execute("SELECT * FROM %s.single_partition WHERE (a) > (1)"); - } + // Wrong order of columns + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (d, c, b) = (?, ?, ?)", 0, 0, 0); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (d, c, b) > (?, ?, ?)", 0, 0, 0); - @Test(expected=InvalidRequestException.class) - public void testPartitionKeyEquality() throws Throwable - { - execute("SELECT * FROM %s.single_partition WHERE (a) = (0)"); - } + // Wrong number of values + assertInvalid("SELECT * FROM %s WHERE a=0 AND (b, c, d) IN ((?, ?))", 0, 1); + assertInvalid("SELECT * FROM %s WHERE a=0 AND (b, c, d) IN ((?, ?, ?, ?, ?))", 0, 1, 2, 3, 4); - @Test(expected=InvalidRequestException.class) - public void testRestrictNonPrimaryKey() throws Throwable - { - execute("SELECT * FROM %s.single_partition WHERE (b) = (0)"); - } + // Missing first clustering column + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (c, d) = (?, ?)", 0, 0); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (c, d) > (?, ?)", 0, 0); - @Test(expected=InvalidRequestException.class) - public void testMixEqualityAndInequality() throws Throwable - { - execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (0) AND (b) > (0)"); - } + // Nulls + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b, c, d) IN ((?, ?, ?))", 1, 2, null); - @Test(expected=InvalidRequestException.class) - public void testMixMultipleInequalitiesOnSameBound() throws Throwable - { - execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (0) AND (b) > (1)"); - } + // Wrong type for 'd' + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b, c, d) = (?, ?, ?)", 1, 2, "foobar"); - @Test(expected=InvalidRequestException.class) - public void testClusteringColumnsOutOfOrderInInequality() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (d, c, b) > (0, 0, 0)"); - } + assertInvalid("SELECT * FROM %s WHERE a = 0 AND b = (?, ?, ?)", 1, 2, 3); - @Test(expected=InvalidRequestException.class) - public void testSkipClusteringColumnInEquality() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (c, d) = (0, 0)"); - } + // Mix single and tuple inequalities + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b, c, d) > (?, ?, ?) AND b < ?", 0, 1, 0, 1); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND (b, c, d) > (?, ?, ?) AND c < ?", 0, 1, 0, 1); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND b > ? AND (b, c, d) < (?, ?, ?)", 1, 1, 1, 0); + assertInvalid("SELECT * FROM %s WHERE a = 0 AND c > ? AND (b, c, d) < (?, ?, ?)", 1, 1, 1, 0); - @Test(expected=InvalidRequestException.class) - public void testSkipClusteringColumnInInequality() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (c, d) > (0, 0)"); + assertInvalid("SELECT * FROM %s WHERE (a, b, c, d) IN ((?, ?, ?, ?))", 0, 1, 2, 3); + assertInvalid("SELECT * FROM %s WHERE (c, d) IN ((?, ?))", 0, 1); } @Test - public void testSingleClusteringColumnEquality() throws Throwable + public void testSinglePartitionInvalidQueries() throws Throwable { - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 2, 0)"); - UntypedResultSet results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (1)"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0); + createTable("CREATE TABLE %s (a int PRIMARY KEY, b int)"); - results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (3)"); - assertEquals(0, results.size()); + assertInvalid("SELECT * FROM %s WHERE (a) > (?)", 0); + assertInvalid("SELECT * FROM %s WHERE (a) = (?)", 0); + assertInvalid("SELECT * FROM %s WHERE (b) = (?)", 0); } @Test - public void testMultipleClusteringColumnEquality() throws Throwable + public void testSingleClustering() throws Throwable { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 1)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 2, 0, 0)"); - UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) = (1)"); - assertEquals(3, results.size()); - checkRow(0, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(2, results, 0, 1, 1, 1); + createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))"); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) = (1, 1)"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 1, 0); - checkRow(1, results, 0, 1, 1, 1); + execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 0); + execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 0); + execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 2, 0); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) = (1, 1, 1)"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 1, 1); - execute("DELETE FROM %s.multiple_clustering WHERE a=0 AND b=2 and c=0 and d=0"); - } + // Equalities - @Test(expected=InvalidRequestException.class) - public void testPartitionAndClusteringColumnEquality() throws Throwable - { - execute("SELECT * FROM %s.single_clustering WHERE (a, b) = (0, 0)"); - } + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) = (?)", 0, 1), + row(0, 1, 0) + ); - @Test(expected=InvalidRequestException.class) - public void testClusteringColumnsOutOfOrderInEquality() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (d, c, b) = (3, 2, 1)"); - } + // Same but check the whole tuple can be prepared + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) = ?", 0, tuple(1)), + row(0, 1, 0) + ); - @Test(expected=InvalidRequestException.class) - public void testBadType() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) = (1, 2, 'foobar')"); - } + assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND (b) = (?)", 0, 3)); - @Test(expected=SyntaxException.class) - public void testSingleColumnTupleRelation() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND b = (1, 2, 3)"); + // Inequalities + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) > (?)", 0, 0), + row(0, 1, 0), + row(0, 2, 0) + ); + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) >= (?)", 0, 1), + row(0, 1, 0), + row(0, 2, 0) + ); + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) < (?)", 0, 2), + row(0, 0, 0), + row(0, 1, 0) + ); + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) <= (?)", 0, 1), + row(0, 0, 0), + row(0, 1, 0) + ); + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) > (?) AND (b) < (?)", 0, 0, 2), + row(0, 1, 0) + ); } @Test - public void testMixSingleAndTupleInequalities() throws Throwable + public void testMultipleClustering() throws Throwable { - String[] queries = new String[]{ - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 0) AND b < 1", - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 0) AND c < 1", - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND b > 1 AND (b, c, d) < (1, 1, 0)", - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND c > 1 AND (b, c, d) < (1, 1, 0)", - }; + createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d))"); - for (String query : queries) - { - try - { - execute(query); - fail(String.format("Expected query \"%s\" to throw an InvalidRequestException", query)); - } - catch (InvalidRequestException e) {} - } - } + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 0, 0); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 1, 0); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 1, 1); - @Test - public void testSingleClusteringColumnInequality() throws Throwable - { - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 2, 0)"); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 0, 0); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 0); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 1); - UntypedResultSet results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (0)"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 0); - checkRow(1, results, 0, 2, 0); + // Empty query + assertEmpty(execute("SELECT * FROM %s WHERE a = 0 AND (b, c, d) IN ()")); - results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) >= (1)"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 0); - checkRow(1, results, 0, 2, 0); + // Equalities - results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) < (2)"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 0); - checkRow(1, results, 0, 1, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) = (?)", 0, 1), + row(0, 1, 0, 0), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) <= (1)"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 0); - checkRow(1, results, 0, 1, 0); + // Same with whole tuple prepared + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) = ?", 0, tuple(1)), + row(0, 1, 0, 0), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (0) AND (b) < (2)"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0); - } + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) = (?, ?)", 0, 1, 1), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - @Test - public void testMultipleClusteringColumnInequality() throws Throwable - { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); + // Same with whole tuple prepared + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) = ?", 0, tuple(1, 1)), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 1)"); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) = (?, ?, ?)", 0, 1, 1, 1), + row(0, 1, 1, 1) + ); - UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > (0)"); - assertEquals(3, results.size()); - checkRow(0, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(2, results, 0, 1, 1, 1); + // Same with whole tuple prepared + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) = ?", 0, tuple(1, 1, 1)), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) >= (0)"); - assertEquals(6, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); - checkRow(3, results, 0, 1, 0, 0); - checkRow(4, results, 0, 1, 1, 0); - checkRow(5, results, 0, 1, 1, 1); + // Inequalities - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) > (1, 0)"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 1, 0); - checkRow(1, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) > (?)", 0, 0), + row(0, 1, 0, 0), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) >= (1, 0)"); - assertEquals(3, results.size()); - checkRow(0, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(2, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) >= (?)", 0, 0), + row(0, 0, 0, 0), + row(0, 0, 1, 0), + row(0, 0, 1, 1), + row(0, 1, 0, 0), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (1, 1, 0)"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) > (?, ?)", 0, 1, 0), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) >= (1, 1, 0)"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 1, 0); - checkRow(1, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) >= (?, ?)", 0, 1, 0), + row(0, 1, 0, 0), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) < (1)"); - assertEquals(3, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?)", 0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) <= (1)"); - assertEquals(6, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); - checkRow(3, results, 0, 1, 0, 0); - checkRow(4, results, 0, 1, 1, 0); - checkRow(5, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) >= (?, ?, ?)", 0, 1, 1, 0), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) < (0, 1)"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 0, 0, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) < (?)", 0, 1), + row(0, 0, 0, 0), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) <= (0, 1)"); - assertEquals(3, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) <= (?)", 0, 1), + row(0, 0, 0, 0), + row(0, 0, 1, 0), + row(0, 0, 1, 1), + row(0, 1, 0, 0), + row(0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) < (0, 1, 1)"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) < (?, ?)", 0, 0, 1), + row(0, 0, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) <= (0, 1, 1)"); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) <= (?, ?)", 0, 0, 1), + row(0, 0, 0, 0), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 0) AND (b) < (1)"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) < (?, ?, ?)", 0, 0, 1, 1), + row(0, 0, 0, 0), + row(0, 0, 1, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 1) AND (b, c) < (1, 1)"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) <= (?, ?, ?)", 0, 0, 1, 1), + row(0, 0, 0, 0), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 1) AND (b, c, d) < (1, 1, 0)"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?) AND (b) < (?)", 0, 0, 1, 0, 1), + row(0, 0, 1, 1) + ); + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?) AND (b, c) < (?, ?)", 0, 0, 1, 1, 1, 1), + row(0, 1, 0, 0) + ); + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?) AND (b, c, d) < (?, ?, ?)", 0, 0, 1, 1, 1, 1, 0), + row(0, 1, 0, 0) + ); + + // Same with whole tuple prepared + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > ? AND (b, c, d) < ?", 0, tuple(0, 1, 1), tuple(1, 1, 0)), + row(0, 1, 0, 0) + ); // reversed - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > (0) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(3, results.size()); - checkRow(2, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(0, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) > (?) ORDER BY b DESC, c DESC, d DESC", 0, 0), + row(0, 1, 1, 1), + row(0, 1, 1, 0), + row(0, 1, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) >= (0) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(6, results.size()); - checkRow(5, results, 0, 0, 0, 0); - checkRow(4, results, 0, 0, 1, 0); - checkRow(3, results, 0, 0, 1, 1); - checkRow(2, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(0, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) >= (?) ORDER BY b DESC, c DESC, d DESC", 0, 0), + row(0, 1, 1, 1), + row(0, 1, 1, 0), + row(0, 1, 0, 0), + row(0, 0, 1, 1), + row(0, 0, 1, 0), + row(0, 0, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) > (1, 0) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(2, results.size()); - checkRow(1, results, 0, 1, 1, 0); - checkRow(0, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) > (?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 1, 0), + row(0, 1, 1, 1), + row(0, 1, 1, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) >= (1, 0) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(3, results.size()); - checkRow(2, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(0, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) >= (?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 1, 0), + row(0, 1, 1, 1), + row(0, 1, 1, 0), + row(0, 1, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (1, 1, 0) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 1, 1, 0), + row(0, 1, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) >= (1, 1, 0) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(2, results.size()); - checkRow(1, results, 0, 1, 1, 0); - checkRow(0, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) >= (?, ?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 1, 1, 0), + row(0, 1, 1, 1), + row(0, 1, 1, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) < (1) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(3, results.size()); - checkRow(2, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(0, results, 0, 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) < (?) ORDER BY b DESC, c DESC, d DESC", 0, 1), + row(0, 0, 1, 1), + row(0, 0, 1, 0), + row(0, 0, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) <= (1) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(6, results.size()); - checkRow(5, results, 0, 0, 0, 0); - checkRow(4, results, 0, 0, 1, 0); - checkRow(3, results, 0, 0, 1, 1); - checkRow(2, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(0, results, 0, 1, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) <= (?) ORDER BY b DESC, c DESC, d DESC", 0, 1), + row(0, 1, 1, 1), + row(0, 1, 1, 0), + row(0, 1, 0, 0), + row(0, 0, 1, 1), + row(0, 0, 1, 0), + row(0, 0, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) < (0, 1) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 0, 0, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) < (?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1), + row(0, 0, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) <= (0, 1) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(3, results.size()); - checkRow(2, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(0, results, 0, 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c) <= (?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1), + row(0, 0, 1, 1), + row(0, 0, 1, 0), + row(0, 0, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) < (0, 1, 1) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(2, results.size()); - checkRow(1, results, 0, 0, 0, 0); - checkRow(0, results, 0, 0, 1, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) < (?, ?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 1), + row(0, 0, 1, 0), + row(0, 0, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) <= (0, 1, 1) ORDER BY b DESC, c DESC, d DESC"); - checkRow(2, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(0, results, 0, 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) <= (?, ?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 1), + row(0, 0, 1, 1), + row(0, 0, 1, 0), + row(0, 0, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 0) AND (b) < (1) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 0, 1, 1); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?) AND (b) < (?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 0, 1), + row(0, 0, 1, 1) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 1) AND (b, c) < (1, 1) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?) AND (b, c) < (?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 1, 1, 1), + row(0, 1, 0, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (0, 1, 1) AND (b, c, d) < (1, 1, 0) ORDER BY b DESC, c DESC, d DESC"); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) > (?, ?, ?) AND (b, c, d) < (?, ?, ?) ORDER BY b DESC, c DESC, d DESC", 0, 0, 1, 1, 1, 1, 0), + row(0, 1, 0, 0) + ); + + // IN + + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) IN ((?, ?, ?), (?, ?, ?))", 0, 0, 1, 0, 0, 1, 1), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); + + // same query but with whole tuple prepared + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) IN (?, ?)", 0, tuple(0, 1, 0), tuple(0, 1, 1)), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); + + // same query but with whole IN list prepared + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) IN ?", 0, list(tuple(0, 1, 0), tuple(0, 1, 1))), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); + + // same query, but reversed order for the IN values + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b, c, d) IN (?, ?)", 0, tuple(0, 1, 1), tuple(0, 1, 0)), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); + + assertRows(execute("SELECT * FROM %s WHERE a = ? and (b, c) IN ((?, ?))", 0, 0, 1), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); + + assertRows(execute("SELECT * FROM %s WHERE a = ? and (b) IN ((?))", 0, 0), + row(0, 0, 0, 0), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); + + // IN on both partition key and clustering key + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 0, 0, 0); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 0, 1, 0); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 1, 0, 1, 1); + + assertRows(execute("SELECT * FROM %s WHERE a IN (?, ?) AND (b, c, d) IN (?, ?)", 0, 1, tuple(0, 1, 0), tuple(0, 1, 1)), + row(0, 0, 1, 0), + row(0, 0, 1, 1), + row(1, 0, 1, 0), + row(1, 0, 1, 1) + ); + + // same but with whole IN lists prepared + assertRows(execute("SELECT * FROM %s WHERE a IN ? AND (b, c, d) IN ?", list(0, 1), list(tuple(0, 1, 0), tuple(0, 1, 1))), + row(0, 0, 1, 0), + row(0, 0, 1, 1), + row(1, 0, 1, 0), + row(1, 0, 1, 1) + ); + + // same query, but reversed order for the IN values + assertRows(execute("SELECT * FROM %s WHERE a IN (?, ?) AND (b, c, d) IN (?, ?)", 1, 0, tuple(0, 1, 1), tuple(0, 1, 0)), + row(1, 0, 1, 0), + row(1, 0, 1, 1), + row(0, 0, 1, 0), + row(0, 0, 1, 1) + ); + + assertRows(execute("SELECT * FROM %s WHERE a IN (?, ?) and (b, c) IN ((?, ?))", 0, 1, 0, 1), + row(0, 0, 1, 0), + row(0, 0, 1, 1), + row(1, 0, 1, 0), + row(1, 0, 1, 1) + ); + + assertRows(execute("SELECT * FROM %s WHERE a IN (?, ?) and (b) IN ((?))", 0, 1, 0), + row(0, 0, 0, 0), + row(0, 0, 1, 0), + row(0, 0, 1, 1), + row(1, 0, 0, 0), + row(1, 0, 1, 0), + row(1, 0, 1, 1) + ); } @Test - public void testMultipleClusteringColumnInequalityReversedComponents() throws Throwable + public void testMultipleClusteringReversedComponents() throws Throwable { + createTable("CREATE TABLE %s (a int, b int, c int, d int, PRIMARY KEY (a, b, c, d)) WITH CLUSTERING ORDER BY (b DESC, c ASC, d DESC)"); + // b and d are reversed in the clustering order - execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 1, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 1, 1, 1)"); - execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 1, 1, 0)"); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 0, 0); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 1); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 1, 1, 0); - execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 0, 1, 1)"); - execute("INSERT INTO %s.multiple_clustering_reversed (a, b, c, d) VALUES (0, 0, 1, 0)"); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 0, 0); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 1, 1); + execute("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?)", 0, 0, 1, 0); - UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b) > (0)"); - assertEquals(3, results.size()); - checkRow(0, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 1); - checkRow(2, results, 0, 1, 1, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) > (?)", 0, 0), + row(0, 1, 0, 0), + row(0, 1, 1, 1), + row(0, 1, 1, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b) >= (0)"); - assertEquals(6, results.size()); - checkRow(0, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 1); - checkRow(2, results, 0, 1, 1, 0); - checkRow(3, results, 0, 0, 0, 0); - checkRow(4, results, 0, 0, 1, 1); - checkRow(5, results, 0, 0, 1, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) >= (?)", 0, 0), + row(0, 1, 0, 0), + row(0, 1, 1, 1), + row(0, 1, 1, 0), + row(0, 0, 0, 0), + row(0, 0, 1, 1), + row(0, 0, 1, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b) < (1)"); - assertEquals(3, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 1); - checkRow(2, results, 0, 0, 1, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) < (?)", 0, 1), + row(0, 0, 0, 0), + row(0, 0, 1, 1), + row(0, 0, 1, 0) + ); - results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b) <= (1)"); - assertEquals(6, results.size()); - checkRow(0, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 1); - checkRow(2, results, 0, 1, 1, 0); - checkRow(3, results, 0, 0, 0, 0); - checkRow(4, results, 0, 0, 1, 1); - checkRow(5, results, 0, 0, 1, 0); + assertRows(execute("SELECT * FROM %s WHERE a = ? AND (b) <= (?)", 0, 1), + row(0, 1, 0, 0), + row(0, 1, 1, 1), + row(0, 1, 1, 0), + row(0, 0, 0, 0), + row(0, 0, 1, 1), + row(0, 0, 1, 0) + ); // preserve pre-6875 behavior (even though the query result is technically incorrect) - results = execute("SELECT * FROM %s.multiple_clustering_reversed WHERE a=0 AND (b, c) > (1, 0)"); - assertEquals(0, results.size()); + assertEmpty(execute("SELECT * FROM %s WHERE a = ? AND (b, c) > (?, ?)", 0, 1, 0)); } - - @Test - public void testLiteralIn() throws Throwable - { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); - - UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((0, 1, 0), (0, 1, 1))"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - // same query, but reversed order for the IN values - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((0, 1, 1), (0, 1, 0))"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b, c) IN ((0, 1))"); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - results = execute("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN ((0))"); - assertEquals(3, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); - } - - @Test(expected=InvalidRequestException.class) - public void testLiteralInWithShortTuple() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((0, 1))"); - } - - @Test(expected=InvalidRequestException.class) - public void testLiteralInWithLongTuple() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((0, 1, 2, 3, 4))"); - } - - @Test(expected=InvalidRequestException.class) - public void testLiteralInWithPartitionKey() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE (a, b, c, d) IN ((0, 1, 2, 3))"); - } - - @Test(expected=InvalidRequestException.class) - public void testLiteralInSkipsClusteringColumn() throws Throwable - { - execute("SELECT * FROM %s.multiple_clustering WHERE (c, d) IN ((0, 1))"); - } - @Test - public void testPartitionAndClusteringInClauses() throws Throwable - { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); - - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (1, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (1, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (1, 0, 1, 1)"); - - UntypedResultSet results = execute("SELECT * FROM %s.multiple_clustering WHERE a IN (0, 1) AND (b, c, d) IN ((0, 1, 0), (0, 1, 1))"); - assertEquals(4, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - checkRow(2, results, 1, 0, 1, 0); - checkRow(3, results, 1, 0, 1, 1); - - // same query, but reversed order for the IN values - results = execute("SELECT * FROM %s.multiple_clustering WHERE a IN (1, 0) AND (b, c, d) IN ((0, 1, 1), (0, 1, 0))"); - assertEquals(4, results.size()); - checkRow(0, results, 1, 0, 1, 0); - checkRow(1, results, 1, 0, 1, 1); - checkRow(2, results, 0, 0, 1, 0); - checkRow(3, results, 0, 0, 1, 1); - - results = execute("SELECT * FROM %s.multiple_clustering WHERE a IN (0, 1) and (b, c) IN ((0, 1))"); - assertEquals(4, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - checkRow(2, results, 1, 0, 1, 0); - checkRow(3, results, 1, 0, 1, 1); - - results = execute("SELECT * FROM %s.multiple_clustering WHERE a IN (0, 1) and (b) IN ((0))"); - assertEquals(6, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); - checkRow(3, results, 1, 0, 0, 0); - checkRow(4, results, 1, 0, 1, 0); - checkRow(5, results, 1, 0, 1, 1); - } - - // prepare statement tests - - @Test(expected=InvalidRequestException.class) - public void testPreparePartitionAndClusteringColumnEquality() throws Throwable - { - prepare("SELECT * FROM %s.single_clustering WHERE (a, b) = (?, ?)"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareDifferentTupleLengths() throws Throwable - { - prepare("SELECT * FROM %s.multiple_clustering WHERE (b, c) > (?, ?, ?)"); - } - - @Test - public void testPrepareEmptyIN() throws Throwable - { - MD5Digest id = prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ()"); - UntypedResultSet results = executePrepared(id, makeIntOptions()); - assertTrue(results.isEmpty()); - } - - @Test(expected=InvalidRequestException.class) - public void testPreparePartitionKeyInequality() throws Throwable - { - prepare("SELECT * FROM %s.single_partition WHERE (a) > (?)"); - } - - @Test(expected=InvalidRequestException.class) - public void testPreparePartitionKeyEquality() throws Throwable - { - prepare("SELECT * FROM %s.single_partition WHERE (a) = (?)"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareRestrictNonPrimaryKey() throws Throwable - { - prepare("SELECT * FROM %s.single_partition WHERE (b) = (?)"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareMixEqualityAndInequality() throws Throwable - { - prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (?) AND (b) > (?)"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareMixMultipleInequalitiesOnSameBound() throws Throwable - { - prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (?) AND (b) > (?)"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareClusteringColumnsOutOfOrderInInequality() throws Throwable - { - prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (d, c, b) > (?, ?, ?)"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareSkipClusteringColumnInEquality() throws Throwable - { - prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (c, d) = (?, ?)"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareSkipClusteringColumnInInequality() throws Throwable - { - prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (c, d) > (?, ?)"); - } - - @Test - public void testPreparedClusteringColumnEquality() throws Throwable - { - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)"); - MD5Digest id = prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = (?)"); - UntypedResultSet results = executePrepared(id, makeIntOptions(0)); - assertEquals(1, results.size()); - checkRow(0, results, 0, 0, 0); - } - - @Test - public void testPreparedClusteringColumnEqualitySingleMarker() throws Throwable - { - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)"); - MD5Digest id = prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) = ?"); - UntypedResultSet results = executePrepared(id, options(tuple(0))); - assertEquals(1, results.size()); - checkRow(0, results, 0, 0, 0); - } - - @Test - public void testPreparedSingleClusteringColumnInequality() throws Throwable - { - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 2, 0)"); - - MD5Digest id = prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (?)"); - UntypedResultSet results = executePrepared(id, makeIntOptions(0)); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 0); - checkRow(1, results, 0, 2, 0); - - results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) >= (?)"), makeIntOptions(1)); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 0); - checkRow(1, results, 0, 2, 0); - - results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) < (?)"), makeIntOptions(2)); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 0); - checkRow(1, results, 0, 1, 0); - - results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) <= (?)"), makeIntOptions(1)); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 0); - checkRow(1, results, 0, 1, 0); - - results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > (?) AND (b) < (?)"), makeIntOptions(0, 2)); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0); - } - - @Test - public void testPreparedSingleClusteringColumnInequalitySingleMarker() throws Throwable - { - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 0, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 1, 0)"); - execute("INSERT INTO %s.single_clustering (a, b, c) VALUES (0, 2, 0)"); - - MD5Digest id = prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > ?"); - UntypedResultSet results = executePrepared(id, options(tuple(0))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 0); - checkRow(1, results, 0, 2, 0); - - results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) >= ?"), options(tuple(1))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 0); - checkRow(1, results, 0, 2, 0); - - results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) < ?"), options(tuple(2))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 0); - checkRow(1, results, 0, 1, 0); - - results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) <= ?"), options(tuple(1))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 0); - checkRow(1, results, 0, 1, 0); - - - results = executePrepared(prepare("SELECT * FROM %s.single_clustering WHERE a=0 AND (b) > ? AND (b) < ?"), - options(tuple(0), tuple(2))); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0); - } - - @Test - public void testPrepareMultipleClusteringColumnInequality() throws Throwable - { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); - - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 1)"); - - UntypedResultSet results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > (?)"), makeIntOptions(0)); - assertEquals(3, results.size()); - checkRow(0, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(2, results, 0, 1, 1, 1); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) > (?, ?)"), makeIntOptions(1, 0)); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 1, 0); - checkRow(1, results, 0, 1, 1, 1); - - results = executePrepared(prepare - ("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?)"), makeIntOptions(1, 1, 0)); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 1, 1); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?) AND (b) < (?)"), - makeIntOptions(0, 1, 0, 1)); - assertEquals(1, results.size()); - checkRow(0, results, 0, 0, 1, 1); - - results = executePrepared(prepare - ("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?) AND (b, c) < (?, ?)"), - makeIntOptions(0, 1, 1, 1, 1)); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?) AND (b, c, d) < (?, ?, ?)"), - makeIntOptions(0, 1, 1, 1, 1, 0)); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); - - // reversed - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > (?) ORDER BY b DESC, c DESC, d DESC"), - makeIntOptions(0)); - assertEquals(3, results.size()); - checkRow(2, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(0, results, 0, 1, 1, 1); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > (?, ?, ?) AND (b, c) < (?, ?) ORDER BY b DESC, c DESC, d DESC"), - makeIntOptions(0, 1, 1, 1, 1)); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); - } - - @Test - public void testPrepareMultipleClusteringColumnInequalitySingleMarker() throws Throwable - { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); - - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 1, 1, 1)"); - - UntypedResultSet results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > ?"), options(tuple(0))); - assertEquals(3, results.size()); - checkRow(0, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(2, results, 0, 1, 1, 1); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c) > ?"), options(tuple(1, 0))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 1, 1, 0); - checkRow(1, results, 0, 1, 1, 1); - - results = executePrepared(prepare - ("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ?"), options(tuple(1, 1, 0))); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 1, 1); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ? AND (b) < ?"), - options(tuple(0, 1, 0), tuple(1))); - assertEquals(1, results.size()); - checkRow(0, results, 0, 0, 1, 1); - - results = executePrepared(prepare - ("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ? AND (b, c) < ?"), - options(tuple(0, 1, 1), tuple(1, 1))); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ? AND (b, c, d) < ?"), - options(tuple(0, 1, 1), tuple(1, 1, 0))); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); - - // reversed - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b) > ? ORDER BY b DESC, c DESC, d DESC"), - options(tuple(0))); - assertEquals(3, results.size()); - checkRow(2, results, 0, 1, 0, 0); - checkRow(1, results, 0, 1, 1, 0); - checkRow(0, results, 0, 1, 1, 1); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) > ? AND (b, c) < ? ORDER BY b DESC, c DESC, d DESC"), - options(tuple(0, 1, 1), tuple(1, 1))); - assertEquals(1, results.size()); - checkRow(0, results, 0, 1, 0, 0); - } - - @Test - public void testPrepareLiteralIn() throws Throwable - { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); - - UntypedResultSet results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?, ?), (?, ?, ?))"), - makeIntOptions(0, 1, 0, 0, 1, 1)); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - // same query, but reversed order for the IN values - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?, ?), (?, ?, ?))"), - makeIntOptions(0, 1, 1, 0, 1, 0)); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b, c) IN ((?, ?))"), - makeIntOptions(0, 1)); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN ((?))"), - makeIntOptions(0)); - assertEquals(3, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); - } - - @Test - public void testPrepareInOneMarkerPerTuple() throws Throwable - { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); - - UntypedResultSet results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN (?, ?)"), - options(tuple(0, 1, 0), tuple(0, 1, 1))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - // same query, but reversed order for the IN values - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN (?, ?)"), - options(tuple(0, 1, 1), tuple(0, 1, 0))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - - results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b, c) IN (?)"), - options(tuple(0, 1))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN (?)"), - options(tuple(0))); - assertEquals(3, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); - } - - @Test - public void testPrepareInOneMarker() throws Throwable - { - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 0, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 0)"); - execute("INSERT INTO %s.multiple_clustering (a, b, c, d) VALUES (0, 0, 1, 1)"); - - UntypedResultSet results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ?"), - options(list(tuple(0, 1, 0), tuple(0, 1, 1)))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - // same query, but reversed order for the IN values - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ?"), - options(list(tuple(0, 1, 1), tuple(0, 1, 0)))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - results = executePrepared(prepare( - "SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ?"), - options(list())); - assertTrue(results.isEmpty()); - - results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b, c) IN ?"), - options(list(tuple(0, 1)))); - assertEquals(2, results.size()); - checkRow(0, results, 0, 0, 1, 0); - checkRow(1, results, 0, 0, 1, 1); - - results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN ?"), - options(list(tuple(0)))); - assertEquals(3, results.size()); - checkRow(0, results, 0, 0, 0, 0); - checkRow(1, results, 0, 0, 1, 0); - checkRow(2, results, 0, 0, 1, 1); - - results = executePrepared(prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 and (b) IN ?"), - options(list())); - assertTrue(results.isEmpty()); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareLiteralInWithShortTuple() throws Throwable - { - prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?))"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareLiteralInWithLongTuple() throws Throwable - { - prepare("SELECT * FROM %s.multiple_clustering WHERE a=0 AND (b, c, d) IN ((?, ?, ?, ?, ?))"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareLiteralInWithPartitionKey() throws Throwable - { - prepare("SELECT * FROM %s.multiple_clustering WHERE (a, b, c, d) IN ((?, ?, ?, ?))"); - } - - @Test(expected=InvalidRequestException.class) - public void testPrepareLiteralInSkipsClusteringColumn() throws Throwable - { - prepare("SELECT * FROM %s.multiple_clustering WHERE (c, d) IN ((?, ?))"); - } - - private static QueryOptions makeIntOptions(Integer... values) - { - List buffers = new ArrayList<>(values.length); - for (int value : values) - buffers.add(ByteBufferUtil.bytes(value)); - return QueryOptions.forInternalCalls(buffers); - } - - private static ByteBuffer tuple(Integer... values) - { - List> types = new ArrayList<>(values.length); - ByteBuffer[] buffers = new ByteBuffer[values.length]; - for (int i = 0; i < values.length; i++) - { - types.add(Int32Type.instance); - buffers[i] = ByteBufferUtil.bytes(values[i]); - } - - TupleType type = new TupleType(types); - return type.buildValue(buffers); - } - - private static ByteBuffer list(ByteBuffer... values) - { - return CollectionSerializer.pack(Arrays.asList(values), values.length, 3); - } - - private static QueryOptions options(ByteBuffer... buffers) - { - return QueryOptions.forInternalCalls(Arrays.asList(buffers)); - } - - private static void checkRow(int rowIndex, UntypedResultSet results, Integer... expectedValues) - { - List rows = newArrayList(results.iterator()); - UntypedResultSet.Row row = rows.get(rowIndex); - Iterator columns = row.getColumns().iterator(); - for (Integer expected : expectedValues) - { - String columnName = columns.next().name.toString(); - int actual = row.getInt(columnName); - assertEquals(String.format("Expected value %d for column %s in row %d, but got %s", actual, columnName, rowIndex, expected), - (long) expected, actual); - } - } -} \ No newline at end of file +} diff --git a/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java b/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java new file mode 100644 index 0000000000..84512a5013 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/TupleTypeTest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cassandra.cql3; + +import org.junit.Test; + +public class TupleTypeTest extends CQLTester +{ + @Test + public void testTuplePutAndGet() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, t tuple)"); + + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(3, "foo", 3.4)); + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 1, tuple(8, "bar", 0.2)); + assertAllRows( + row(0, tuple(3, "foo", 3.4)), + row(1, tuple(8, "bar", 0.2)) + ); + + // nulls + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 2, tuple(5, null, 3.4)); + assertRows(execute("SELECT * FROM %s WHERE k=?", 2), + row(2, tuple(5, null, 3.4)) + ); + + // incomplete tuple + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 3, tuple(5, "bar")); + assertRows(execute("SELECT * FROM %s WHERE k=?", 3), + row(3, tuple(5, "bar")) + ); + } + + @Test + public void testNestedTuple() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, t tuple>)"); + + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(3, tuple("foo", 3.4))); + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 1, tuple(8, tuple("bar", 0.2))); + assertAllRows( + row(0, tuple(3, tuple("foo", 3.4))), + row(1, tuple(8, tuple("bar", 0.2))) + ); + } + + @Test + public void testTupleInPartitionKey() throws Throwable + { + createTable("CREATE TABLE %s (t tuple PRIMARY KEY)"); + + execute("INSERT INTO %s (t) VALUES (?)", tuple(3, "foo")); + assertAllRows(row(tuple(3, "foo"))); + } + + @Test + public void testTupleInClusteringKey() throws Throwable + { + createTable("CREATE TABLE %s (k int, t tuple, PRIMARY KEY (k, t))"); + + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(5, "bar")); + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(3, "foo")); + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(6, "bar")); + execute("INSERT INTO %s (k, t) VALUES (?, ?)", 0, tuple(5, "foo")); + + assertAllRows( + row(0, tuple(3, "foo")), + row(0, tuple(5, "bar")), + row(0, tuple(5, "foo")), + row(0, tuple(6, "bar")) + ); + } + + @Test + public void testInvalidQueries() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, t tuple)"); + + assertInvalid("INSERT INTO %s (k, t) VALUES (0, ())"); + assertInvalid("INSERT INTO %s (k, t) VALUES (0, (2, 'foo', 3.1, 'bar'))"); + } +}