Support for non-frozen UDTS

Patch by Tyler Hobbs; reviewed by Benjamin Lerer for CASSANDRA-7423
This commit is contained in:
Tyler Hobbs 2016-04-08 11:56:39 -05:00
parent 66fb8f51ed
commit 677230df69
50 changed files with 2053 additions and 522 deletions

View File

@ -1,4 +1,6 @@
3.6
* Support for non-frozen user-defined types, updating
individual fields of user-defined types (CASSANDRA-7423)
* Make LZ4 compression level configurable (CASSANDRA-11051)
* Allow per-partition LIMIT clause in CQL (CASSANDRA-7017)
* Make custom filtering more extensible with UserExpression (CASSANDRA-11295)

View File

@ -898,8 +898,7 @@ class Shell(cmd.Cmd):
except KeyError:
raise UserTypeNotFound("User type %r not found" % typename)
return [(field_name, field_type.cql_parameterized_type())
for field_name, field_type in zip(user_type.field_names, user_type.field_types)]
return zip(user_type.field_names, user_type.field_types)
def get_userfunction_names(self, ksname=None):
if ksname is None:

View File

@ -870,13 +870,17 @@ bc(syntax)..
| <identifier> '=' <identifier> ('+' | '-') (<int-term> | <set-literal> | <list-literal>)
| <identifier> '=' <identifier> '+' <map-literal>
| <identifier> '[' <term> ']' '=' <term>
| <identifier> '.' <field> '=' <term>
<condition> ::= <identifier> <op> <term>
| <identifier> IN (<variable> | '(' ( <term> ( ',' <term> )* )? ')')
| <identifier> IN <in-values>
| <identifier> '[' <term> ']' <op> <term>
| <identifier> '[' <term> ']' IN <term>
| <identifier> '[' <term> ']' IN <in-values>
| <identifier> '.' <field> <op> <term>
| <identifier> '.' <field> IN <in-values>
<op> ::= '<' | '<=' | '=' | '!=' | '>=' | '>'
<in-values> ::= (<variable> | '(' ( <term> ( ',' <term> )* )? ')')
<where-clause> ::= <relation> ( AND <relation> )*
@ -913,6 +917,8 @@ The @c = c + 3@ form of @<assignment>@ is used to increment/decrement counters.
The @id = id + <collection-literal>@ and @id[value1] = value2@ forms of @<assignment>@ are for collections. Please refer to the "relevant section":#collections for more details.
The @id.field = <term>@ form of @<assignemt>@ is for setting the value of a single field on a non-frozen user-defined types.
h4(#updateOptions). @<options>@
The @UPDATE@ and @INSERT@ statements support the following options:
@ -931,7 +937,9 @@ bc(syntax)..
WHERE <where-clause>
( IF ( EXISTS | ( <condition> ( AND <condition> )*) ) )?
<selection> ::= <identifier> ( '[' <term> ']' )?
<selection> ::= <identifier>
| <identifier> '[' <term> ']'
| <identifier> '.' <field>
<where-clause> ::= <relation> ( AND <relation> )*
@ -943,11 +951,14 @@ bc(syntax)..
| '(' <identifier> (',' <identifier>)* ')' IN <variable>
<op> ::= '=' | '<' | '>' | '<=' | '>='
<in-values> ::= (<variable> | '(' ( <term> ( ',' <term> )* )? ')')
<condition> ::= <identifier> (<op> | '!=') <term>
| <identifier> IN (<variable> | '(' ( <term> ( ',' <term> )* )? ')')
| <identifier> IN <in-values>
| <identifier> '[' <term> ']' (<op> | '!=') <term>
| <identifier> '[' <term> ']' IN <term>
| <identifier> '[' <term> ']' IN <in-values>
| <identifier> '.' <field> (<op> | '!=') <term>
| <identifier> '.' <field> IN <in-values>
p.
__Sample:__
@ -957,7 +968,7 @@ DELETE FROM NerdMovies USING TIMESTAMP 1240003134 WHERE movie = 'Serenity';
DELETE phone FROM Users WHERE userid IN (C73DE1D3-AF08-40F3-B124-3FF3E5109F22, B70DE1D0-9908-4AE3-BE34-5573E5B09F14);
p.
The @DELETE@ statement deletes columns and rows. If column names are provided directly after the @DELETE@ keyword, only those columns are deleted from the row indicated by the @<where-clause>@ (the @id[value]@ syntax in @<selection>@ is for collection, please refer to the "collection section":#collections for more details). Otherwise, whole rows are removed. The @<where-clause>@ specifies which rows are to be deleted. Multiple rows may be deleted with one statement by using an @IN@ clause. A range of rows may be deleted using an inequality operator (such as @>=@).
The @DELETE@ statement deletes columns and rows. If column names are provided directly after the @DELETE@ keyword, only those columns are deleted from the row indicated by the @<where-clause>@. The @id[value]@ syntax in @<selection>@ is for non-frozen collections (please refer to the "collection section":#collections for more details). The @id.field@ syntax is for the deletion of non-frozen user-defined types. Otherwise, whole rows are removed. The @<where-clause>@ specifies which rows are to be deleted. Multiple rows may be deleted with one statement by using an @IN@ clause. A range of rows may be deleted using an inequality operator (such as @>=@).
@DELETE@ supports the @TIMESTAMP@ option with the same semantics as the "@UPDATE@":#updateStmt statement.
@ -2318,6 +2329,10 @@ h3. 3.4.2
* "@ALTER TABLE@":#alterTableStmt @ADD@ and @DROP@ now allow mutiple columns to be added/removed
* New "@PER PARTITION LIMIT@":#selectLimit option (see "CASSANDRA-7017":https://issues.apache.org/jira/browse/CASSANDRA-7017).
h3. 3.4.2
* User-defined types may now be stored in a non-frozen form, allowing individual fields to be updated and deleted in "@UPDATE@ statements":#updateStmt and "@DELETE@ statements":#deleteStmt, respectively. (CASSANDRA-7423)
h3. 3.4.1
* Adds @CAST@ functions. See "@Cast@":#castFun.

View File

@ -552,13 +552,13 @@ def ks_name_completer(ctxt, cass):
@completer_for('nonSystemKeyspaceName', 'ksname')
def ks_name_completer(ctxt, cass):
def non_system_ks_name_completer(ctxt, cass):
ksnames = [n for n in cass.get_keyspace_names() if n not in SYSTEM_KEYSPACES]
return map(maybe_escape_name, ksnames)
@completer_for('alterableKeyspaceName', 'ksname')
def ks_name_completer(ctxt, cass):
def alterable_ks_name_completer(ctxt, cass):
ksnames = [n for n in cass.get_keyspace_names() if n not in NONALTERBALE_KEYSPACES]
return map(maybe_escape_name, ksnames)
@ -864,7 +864,6 @@ def insert_newval_completer(ctxt, cass):
@completer_for('insertStatement', 'valcomma')
def insert_valcomma_completer(ctxt, cass):
layout = get_table_meta(ctxt, cass)
numcols = len(ctxt.get_binding('colname', ()))
numvals = len(ctxt.get_binding('newval', ()))
if numcols > numvals:
@ -888,21 +887,27 @@ syntax_rules += r'''
( "IF" ( "EXISTS" | <conditions> ))?
;
<assignment> ::= updatecol=<cident>
( "=" update_rhs=( <term> | <cident> )
(( "=" update_rhs=( <term> | <cident> )
( counterop=( "+" | "-" ) inc=<wholenumber>
| listadder="+" listcol=<cident> )?
| indexbracket="[" <term> "]" "=" <term> )
| listadder="+" listcol=<cident> )? )
| ( indexbracket="[" <term> "]" "=" <term> )
| ( udt_field_dot="." udt_field=<identifier> "=" <term> ))
;
<conditions> ::= <condition> ( "AND" <condition> )*
;
<condition> ::= <cident> ( "[" <term> "]" )? (("=" | "<" | ">" | "<=" | ">=" | "!=") <term>
| "IN" "(" <term> ( "," <term> )* ")")
<condition_op_and_rhs> ::= (("=" | "<" | ">" | "<=" | ">=" | "!=") <term>)
| ("IN" "(" <term> ( "," <term> )* ")" )
;
<condition> ::= conditioncol=<cident>
( (( indexbracket="[" <term> "]" )
|( udt_field_dot="." udt_field=<identifier> )) )?
<condition_op_and_rhs>
;
'''
@completer_for('updateStatement', 'updateopt')
def insert_option_completer(ctxt, cass):
def update_option_completer(ctxt, cass):
opts = set('TIMESTAMP TTL'.split())
for opt in ctxt.get_binding('updateopt', ()):
opts.discard(opt.split()[0])
@ -971,6 +976,62 @@ def update_indexbracket_completer(ctxt, cass):
return ['[']
return []
@completer_for('assignment', 'udt_field_dot')
def update_udt_field_dot_completer(ctxt, cass):
layout = get_table_meta(ctxt, cass)
curcol = dequote_name(ctxt.get_binding('updatecol', ''))
return ["."] if _is_usertype(layout, curcol) else []
@completer_for('assignment', 'udt_field')
def assignment_udt_field_completer(ctxt, cass):
layout = get_table_meta(ctxt, cass)
curcol = dequote_name(ctxt.get_binding('updatecol', ''))
return _usertype_fields(ctxt, cass, layout, curcol)
def _is_usertype(layout, curcol):
coltype = layout.columns[curcol].cql_type
return coltype not in simple_cql_types and coltype not in ('map', 'set', 'list')
def _usertype_fields(ctxt, cass, layout, curcol):
if not _is_usertype(layout, curcol):
return []
coltype = layout.columns[curcol].cql_type
ks = ctxt.get_binding('ksname', None)
if ks is not None:
ks = dequote_name(ks)
user_type = cass.get_usertype_layout(ks, coltype)
return [field_name for (field_name, field_type) in user_type]
@completer_for('condition', 'indexbracket')
def condition_indexbracket_completer(ctxt, cass):
layout = get_table_meta(ctxt, cass)
curcol = dequote_name(ctxt.get_binding('conditioncol', ''))
coltype = layout.columns[curcol].cql_type
if coltype in ('map', 'list'):
return ['[']
return []
@completer_for('condition', 'udt_field_dot')
def condition_udt_field_dot_completer(ctxt, cass):
layout = get_table_meta(ctxt, cass)
curcol = dequote_name(ctxt.get_binding('conditioncol', ''))
return ["."] if _is_usertype(layout, curcol) else []
@completer_for('condition', 'udt_field')
def condition_udt_field_completer(ctxt, cass):
layout = get_table_meta(ctxt, cass)
curcol = dequote_name(ctxt.get_binding('conditioncol', ''))
return _usertype_fields(ctxt, cass, layout, curcol)
syntax_rules += r'''
<deleteStatement> ::= "DELETE" ( <deleteSelector> ( "," <deleteSelector> )* )?
"FROM" cf=<columnFamilyName>
@ -978,7 +1039,9 @@ syntax_rules += r'''
"WHERE" <whereClause>
( "IF" ( "EXISTS" | <conditions> ) )?
;
<deleteSelector> ::= delcol=<cident> ( memberbracket="[" memberselector=<term> "]" )?
<deleteSelector> ::= delcol=<cident>
( ( "[" <term> "]" )
| ( "." <identifier> ) )?
;
<deleteOption> ::= "TIMESTAMP" <wholenumber>
;
@ -998,6 +1061,7 @@ def delete_delcol_completer(ctxt, cass):
layout = get_table_meta(ctxt, cass)
return map(maybe_escape_name, regular_column_names(layout))
syntax_rules += r'''
<batchStatement> ::= "BEGIN" ( "UNLOGGED" | "COUNTER" )? "BATCH"
( "USING" [batchopt]=<usingOption>
@ -1459,7 +1523,7 @@ def get_trigger_names(ctxt, cass):
@completer_for('dropTriggerStatement', 'triggername')
def alter_type_field_completer(ctxt, cass):
def drop_trigger_completer(ctxt, cass):
names = get_trigger_names(ctxt, cass)
return map(maybe_escape_name, names)

View File

@ -367,12 +367,12 @@ class TestCqlshCompletion(CqlshCompletionCase):
choices=['EXISTS', '<quotedName>', '<identifier>'])
self.trycompletions("UPDATE empty_table SET lonelycol = 'eggs' WHERE TOKEN(lonelykey) <= TOKEN(13) IF EXISTS ",
choices=['>=', '!=', '<=', 'IN', '[', ';', '=', '<', '>'])
choices=['>=', '!=', '<=', 'IN', '[', ';', '=', '<', '>', '.'])
def test_complete_in_delete(self):
self.trycompletions('DELETE F', choices=['FROM', '<identifier>', '<quotedName>'])
self.trycompletions('DELETE a ', choices=['FROM', '[', ','])
self.trycompletions('DELETE a ', choices=['FROM', '[', '.', ','])
self.trycompletions('DELETE a [',
choices=['<wholenumber>', 'false', '-', '<uuid>',
'<pgStringLiteral>', '<float>', 'TOKEN',
@ -449,7 +449,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
choices=['EXISTS', '<identifier>', '<quotedName>'])
self.trycompletions(('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE '
'TOKEN(a) >= TOKEN(0) IF b '),
choices=['>=', '!=', '<=', 'IN', '[', '=', '<', '>'])
choices=['>=', '!=', '<=', 'IN', '=', '<', '>'])
self.trycompletions(('DELETE FROM twenty_rows_composite_table USING TIMESTAMP 0 WHERE '
'TOKEN(a) >= TOKEN(0) IF b < 0 '),
choices=['AND', ';'])

View File

@ -429,6 +429,7 @@ deleteSelection returns [List<Operation.RawDeletion> operations]
deleteOp returns [Operation.RawDeletion op]
: c=cident { $op = new Operation.ColumnDeletion(c); }
| c=cident '[' t=term ']' { $op = new Operation.ElementDeletion(c, t); }
| c=cident '.' field=cident { $op = new Operation.FieldDeletion(c, field); }
;
usingClauseDelete[Attributes.Raw attrs]
@ -1282,7 +1283,8 @@ columnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations
columnOperationDifferentiator[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key]
: '=' normalColumnOperation[operations, key]
| '[' k=term ']' specializedColumnOperation[operations, key, k]
| '[' k=term ']' collectionColumnOperation[operations, key, k]
| '.' field=cident udtColumnOperation[operations, key, field]
;
normalColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key]
@ -1315,13 +1317,20 @@ normalColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> oper
}
;
specializedColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key, Term.Raw k]
collectionColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key, Term.Raw k]
: '=' t=term
{
addRawUpdate(operations, key, new Operation.SetElement(k, t));
}
;
udtColumnOperation[List<Pair<ColumnIdentifier.Raw, Operation.RawUpdate>> operations, ColumnIdentifier.Raw key, ColumnIdentifier.Raw field]
: '=' t=term
{
addRawUpdate(operations, key, new Operation.SetField(field, t));
}
;
columnCondition[List<Pair<ColumnIdentifier.Raw, ColumnCondition.Raw>> conditions]
// Note: we'll reject duplicates later
: key=cident
@ -1337,6 +1346,13 @@ columnCondition[List<Pair<ColumnIdentifier.Raw, ColumnCondition.Raw>> conditions
| marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.collectionInCondition(element, marker))); }
)
)
| '.' field=cident
( op=relationType t=term { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldCondition(t, field, op))); }
| K_IN
( values=singleColumnInValues { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldInCondition(field, values))); }
| marker=inMarker { conditions.add(Pair.create(key, ColumnCondition.Raw.udtFieldInCondition(field, marker))); }
)
)
)
;

View File

@ -164,10 +164,13 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
private static Comparator<CellPath> makeCellPathComparator(Kind kind, AbstractType<?> type)
{
if (kind.isPrimaryKeyKind() || !type.isCollection() || !type.isMultiCell())
if (kind.isPrimaryKeyKind() || !type.isMultiCell())
return null;
CollectionType collection = (CollectionType) type;
AbstractType<?> nameComparator = type.isCollection()
? ((CollectionType) type).nameComparator()
: ((UserType) type).nameComparator();
return new Comparator<CellPath>()
{
@ -184,7 +187,7 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
// This will get more complicated once we have non-frozen UDT and nested collections
assert path1.size() == 1 && path2.size() == 1;
return collection.nameComparator().compare(path1.get(0), path2.get(0));
return nameComparator.compare(path1.get(0), path2.get(0));
}
};
}
@ -365,8 +368,11 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
if (!isComplex())
throw new MarshalException("Only complex cells should have a cell path");
assert type instanceof CollectionType;
((CollectionType)type).nameComparator().validate(path.get(0));
assert type.isMultiCell();
if (type.isCollection())
((CollectionType)type).nameComparator().validate(path.get(0));
else
((UserType)type).nameComparator().validate(path.get(0));
}
public static String toCQLString(Iterable<ColumnDefinition> defs)

View File

@ -67,16 +67,26 @@ public abstract class AbstractMarker extends Term.NonTerminal
public NonTerminal prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{
if (!(receiver.type instanceof CollectionType))
return new Constants.Marker(bindIndex, receiver);
switch (((CollectionType)receiver.type).kind)
if (receiver.type.isCollection())
{
case LIST: return new Lists.Marker(bindIndex, receiver);
case SET: return new Sets.Marker(bindIndex, receiver);
case MAP: return new Maps.Marker(bindIndex, receiver);
switch (((CollectionType) receiver.type).kind)
{
case LIST:
return new Lists.Marker(bindIndex, receiver);
case SET:
return new Sets.Marker(bindIndex, receiver);
case MAP:
return new Maps.Marker(bindIndex, receiver);
default:
throw new AssertionError();
}
}
throw new AssertionError();
else if (receiver.type.isUDT())
{
return new UserTypes.Marker(bindIndex, receiver);
}
return new Constants.Marker(bindIndex, receiver);
}
public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver)

View File

@ -39,7 +39,16 @@ public interface CQL3Type
{
static final Logger logger = LoggerFactory.getLogger(CQL3Type.class);
public boolean isCollection();
default boolean isCollection()
{
return false;
}
default boolean isUDT()
{
return false;
}
public AbstractType<?> getType();
/**
@ -82,11 +91,6 @@ public interface CQL3Type
this.type = type;
}
public boolean isCollection()
{
return false;
}
public AbstractType<?> getType()
{
return type;
@ -125,11 +129,6 @@ public interface CQL3Type
this(TypeParser.parse(className));
}
public boolean isCollection()
{
return false;
}
public AbstractType<?> getType()
{
return type;
@ -305,9 +304,9 @@ public interface CQL3Type
return new UserDefined(UTF8Type.instance.compose(type.name), type);
}
public boolean isCollection()
public boolean isUDT()
{
return false;
return true;
}
public AbstractType<?> getType()
@ -377,7 +376,10 @@ public interface CQL3Type
@Override
public String toString()
{
return "frozen<" + ColumnIdentifier.maybeQuote(name) + '>';
if (type.isMultiCell())
return ColumnIdentifier.maybeQuote(name);
else
return "frozen<" + ColumnIdentifier.maybeQuote(name) + '>';
}
}
@ -395,11 +397,6 @@ public interface CQL3Type
return new Tuple(type);
}
public boolean isCollection()
{
return false;
}
public AbstractType<?> getType()
{
return type;
@ -485,12 +482,7 @@ public interface CQL3Type
{
protected boolean frozen = false;
protected abstract boolean supportsFreezing();
public boolean isCollection()
{
return false;
}
public abstract boolean supportsFreezing();
public boolean isFrozen()
{
@ -507,6 +499,11 @@ public interface CQL3Type
return false;
}
public boolean isUDT()
{
return false;
}
public String keyspace()
{
return null;
@ -588,7 +585,7 @@ public interface CQL3Type
return type;
}
protected boolean supportsFreezing()
public boolean supportsFreezing()
{
return false;
}
@ -627,7 +624,7 @@ public interface CQL3Type
frozen = true;
}
protected boolean supportsFreezing()
public boolean supportsFreezing()
{
return true;
}
@ -652,7 +649,7 @@ public interface CQL3Type
assert values != null : "Got null values type for a collection";
if (!frozen && values.supportsFreezing() && !values.frozen)
throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this);
throwNestedNonFrozenError(values);
// we represent Thrift supercolumns as maps, internally, and we do allow counters in supercolumns. Thus,
// for internal type parsing (think schema) we have to make an exception and allow counters as (map) values
@ -664,22 +661,31 @@ public interface CQL3Type
if (keys.isCounter())
throw new InvalidRequestException("Counters are not allowed inside collections: " + this);
if (!frozen && keys.supportsFreezing() && !keys.frozen)
throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this);
throwNestedNonFrozenError(keys);
}
AbstractType<?> valueType = values.prepare(keyspace, udts).getType();
switch (kind)
{
case LIST:
return new Collection(ListType.getInstance(values.prepare(keyspace, udts).getType(), !frozen));
return new Collection(ListType.getInstance(valueType, !frozen));
case SET:
return new Collection(SetType.getInstance(values.prepare(keyspace, udts).getType(), !frozen));
return new Collection(SetType.getInstance(valueType, !frozen));
case MAP:
assert keys != null : "Got null keys type for a collection";
return new Collection(MapType.getInstance(keys.prepare(keyspace, udts).getType(), values.prepare(keyspace, udts).getType(), !frozen));
return new Collection(MapType.getInstance(keys.prepare(keyspace, udts).getType(), valueType, !frozen));
}
throw new AssertionError();
}
private void throwNestedNonFrozenError(Raw innerType)
{
if (innerType instanceof RawCollection)
throw new InvalidRequestException("Non-frozen collections are not allowed inside collections: " + this);
else
throw new InvalidRequestException("Non-frozen UDTs are not allowed inside collections: " + this);
}
public boolean referencesUserType(String name)
{
return (keys != null && keys.referencesUserType(name)) || values.referencesUserType(name);
@ -721,7 +727,7 @@ public interface CQL3Type
public boolean canBeNonFrozen()
{
return false;
return true;
}
public CQL3Type prepare(String keyspace, Types udts) throws InvalidRequestException
@ -744,9 +750,8 @@ public interface CQL3Type
if (type == null)
throw new InvalidRequestException("Unknown type " + name);
if (!frozen)
throw new InvalidRequestException("Non-frozen User-Defined types are not supported, please use frozen<>");
if (frozen)
type = type.freeze();
return new UserDefined(name.toString(), type);
}
@ -755,7 +760,12 @@ public interface CQL3Type
return this.name.getStringTypeName().equals(name);
}
protected boolean supportsFreezing()
public boolean supportsFreezing()
{
return true;
}
public boolean isUDT()
{
return true;
}
@ -776,7 +786,7 @@ public interface CQL3Type
this.types = types;
}
protected boolean supportsFreezing()
public boolean supportsFreezing()
{
return true;
}

View File

@ -23,6 +23,7 @@ import java.util.*;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.rows.*;
@ -31,8 +32,6 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.ByteBufferUtil;
import static com.google.common.collect.Lists.newArrayList;
/**
* A CQL3 condition on the value of a column or collection element. For example, "UPDATE .. IF a = 0".
*/
@ -43,51 +42,71 @@ public class ColumnCondition
// For collection, when testing the equality of a specific element, null otherwise.
private final Term collectionElement;
// For UDT, when testing the equality of a specific field, null otherwise.
private final ColumnIdentifier field;
private final Term value; // a single value or a marker for a list of IN values
private final List<Term> inValues;
public final Operator operator;
private ColumnCondition(ColumnDefinition column, Term collectionElement, Term value, List<Term> inValues, Operator op)
private ColumnCondition(ColumnDefinition column, Term collectionElement, ColumnIdentifier field, Term value, List<Term> inValues, Operator op)
{
this.column = column;
this.collectionElement = collectionElement;
this.field = field;
this.value = value;
this.inValues = inValues;
this.operator = op;
assert field == null || collectionElement == null;
if (operator != Operator.IN)
assert this.inValues == null;
}
public static ColumnCondition condition(ColumnDefinition column, Term value, Operator op)
{
return new ColumnCondition(column, null, value, null, op);
return new ColumnCondition(column, null, null, value, null, op);
}
public static ColumnCondition condition(ColumnDefinition column, Term collectionElement, Term value, Operator op)
{
return new ColumnCondition(column, collectionElement, value, null, op);
return new ColumnCondition(column, collectionElement, null, value, null, op);
}
public static ColumnCondition condition(ColumnDefinition column, ColumnIdentifier udtField, Term value, Operator op)
{
return new ColumnCondition(column, null, udtField, value, null, op);
}
public static ColumnCondition inCondition(ColumnDefinition column, List<Term> inValues)
{
return new ColumnCondition(column, null, null, inValues, Operator.IN);
return new ColumnCondition(column, null, null, null, inValues, Operator.IN);
}
public static ColumnCondition inCondition(ColumnDefinition column, Term collectionElement, List<Term> inValues)
{
return new ColumnCondition(column, collectionElement, null, inValues, Operator.IN);
return new ColumnCondition(column, collectionElement, null, null, inValues, Operator.IN);
}
public static ColumnCondition inCondition(ColumnDefinition column, ColumnIdentifier udtField, List<Term> inValues)
{
return new ColumnCondition(column, null, udtField, null, inValues, Operator.IN);
}
public static ColumnCondition inCondition(ColumnDefinition column, Term inMarker)
{
return new ColumnCondition(column, null, inMarker, null, Operator.IN);
return new ColumnCondition(column, null, null, inMarker, null, Operator.IN);
}
public static ColumnCondition inCondition(ColumnDefinition column, Term collectionElement, Term inMarker)
{
return new ColumnCondition(column, collectionElement, inMarker, null, Operator.IN);
return new ColumnCondition(column, collectionElement, null, inMarker, null, Operator.IN);
}
public static ColumnCondition inCondition(ColumnDefinition column, ColumnIdentifier udtField, Term inMarker)
{
return new ColumnCondition(column, null, udtField, inMarker, null, Operator.IN);
}
public Iterable<Function> getFunctions()
@ -131,11 +150,19 @@ public class ColumnCondition
boolean isInCondition = operator == Operator.IN;
if (column.type instanceof CollectionType)
{
if (collectionElement == null)
return isInCondition ? new CollectionInBound(this, options) : new CollectionBound(this, options);
else
if (collectionElement != null)
return isInCondition ? new ElementAccessInBound(this, options) : new ElementAccessBound(this, options);
else
return isInCondition ? new CollectionInBound(this, options) : new CollectionBound(this, options);
}
else if (column.type.isUDT())
{
if (field != null)
return isInCondition ? new UDTFieldAccessInBound(this, options) : new UDTFieldAccessBound(this, options);
else
return isInCondition ? new UDTInBound(this, options) : new UDTBound(this, options);
}
return isInCondition ? new SimpleInBound(this, options) : new SimpleBound(this, options);
}
@ -216,6 +243,35 @@ public class ColumnCondition
return complexData == null ? Collections.<Cell>emptyIterator() : complexData.iterator();
}
private static boolean evaluateComparisonWithOperator(int comparison, Operator operator)
{
// called when comparison != 0
switch (operator)
{
case EQ:
return false;
case LT:
case LTE:
return comparison < 0;
case GT:
case GTE:
return comparison > 0;
case NEQ:
return true;
default:
throw new AssertionError();
}
}
private static ByteBuffer cellValueAtIndex(Iterator<Cell> iter, int index)
{
int adv = Iterators.advance(iter, index);
if (adv == index && iter.hasNext())
return iter.next().value();
else
return null;
}
/**
* A condition on a single non-collection column. This does not support IN operators (see SimpleInBound).
*/
@ -226,7 +282,7 @@ public class ColumnCondition
private SimpleBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
{
super(condition.column, condition.operator);
assert !(column.type instanceof CollectionType) && condition.collectionElement == null;
assert !(column.type instanceof CollectionType) && condition.field == null;
assert condition.operator != Operator.IN;
this.value = condition.value.bindAndGet(options);
}
@ -247,7 +303,7 @@ public class ColumnCondition
private SimpleInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
{
super(condition.column, condition.operator);
assert !(column.type instanceof CollectionType) && condition.collectionElement == null;
assert !(column.type instanceof CollectionType) && condition.field == null;
assert condition.operator == Operator.IN;
if (condition.inValues == null)
this.inValues = ((Lists.Value) condition.value.bind(options)).getElements();
@ -311,7 +367,7 @@ public class ColumnCondition
ListType listType = (ListType) column.type;
if (column.type.isMultiCell())
{
ByteBuffer columnValue = getListItem(getCells(row, column), getListIndex(collectionElement));
ByteBuffer columnValue = cellValueAtIndex(getCells(row, column), getListIndex(collectionElement));
return compareWithOperator(operator, ((ListType)column.type).getElementsType(), value, columnValue);
}
else
@ -330,15 +386,6 @@ public class ColumnCondition
return idx;
}
static ByteBuffer getListItem(Iterator<Cell> iter, int index)
{
int adv = Iterators.advance(iter, index);
if (adv == index && iter.hasNext())
return iter.next().value();
else
return null;
}
public ByteBuffer getCollectionElementValue()
{
return collectionElement;
@ -371,71 +418,46 @@ public class ColumnCondition
if (collectionElement == null)
throw new InvalidRequestException("Invalid null value for " + (column.type instanceof MapType ? "map" : "list") + " element access");
ByteBuffer cellValue;
AbstractType<?> valueType;
if (column.type instanceof MapType)
{
MapType mapType = (MapType) column.type;
AbstractType<?> valueType = mapType.getValuesType();
valueType = mapType.getValuesType();
if (column.type.isMultiCell())
{
Cell item = getCell(row, column, CellPath.create(collectionElement));
for (ByteBuffer value : inValues)
{
if (isSatisfiedByValue(value, item, valueType, Operator.EQ))
return true;
}
return false;
Cell cell = getCell(row, column, CellPath.create(collectionElement));
cellValue = cell == null ? null : cell.value();
}
else
{
Cell cell = getCell(row, column);
ByteBuffer mapElementValue = cell == null
? null
: mapType.getSerializer().getSerializedValue(cell.value(), collectionElement, mapType.getKeysType());
for (ByteBuffer value : inValues)
{
if (value == null)
{
if (mapElementValue == null)
return true;
continue;
}
if (valueType.compare(value, mapElementValue) == 0)
return true;
}
return false;
cellValue = cell == null
? null
: mapType.getSerializer().getSerializedValue(cell.value(), collectionElement, mapType.getKeysType());
}
}
else // ListType
{
ListType listType = (ListType) column.type;
valueType = listType.getElementsType();
if (column.type.isMultiCell())
{
cellValue = cellValueAtIndex(getCells(row, column), ElementAccessBound.getListIndex(collectionElement));
}
else
{
Cell cell = getCell(row, column);
cellValue = cell == null
? null
: listType.getSerializer().getElement(cell.value(), ElementAccessBound.getListIndex(collectionElement));
}
}
ListType listType = (ListType) column.type;
AbstractType<?> elementsType = listType.getElementsType();
if (column.type.isMultiCell())
for (ByteBuffer value : inValues)
{
ByteBuffer columnValue = ElementAccessBound.getListItem(getCells(row, column), ElementAccessBound.getListIndex(collectionElement));
for (ByteBuffer value : inValues)
{
if (compareWithOperator(Operator.EQ, elementsType, value, columnValue))
return true;
}
}
else
{
Cell cell = getCell(row, column);
ByteBuffer listElementValue = cell == null
? null
: listType.getSerializer().getElement(cell.value(), ElementAccessBound.getListIndex(collectionElement));
for (ByteBuffer value : inValues)
{
if (value == null)
{
if (listElementValue == null)
return true;
continue;
}
if (elementsType.compare(value, listElementValue) == 0)
return true;
}
if (compareWithOperator(Operator.EQ, valueType, value, cellValue))
return true;
}
return false;
}
@ -539,26 +561,6 @@ public class ColumnCondition
return operator == Operator.EQ || operator == Operator.LTE || operator == Operator.GTE;
}
private static boolean evaluateComparisonWithOperator(int comparison, Operator operator)
{
// called when comparison != 0
switch (operator)
{
case EQ:
return false;
case LT:
case LTE:
return comparison < 0;
case GT:
case GTE:
return comparison > 0;
case NEQ:
return true;
default:
throw new AssertionError();
}
}
static boolean listAppliesTo(ListType type, Iterator<Cell> iter, List<ByteBuffer> elements, Operator operator)
{
return setOrListAppliesTo(type.getElementsType(), iter, elements.iterator(), operator, false);
@ -691,6 +693,195 @@ public class ColumnCondition
}
}
/** A condition on a UDT field. IN operators are not supported here, see UDTFieldAccessInBound. */
static class UDTFieldAccessBound extends Bound
{
public final ColumnIdentifier field;
public final ByteBuffer value;
private UDTFieldAccessBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
{
super(condition.column, condition.operator);
assert column.type.isUDT() && condition.field != null;
assert condition.operator != Operator.IN;
this.field = condition.field;
this.value = condition.value.bindAndGet(options);
}
public boolean appliesTo(Row row) throws InvalidRequestException
{
UserType userType = (UserType) column.type;
int fieldPosition = userType.fieldPosition(field);
assert fieldPosition >= 0;
ByteBuffer cellValue;
if (column.type.isMultiCell())
{
Cell cell = getCell(row, column, userType.cellPathForField(field.bytes));
cellValue = cell == null ? null : cell.value();
}
else
{
Cell cell = getCell(row, column);
cellValue = cell == null
? null
: userType.split(cell.value())[fieldPosition];
}
return compareWithOperator(operator, userType.fieldType(fieldPosition), value, cellValue);
}
}
/** An IN condition on a UDT field. For example: IF user.name IN ('a', 'b') */
static class UDTFieldAccessInBound extends Bound
{
public final ColumnIdentifier field;
public final List<ByteBuffer> inValues;
private UDTFieldAccessInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
{
super(condition.column, condition.operator);
assert column.type.isUDT() && condition.field != null;
this.field = condition.field;
if (condition.inValues == null)
this.inValues = ((Lists.Value) condition.value.bind(options)).getElements();
else
{
this.inValues = new ArrayList<>(condition.inValues.size());
for (Term value : condition.inValues)
this.inValues.add(value.bindAndGet(options));
}
}
public boolean appliesTo(Row row) throws InvalidRequestException
{
UserType userType = (UserType) column.type;
int fieldPosition = userType.fieldPosition(field);
assert fieldPosition >= 0;
ByteBuffer cellValue;
if (column.type.isMultiCell())
{
Cell cell = getCell(row, column, userType.cellPathForField(field.bytes));
cellValue = cell == null ? null : cell.value();
}
else
{
Cell cell = getCell(row, column);
cellValue = cell == null ? null : userType.split(getCell(row, column).value())[fieldPosition];
}
AbstractType<?> valueType = userType.fieldType(fieldPosition);
for (ByteBuffer value : inValues)
{
if (compareWithOperator(Operator.EQ, valueType, value, cellValue))
return true;
}
return false;
}
}
/** A non-IN condition on an entire UDT. For example: IF user = {name: 'joe', age: 42}). */
static class UDTBound extends Bound
{
private final ByteBuffer value;
private final int protocolVersion;
private UDTBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
{
super(condition.column, condition.operator);
assert column.type.isUDT() && condition.field == null;
assert condition.operator != Operator.IN;
protocolVersion = options.getProtocolVersion();
value = condition.value.bindAndGet(options);
}
public boolean appliesTo(Row row) throws InvalidRequestException
{
UserType userType = (UserType) column.type;
ByteBuffer rowValue;
if (userType.isMultiCell())
{
Iterator<Cell> iter = getCells(row, column);
rowValue = iter.hasNext() ? userType.serializeForNativeProtocol(iter, protocolVersion) : null;
}
else
{
Cell cell = getCell(row, column);
rowValue = cell == null ? null : cell.value();
}
if (value == null)
{
if (operator == Operator.EQ)
return rowValue == null;
else if (operator == Operator.NEQ)
return rowValue != null;
else
throw new InvalidRequestException(String.format("Invalid comparison with null for operator \"%s\"", operator));
}
return compareWithOperator(operator, userType, value, rowValue);
}
}
/** An IN condition on an entire UDT. For example: IF user IN ({name: 'joe', age: 42}, {name: 'bob', age: 23}). */
public static class UDTInBound extends Bound
{
private final List<ByteBuffer> inValues;
private final int protocolVersion;
private UDTInBound(ColumnCondition condition, QueryOptions options) throws InvalidRequestException
{
super(condition.column, condition.operator);
assert column.type.isUDT() && condition.field == null;
assert condition.operator == Operator.IN;
protocolVersion = options.getProtocolVersion();
inValues = new ArrayList<>();
if (condition.inValues == null)
{
Lists.Marker inValuesMarker = (Lists.Marker) condition.value;
for (ByteBuffer buffer : ((Lists.Value)inValuesMarker.bind(options)).elements)
this.inValues.add(buffer);
}
else
{
for (Term value : condition.inValues)
this.inValues.add(value.bindAndGet(options));
}
}
public boolean appliesTo(Row row) throws InvalidRequestException
{
UserType userType = (UserType) column.type;
ByteBuffer rowValue;
if (userType.isMultiCell())
{
Iterator<Cell> cells = getCells(row, column);
rowValue = cells.hasNext() ? userType.serializeForNativeProtocol(cells, protocolVersion) : null;
}
else
{
Cell cell = getCell(row, column);
rowValue = cell == null ? null : cell.value();
}
for (ByteBuffer value : inValues)
{
if (value == null || rowValue == null)
{
if (value == rowValue) // both null
return true;
}
else if (userType.compare(value, rowValue) == 0)
{
return true;
}
}
return false;
}
}
public static class Raw
{
private final Term.Raw value;
@ -700,59 +891,142 @@ public class ColumnCondition
// Can be null, only used with the syntax "IF m[e] = ..." (in which case it's 'e')
private final Term.Raw collectionElement;
// Can be null, only used with the syntax "IF udt.field = ..." (in which case it's 'field')
private final ColumnIdentifier.Raw udtField;
private final Operator operator;
private Raw(Term.Raw value, List<Term.Raw> inValues, AbstractMarker.INRaw inMarker, Term.Raw collectionElement, Operator op)
private Raw(Term.Raw value, List<Term.Raw> inValues, AbstractMarker.INRaw inMarker, Term.Raw collectionElement,
ColumnIdentifier.Raw udtField, Operator op)
{
this.value = value;
this.inValues = inValues;
this.inMarker = inMarker;
this.collectionElement = collectionElement;
this.udtField = udtField;
this.operator = op;
}
/** A condition on a column. For example: "IF col = 'foo'" */
public static Raw simpleCondition(Term.Raw value, Operator op)
{
return new Raw(value, null, null, null, op);
return new Raw(value, null, null, null, null, op);
}
/** An IN condition on a column. For example: "IF col IN ('foo', 'bar', ...)" */
public static Raw simpleInCondition(List<Term.Raw> inValues)
{
return new Raw(null, inValues, null, null, Operator.IN);
return new Raw(null, inValues, null, null, null, Operator.IN);
}
/** An IN condition on a column with a single marker. For example: "IF col IN ?" */
public static Raw simpleInCondition(AbstractMarker.INRaw inMarker)
{
return new Raw(null, null, inMarker, null, Operator.IN);
return new Raw(null, null, inMarker, null, null, Operator.IN);
}
/** A condition on a collection element. For example: "IF col['key'] = 'foo'" */
public static Raw collectionCondition(Term.Raw value, Term.Raw collectionElement, Operator op)
{
return new Raw(value, null, null, collectionElement, op);
return new Raw(value, null, null, collectionElement, null, op);
}
/** An IN condition on a collection element. For example: "IF col['key'] IN ('foo', 'bar', ...)" */
public static Raw collectionInCondition(Term.Raw collectionElement, List<Term.Raw> inValues)
{
return new Raw(null, inValues, null, collectionElement, Operator.IN);
return new Raw(null, inValues, null, collectionElement, null, Operator.IN);
}
/** An IN condition on a collection element with a single marker. For example: "IF col['key'] IN ?" */
public static Raw collectionInCondition(Term.Raw collectionElement, AbstractMarker.INRaw inMarker)
{
return new Raw(null, null, inMarker, collectionElement, Operator.IN);
return new Raw(null, null, inMarker, collectionElement, null, Operator.IN);
}
public ColumnCondition prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
/** A condition on a UDT field. For example: "IF col.field = 'foo'" */
public static Raw udtFieldCondition(Term.Raw value, ColumnIdentifier.Raw udtField, Operator op)
{
return new Raw(value, null, null, null, udtField, op);
}
/** An IN condition on a collection element. For example: "IF col.field IN ('foo', 'bar', ...)" */
public static Raw udtFieldInCondition(ColumnIdentifier.Raw udtField, List<Term.Raw> inValues)
{
return new Raw(null, inValues, null, null, udtField, Operator.IN);
}
/** An IN condition on a collection element with a single marker. For example: "IF col.field IN ?" */
public static Raw udtFieldInCondition(ColumnIdentifier.Raw udtField, AbstractMarker.INRaw inMarker)
{
return new Raw(null, null, inMarker, null, udtField, Operator.IN);
}
public ColumnCondition prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException
{
if (receiver.type instanceof CounterColumnType)
throw new InvalidRequestException("Conditions on counters are not supported");
if (collectionElement == null)
if (collectionElement != null)
{
if (!(receiver.type.isCollection()))
throw new InvalidRequestException(String.format("Invalid element access syntax for non-collection column %s", receiver.name));
ColumnSpecification elementSpec, valueSpec;
switch ((((CollectionType) receiver.type).kind))
{
case LIST:
elementSpec = Lists.indexSpecOf(receiver);
valueSpec = Lists.valueSpecOf(receiver);
break;
case MAP:
elementSpec = Maps.keySpecOf(receiver);
valueSpec = Maps.valueSpecOf(receiver);
break;
case SET:
throw new InvalidRequestException(String.format("Invalid element access syntax for set column %s", receiver.name));
default:
throw new AssertionError();
}
if (operator == Operator.IN)
{
if (inValues == null)
return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), inMarker.prepare(keyspace, valueSpec));
List<Term> terms = new ArrayList<>(inValues.size());
for (Term.Raw value : inValues)
terms.add(value.prepare(keyspace, valueSpec));
return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), terms);
}
else
{
return ColumnCondition.condition(receiver, collectionElement.prepare(keyspace, elementSpec), value.prepare(keyspace, valueSpec), operator);
}
}
else if (udtField != null)
{
UserType userType = (UserType) receiver.type;
ColumnIdentifier fieldIdentifier = udtField.prepare(cfm);
int fieldPosition = userType.fieldPosition(fieldIdentifier);
if (fieldPosition == -1)
throw new InvalidRequestException(String.format("Unknown field %s for column %s", fieldIdentifier, receiver.name));
ColumnSpecification fieldReceiver = UserTypes.fieldSpecOf(receiver, fieldPosition);
if (operator == Operator.IN)
{
if (inValues == null)
return ColumnCondition.inCondition(receiver, udtField.prepare(cfm), inMarker.prepare(keyspace, fieldReceiver));
List<Term> terms = new ArrayList<>(inValues.size());
for (Term.Raw value : inValues)
terms.add(value.prepare(keyspace, fieldReceiver));
return ColumnCondition.inCondition(receiver, udtField.prepare(cfm), terms);
}
else
{
return ColumnCondition.condition(receiver, udtField.prepare(cfm), value.prepare(keyspace, fieldReceiver), operator);
}
}
else
{
if (operator == Operator.IN)
{
@ -768,39 +1042,6 @@ public class ColumnCondition
return ColumnCondition.condition(receiver, value.prepare(keyspace, receiver), operator);
}
}
if (!(receiver.type.isCollection()))
throw new InvalidRequestException(String.format("Invalid element access syntax for non-collection column %s", receiver.name));
ColumnSpecification elementSpec, valueSpec;
switch ((((CollectionType)receiver.type).kind))
{
case LIST:
elementSpec = Lists.indexSpecOf(receiver);
valueSpec = Lists.valueSpecOf(receiver);
break;
case MAP:
elementSpec = Maps.keySpecOf(receiver);
valueSpec = Maps.valueSpecOf(receiver);
break;
case SET:
throw new InvalidRequestException(String.format("Invalid element access syntax for set column %s", receiver.name));
default:
throw new AssertionError();
}
if (operator == Operator.IN)
{
if (inValues == null)
return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), inMarker.prepare(keyspace, valueSpec));
List<Term> terms = new ArrayList<>(inValues.size());
for (Term.Raw value : inValues)
terms.add(value.prepare(keyspace, valueSpec));
return ColumnCondition.inCondition(receiver, collectionElement.prepare(keyspace, elementSpec), terms);
}
else
{
return ColumnCondition.condition(receiver, collectionElement.prepare(keyspace, elementSpec), value.prepare(keyspace, valueSpec), operator);
}
}
}
}

View File

@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.MapMaker;
import org.apache.cassandra.cache.IMeasurableMemory;
@ -329,7 +330,8 @@ public class ColumnIdentifier extends Selectable implements IMeasurableMemory, C
}
}
static String maybeQuote(String text)
@VisibleForTesting
public static String maybeQuote(String text)
{
if (UNQUOTED_IDENTIFIER.matcher(text).matches())
return text;

View File

@ -162,7 +162,7 @@ public abstract class Constants
public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
CQL3Type receiverType = receiver.type.asCQL3Type();
if (receiverType.isCollection())
if (receiverType.isCollection() || receiverType.isUDT())
return AssignmentTestable.TestResult.NOT_ASSIGNABLE;
if (!(receiverType instanceof CQL3Type.Native))

View File

@ -19,6 +19,7 @@ package org.apache.cassandra.cql3;
import java.util.Collections;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.DecoratedKey;
@ -107,12 +108,10 @@ public abstract class Operation
* It returns an Operation which can be though as post-preparation well-typed
* Operation.
*
* @param receiver the "column" this operation applies to. Note that
* contrarly to the method of same name in Term.Raw, the receiver should always
* be a true column.
* @param receiver the column this operation applies to.
* @return the prepared update operation.
*/
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException;
public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException;
/**
* @return whether this operation can be applied alongside the {@code
@ -146,7 +145,7 @@ public abstract class Operation
* @param receiver the "column" this operation applies to.
* @return the prepared delete operation.
*/
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException;
public Operation prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException;
}
public static class SetValue implements RawUpdate
@ -158,26 +157,32 @@ public abstract class Operation
this.value = value;
}
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException
{
Term v = value.prepare(keyspace, receiver);
Term v = value.prepare(cfm.ksName, receiver);
if (receiver.type instanceof CounterColumnType)
throw new InvalidRequestException(String.format("Cannot set the value of counter column %s (counters can only be incremented/decremented, not set)", receiver.name));
if (!(receiver.type.isCollection()))
return new Constants.Setter(receiver, v);
switch (((CollectionType)receiver.type).kind)
if (receiver.type.isCollection())
{
case LIST:
return new Lists.Setter(receiver, v);
case SET:
return new Sets.Setter(receiver, v);
case MAP:
return new Maps.Setter(receiver, v);
switch (((CollectionType) receiver.type).kind)
{
case LIST:
return new Lists.Setter(receiver, v);
case SET:
return new Sets.Setter(receiver, v);
case MAP:
return new Maps.Setter(receiver, v);
default:
throw new AssertionError();
}
}
throw new AssertionError();
if (receiver.type.isUDT())
return new UserTypes.Setter(receiver, v);
return new Constants.Setter(receiver, v);
}
protected String toString(ColumnSpecification column)
@ -204,7 +209,7 @@ public abstract class Operation
this.value = value;
}
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException
{
if (!(receiver.type instanceof CollectionType))
throw new InvalidRequestException(String.format("Invalid operation (%s) for non collection column %s", toString(receiver), receiver.name));
@ -214,14 +219,14 @@ public abstract class Operation
switch (((CollectionType)receiver.type).kind)
{
case LIST:
Term idx = selector.prepare(keyspace, Lists.indexSpecOf(receiver));
Term lval = value.prepare(keyspace, Lists.valueSpecOf(receiver));
Term idx = selector.prepare(cfm.ksName, Lists.indexSpecOf(receiver));
Term lval = value.prepare(cfm.ksName, Lists.valueSpecOf(receiver));
return new Lists.SetterByIndex(receiver, idx, lval);
case SET:
throw new InvalidRequestException(String.format("Invalid operation (%s) for set column %s", toString(receiver), receiver.name));
case MAP:
Term key = selector.prepare(keyspace, Maps.keySpecOf(receiver));
Term mval = value.prepare(keyspace, Maps.valueSpecOf(receiver));
Term key = selector.prepare(cfm.ksName, Maps.keySpecOf(receiver));
Term mval = value.prepare(cfm.ksName, Maps.valueSpecOf(receiver));
return new Maps.SetterByKey(receiver, key, mval);
}
throw new AssertionError();
@ -240,6 +245,47 @@ public abstract class Operation
}
}
public static class SetField implements RawUpdate
{
private final ColumnIdentifier.Raw field;
private final Term.Raw value;
public SetField(ColumnIdentifier.Raw field, Term.Raw value)
{
this.field = field;
this.value = value;
}
public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException
{
if (!receiver.type.isUDT())
throw new InvalidRequestException(String.format("Invalid operation (%s) for non-UDT column %s", toString(receiver), receiver.name));
else if (!receiver.type.isMultiCell())
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen UDT column %s", toString(receiver), receiver.name));
ColumnIdentifier fieldIdentifier = field.prepare(cfm);
int fieldPosition = ((UserType) receiver.type).fieldPosition(fieldIdentifier);
if (fieldPosition == -1)
throw new InvalidRequestException(String.format("UDT column %s does not have a field named %s", receiver.name, fieldIdentifier));
Term val = value.prepare(cfm.ksName, UserTypes.fieldSpecOf(receiver, fieldPosition));
return new UserTypes.SetterByField(receiver, fieldIdentifier, val);
}
protected String toString(ColumnSpecification column)
{
return String.format("%s.%s = %s", column.name, field, value);
}
public boolean isCompatibleWith(RawUpdate other)
{
if (other instanceof SetField)
return !((SetField) other).field.equals(field);
else
return !(other instanceof SetValue);
}
}
public static class Addition implements RawUpdate
{
private final Term.Raw value;
@ -249,9 +295,9 @@ public abstract class Operation
this.value = value;
}
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException
{
Term v = value.prepare(keyspace, receiver);
Term v = value.prepare(cfm.ksName, receiver);
if (!(receiver.type instanceof CollectionType))
{
@ -294,13 +340,13 @@ public abstract class Operation
this.value = value;
}
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException
{
if (!(receiver.type instanceof CollectionType))
{
if (!(receiver.type instanceof CounterColumnType))
throw new InvalidRequestException(String.format("Invalid operation (%s) for non counter column %s", toString(receiver), receiver.name));
return new Constants.Substracter(receiver, value.prepare(keyspace, receiver));
return new Constants.Substracter(receiver, value.prepare(cfm.ksName, receiver));
}
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
@ -308,16 +354,16 @@ public abstract class Operation
switch (((CollectionType)receiver.type).kind)
{
case LIST:
return new Lists.Discarder(receiver, value.prepare(keyspace, receiver));
return new Lists.Discarder(receiver, value.prepare(cfm.ksName, receiver));
case SET:
return new Sets.Discarder(receiver, value.prepare(keyspace, receiver));
return new Sets.Discarder(receiver, value.prepare(cfm.ksName, receiver));
case MAP:
// The value for a map subtraction is actually a set
ColumnSpecification vr = new ColumnSpecification(receiver.ksName,
receiver.cfName,
receiver.name,
SetType.getInstance(((MapType)receiver.type).getKeysType(), false));
return new Sets.Discarder(receiver, value.prepare(keyspace, vr));
return new Sets.Discarder(receiver, value.prepare(cfm.ksName, vr));
}
throw new AssertionError();
}
@ -342,9 +388,9 @@ public abstract class Operation
this.value = value;
}
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
public Operation prepare(CFMetaData cfm, ColumnDefinition receiver) throws InvalidRequestException
{
Term v = value.prepare(keyspace, receiver);
Term v = value.prepare(cfm.ksName, receiver);
if (!(receiver.type instanceof ListType))
throw new InvalidRequestException(String.format("Invalid operation (%s) for non list column %s", toString(receiver), receiver.name));
@ -379,7 +425,7 @@ public abstract class Operation
return id;
}
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
public Operation prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException
{
// No validation, deleting a column is always "well typed"
return new Constants.Deleter(receiver);
@ -402,7 +448,7 @@ public abstract class Operation
return id;
}
public Operation prepare(String keyspace, ColumnDefinition receiver) throws InvalidRequestException
public Operation prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException
{
if (!(receiver.type.isCollection()))
throw new InvalidRequestException(String.format("Invalid deletion operation for non collection column %s", receiver.name));
@ -424,4 +470,35 @@ public abstract class Operation
throw new AssertionError();
}
}
public static class FieldDeletion implements RawDeletion
{
private final ColumnIdentifier.Raw id;
private final ColumnIdentifier.Raw field;
public FieldDeletion(ColumnIdentifier.Raw id, ColumnIdentifier.Raw field)
{
this.id = id;
this.field = field;
}
public ColumnIdentifier.Raw affectedColumn()
{
return id;
}
public Operation prepare(String keyspace, ColumnDefinition receiver, CFMetaData cfm) throws InvalidRequestException
{
if (!receiver.type.isUDT())
throw new InvalidRequestException(String.format("Invalid field deletion operation for non-UDT column %s", receiver.name));
else if (!receiver.type.isMultiCell())
throw new InvalidRequestException(String.format("Frozen UDT column %s does not support field deletions", receiver.name));
ColumnIdentifier fieldIdentifier = field.prepare(cfm);
if (((UserType) receiver.type).fieldPosition(fieldIdentifier) == -1)
throw new InvalidRequestException(String.format("UDT column %s does not have a field named %s", receiver.name, fieldIdentifier));
return new UserTypes.DeleterByField(receiver, fieldIdentifier);
}
}
}

View File

@ -111,8 +111,10 @@ public class Tuples
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()));
receiver.name, tt.asCQL3Type(), tt.size(), elements.size()));
}
Term.Raw value = elements.get(i);
ColumnSpecification spec = componentSpecOf(receiver, i);
@ -154,6 +156,13 @@ public class Tuples
public static Value fromSerialized(ByteBuffer bytes, TupleType type)
{
ByteBuffer[] values = type.split(bytes);
if (values.length > type.size())
{
throw new InvalidRequestException(String.format(
"Tuple value contained too many fields (expected %s, got %s)", type.size(), values.length));
}
return new Value(type.split(bytes));
}
@ -199,6 +208,10 @@ public class Tuples
private ByteBuffer[] bindInternal(QueryOptions options) throws InvalidRequestException
{
if (elements.size() > type.size())
throw new InvalidRequestException(String.format(
"Tuple value contained too many fields (expected %s, got %s)", type.size(), elements.size()));
ByteBuffer[] buffers = new ByteBuffer[elements.size()];
for (int i = 0; i < elements.size(); i++)
{

View File

@ -245,7 +245,7 @@ public abstract class UntypedResultSet implements Iterable<UntypedResultSet.Row>
{
ComplexColumnData complexData = row.getComplexColumnData(def);
if (complexData != null)
data.put(def.name.toString(), ((CollectionType)def.type).serializeForNativeProtocol(def, complexData.iterator(), Server.VERSION_3));
data.put(def.name.toString(), ((CollectionType)def.type).serializeForNativeProtocol(complexData.iterator(), Server.VERSION_3));
}
}

View File

@ -20,12 +20,18 @@ package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.TupleType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.cql3.Constants.UNSET_VALUE;
/**
* Static helper methods and classes for user types.
*/
@ -78,8 +84,10 @@ public abstract class UserTypes
{
// We had some field that are not part of the type
for (ColumnIdentifier id : entries.keySet())
{
if (!ut.fieldNames().contains(id.bytes))
throw new InvalidRequestException(String.format("Unknown field '%s' in value of user defined type %s", id, ut.getNameAsString()));
}
}
DelayedValue value = new DelayedValue(((UserType)receiver.type), values);
@ -88,7 +96,7 @@ public abstract class UserTypes
private void validateAssignableTo(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{
if (!(receiver.type instanceof UserType))
if (!receiver.type.isUDT())
throw new InvalidRequestException(String.format("Invalid user type literal for %s of type %s", receiver, receiver.type.asCQL3Type()));
UserType ut = (UserType)receiver.type;
@ -101,7 +109,10 @@ public abstract class UserTypes
ColumnSpecification fieldSpec = fieldSpecOf(receiver, i);
if (!value.testAssignment(keyspace, fieldSpec).isAssignable())
throw new InvalidRequestException(String.format("Invalid user type literal for %s: field %s is not of type %s", receiver, field, fieldSpec.type.asCQL3Type()));
{
throw new InvalidRequestException(String.format("Invalid user type literal for %s: field %s is not of type %s",
receiver, field, fieldSpec.type.asCQL3Type()));
}
}
}
@ -135,7 +146,52 @@ public abstract class UserTypes
}
}
// Same purpose than Lists.DelayedValue, except we do handle bind marker in that case
public static class Value extends Term.MultiItemTerminal
{
private final UserType type;
public final ByteBuffer[] elements;
public Value(UserType type, ByteBuffer[] elements)
{
this.type = type;
this.elements = elements;
}
public static Value fromSerialized(ByteBuffer bytes, UserType type)
{
ByteBuffer[] values = type.split(bytes);
if (values.length > type.size())
{
throw new InvalidRequestException(String.format(
"UDT value contained too many fields (expected %s, got %s)", type.size(), values.length));
}
return new Value(type, type.split(bytes));
}
public ByteBuffer get(int protocolVersion)
{
return TupleType.buildValue(elements);
}
public boolean equals(UserType userType, Value v)
{
if (elements.length != v.elements.length)
return false;
for (int i = 0; i < elements.length; i++)
if (userType.fieldType(i).compare(elements[i], v.elements[i]) != 0)
return false;
return true;
}
public List<ByteBuffer> getElements()
{
return Arrays.asList(elements);
}
}
public static class DelayedValue extends Term.NonTerminal
{
private final UserType type;
@ -168,20 +224,27 @@ public abstract class UserTypes
private ByteBuffer[] bindInternal(QueryOptions options) throws InvalidRequestException
{
if (values.size() > type.size())
{
throw new InvalidRequestException(String.format(
"UDT value contained too many fields (expected %s, got %s)", type.size(), values.size()));
}
ByteBuffer[] buffers = new ByteBuffer[values.size()];
for (int i = 0; i < type.size(); i++)
{
buffers[i] = values.get(i).bindAndGet(options);
// Since A UDT value is always written in its entirety Cassandra can't preserve a pre-existing value by 'not setting' the new value. Reject the query.
if (buffers[i] == ByteBufferUtil.UNSET_BYTE_BUFFER)
// Since a frozen UDT value is always written in its entirety Cassandra can't preserve a pre-existing
// value by 'not setting' the new value. Reject the query.
if (!type.isMultiCell() && buffers[i] == ByteBufferUtil.UNSET_BYTE_BUFFER)
throw new InvalidRequestException(String.format("Invalid unset value for field '%s' of user defined type %s", type.fieldNameAsString(i), type.getNameAsString()));
}
return buffers;
}
public Constants.Value bind(QueryOptions options) throws InvalidRequestException
public Value bind(QueryOptions options) throws InvalidRequestException
{
return new Constants.Value(bindAndGet(options));
return new Value(type, bindInternal(options));
}
@Override
@ -190,4 +253,113 @@ public abstract class UserTypes
return UserType.buildValue(bindInternal(options));
}
}
public static class Marker extends AbstractMarker
{
protected Marker(int bindIndex, ColumnSpecification receiver)
{
super(bindIndex, receiver);
assert receiver.type.isUDT();
}
public Terminal bind(QueryOptions options) throws InvalidRequestException
{
ByteBuffer value = options.getValues().get(bindIndex);
if (value == null)
return null;
if (value == ByteBufferUtil.UNSET_BYTE_BUFFER)
return UNSET_VALUE;
return Value.fromSerialized(value, (UserType) receiver.type);
}
}
public static class Setter extends Operation
{
public Setter(ColumnDefinition column, Term t)
{
super(column, t);
}
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
Term.Terminal value = t.bind(params.options);
if (value == UNSET_VALUE)
return;
Value userTypeValue = (Value) value;
if (column.type.isMultiCell())
{
// setting a whole UDT at once means we overwrite all cells, so delete existing cells
params.setComplexDeletionTimeForOverwrite(column);
if (value == null)
return;
Iterator<ByteBuffer> fieldNameIter = userTypeValue.type.fieldNames().iterator();
for (ByteBuffer buffer : userTypeValue.elements)
{
ByteBuffer fieldName = fieldNameIter.next();
if (buffer == null)
continue;
CellPath fieldPath = userTypeValue.type.cellPathForField(fieldName);
params.addCell(column, fieldPath, buffer);
}
}
else
{
// for frozen UDTs, we're overwriting the whole cell value
if (value == null)
params.addTombstone(column);
else
params.addCell(column, value.get(params.options.getProtocolVersion()));
}
}
}
public static class SetterByField extends Operation
{
private final ColumnIdentifier field;
public SetterByField(ColumnDefinition column, ColumnIdentifier field, Term t)
{
super(column, t);
this.field = field;
}
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
// we should not get here for frozen UDTs
assert column.type.isMultiCell() : "Attempted to set an individual field on a frozen UDT";
Term.Terminal value = t.bind(params.options);
if (value == UNSET_VALUE)
return;
CellPath fieldPath = ((UserType) column.type).cellPathForField(field.bytes);
if (value == null)
params.addTombstone(column, fieldPath);
else
params.addCell(column, fieldPath, value.get(params.options.getProtocolVersion()));
}
}
public static class DeleterByField extends Operation
{
private final ColumnIdentifier field;
public DeleterByField(ColumnDefinition column, ColumnIdentifier field)
{
super(column, null);
this.field = field;
}
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
// we should not get here for frozen UDTs
assert column.type.isMultiCell() : "Attempted to delete a single field from a frozen UDT";
CellPath fieldPath = ((UserType) column.type).cellPathForField(field.bytes);
params.addTombstone(column, fieldPath);
}
}
}

View File

@ -91,7 +91,7 @@ public abstract class AbstractFunction implements Function
// We should ignore the fact that the receiver type is frozen in our comparison as functions do not support
// frozen types for return type
AbstractType<?> returnType = returnType();
if (receiver.type.isFrozenCollection())
if (receiver.type.isFreezable() && !receiver.type.isMultiCell())
returnType = returnType.freeze();
if (receiver.type.equals(returnType))

View File

@ -99,16 +99,24 @@ public class FunctionCall extends Term.NonTerminal
private static Term.Terminal makeTerminal(Function fun, ByteBuffer result, int version) throws InvalidRequestException
{
if (!(fun.returnType() instanceof CollectionType))
return new Constants.Value(result);
switch (((CollectionType)fun.returnType()).kind)
if (fun.returnType().isCollection())
{
case LIST: return Lists.Value.fromSerialized(result, (ListType)fun.returnType(), version);
case SET: return Sets.Value.fromSerialized(result, (SetType)fun.returnType(), version);
case MAP: return Maps.Value.fromSerialized(result, (MapType)fun.returnType(), version);
switch (((CollectionType) fun.returnType()).kind)
{
case LIST:
return Lists.Value.fromSerialized(result, (ListType) fun.returnType(), version);
case SET:
return Sets.Value.fromSerialized(result, (SetType) fun.returnType(), version);
case MAP:
return Maps.Value.fromSerialized(result, (MapType) fun.returnType(), version);
}
}
throw new AssertionError();
else if (fun.returnType().isUDT())
{
return UserTypes.Value.fromSerialized(result, (UserType) fun.returnType());
}
return new Constants.Value(result);
}
public static class Raw extends Term.Raw

View File

@ -223,7 +223,7 @@ public class UDAggregate extends AbstractFunction implements AggregateFunction
&& Functions.typesMatch(returnType, that.returnType)
&& Objects.equal(stateFunction, that.stateFunction)
&& Objects.equal(finalFunction, that.finalFunction)
&& Objects.equal(stateType, that.stateType)
&& ((stateType == that.stateType) || ((stateType != null) && stateType.equals(that.stateType, true))) // ignore freezing
&& Objects.equal(initcond, that.initcond);
}

View File

@ -124,7 +124,7 @@ public final class StatementRestrictions
WhereClause whereClause,
VariableSpecifications boundNames,
boolean selectsOnlyStaticColumns,
boolean selectACollection,
boolean selectsComplexColumn,
boolean useFiltering,
boolean forView) throws InvalidRequestException
{
@ -218,7 +218,7 @@ public final class StatementRestrictions
throw invalidRequest("Cannot restrict clustering columns when selecting only static columns");
}
processClusteringColumnsRestrictions(hasQueriableIndex, selectsOnlyStaticColumns, selectACollection, forView);
processClusteringColumnsRestrictions(hasQueriableIndex, selectsOnlyStaticColumns, selectsComplexColumn, forView);
// Covers indexes on the first clustering column (among others).
if (isKeyRange && hasQueriableClusteringColumnIndex)
@ -447,11 +447,11 @@ public final class StatementRestrictions
* @param hasQueriableIndex <code>true</code> if some of the queried data are indexed, <code>false</code> otherwise
* @param selectsOnlyStaticColumns <code>true</code> if the selected or modified columns are all statics,
* <code>false</code> otherwise.
* @param selectACollection <code>true</code> if the query should return a collection column
* @param selectsComplexColumn <code>true</code> if the query should return a collection column
*/
private void processClusteringColumnsRestrictions(boolean hasQueriableIndex,
boolean selectsOnlyStaticColumns,
boolean selectACollection,
boolean selectsComplexColumn,
boolean forView) throws InvalidRequestException
{
checkFalse(!type.allowClusteringColumnSlices() && clusteringColumnsRestrictions.isSlice(),
@ -466,7 +466,7 @@ public final class StatementRestrictions
}
else
{
checkFalse(clusteringColumnsRestrictions.isIN() && selectACollection,
checkFalse(clusteringColumnsRestrictions.isIN() && selectsComplexColumn,
"Cannot restrict clustering columns by IN relations when a collection is selected by the query");
checkFalse(clusteringColumnsRestrictions.isContains() && !hasQueriableIndex,
"Cannot restrict clustering columns by a CONTAINS relation without a secondary index");

View File

@ -277,23 +277,23 @@ public abstract class Selectable
{
Selector.Factory factory = selected.newSelectorFactory(cfm, defs);
AbstractType<?> type = factory.newInstance().getType();
if (!(type instanceof UserType))
if (!type.isUDT())
{
throw new InvalidRequestException(
String.format("Invalid field selection: %s of type %s is not a user type",
selected,
type.asCQL3Type()));
selected,
type.asCQL3Type()));
}
UserType ut = (UserType) type;
for (int i = 0; i < ut.size(); i++)
int fieldIndex = ((UserType) type).fieldPosition(field);
if (fieldIndex == -1)
{
if (!ut.fieldName(i).equals(field.bytes))
continue;
return FieldSelector.newFactory(ut, i, factory);
throw new InvalidRequestException(String.format("%s of type %s has no field %s",
selected, type.asCQL3Type(), field));
}
throw new InvalidRequestException(String.format("%s of type %s has no field %s",
selected,
type.asCQL3Type(),
field));
return FieldSelector.newFactory(ut, fieldIndex, factory);
}
public static class Raw implements Selectable.Raw

View File

@ -112,14 +112,14 @@ public abstract class Selection
}
/**
* Checks if this selection contains a collection.
* Checks if this selection contains a complex column.
*
* @return <code>true</code> if this selection contains a collection, <code>false</code> otherwise.
* @return <code>true</code> if this selection contains a multicell collection or UDT, <code>false</code> otherwise.
*/
public boolean containsACollection()
public boolean containsAComplexColumn()
{
for (ColumnDefinition def : getColumns())
if (def.type.isCollection() && def.type.isMultiCell())
if (def.isComplex())
return true;
return false;

View File

@ -190,7 +190,7 @@ public abstract class Selector implements AssignmentTestable
// We should ignore the fact that the output type is frozen in our comparison as functions do not support
// frozen types for arguments
AbstractType<?> receiverType = receiver.type;
if (getType().isFrozenCollection())
if (getType().isFreezable() && !getType().isMultiCell())
receiverType = receiverType.freeze();
if (getType().isReversed())

View File

@ -166,11 +166,11 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
// If it's directly the type we've updated, then just use the new one.
if (keyspace.equals(ut.keyspace) && toReplace.equals(ut.name))
return updated;
return type.isMultiCell() ? updated : updated.freeze();
// Otherwise, check for nesting
List<AbstractType<?>> updatedTypes = updateTypes(ut.fieldTypes(), keyspace, toReplace, updated);
return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.fieldNames()), updatedTypes);
return updatedTypes == null ? null : new UserType(ut.keyspace, ut.name, new ArrayList<>(ut.fieldNames()), updatedTypes, type.isMultiCell());
}
else if (type instanceof TupleType)
{
@ -275,7 +275,7 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
newTypes.addAll(toUpdate.fieldTypes());
newTypes.add(addType);
return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes);
return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes, toUpdate.isMultiCell());
}
private UserType doAlter(UserType toUpdate, KeyspaceMetadata ksm) throws InvalidRequestException
@ -294,7 +294,7 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
List<AbstractType<?>> newTypes = new ArrayList<>(toUpdate.fieldTypes());
newTypes.set(idx, type.prepare(keyspace()).getType());
return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes);
return new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes, toUpdate.isMultiCell());
}
protected UserType makeUpdatedType(UserType toUpdate, KeyspaceMetadata ksm) throws InvalidRequestException
@ -330,7 +330,7 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement
newNames.set(idx, to.bytes);
}
UserType updated = new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes);
UserType updated = new UserType(toUpdate.keyspace, toUpdate.name, newNames, newTypes, toUpdate.isMultiCell());
CreateTypeStatement.checkForDuplicateNames(updated);
return updated;
}

View File

@ -46,7 +46,7 @@ public class CreateTableStatement extends SchemaAlteringStatement
private List<AbstractType<?>> keyTypes;
private List<AbstractType<?>> clusteringTypes;
private final Map<ByteBuffer, CollectionType> collections = new HashMap<>();
private final Map<ByteBuffer, AbstractType> multicellColumns = new HashMap<>();
private final List<ColumnIdentifier> keyAliases = new ArrayList<>();
private final List<ColumnIdentifier> columnAliases = new ArrayList<>();
@ -223,10 +223,24 @@ public class CreateTableStatement extends SchemaAlteringStatement
{
ColumnIdentifier id = entry.getKey();
CQL3Type pt = entry.getValue().prepare(keyspace(), udts);
if (pt.isCollection() && ((CollectionType)pt.getType()).isMultiCell())
stmt.collections.put(id.bytes, (CollectionType)pt.getType());
if (pt.getType().isMultiCell())
stmt.multicellColumns.put(id.bytes, pt.getType());
if (entry.getValue().isCounter())
stmt.hasCounters = true;
// check for non-frozen UDTs or collections in a non-frozen UDT
if (pt.getType().isUDT() && pt.getType().isMultiCell())
{
for (AbstractType<?> innerType : ((UserType) pt.getType()).fieldTypes())
{
if (innerType.isMultiCell())
{
assert innerType.isCollection(); // shouldn't get this far with a nested non-frozen UDT
throw new InvalidRequestException("Non-frozen UDTs with nested non-frozen collections are not supported");
}
}
}
stmt.columns.put(id, pt.getType()); // we'll remove what is not a column below
}
@ -285,8 +299,8 @@ public class CreateTableStatement extends SchemaAlteringStatement
// For COMPACT STORAGE, we reject any "feature" that we wouldn't be able to translate back to thrift.
if (useCompactStorage)
{
if (!stmt.collections.isEmpty())
throw new InvalidRequestException("Non-frozen collection types are not supported with COMPACT STORAGE");
if (!stmt.multicellColumns.isEmpty())
throw new InvalidRequestException("Non-frozen collections and UDTs are not supported with COMPACT STORAGE");
if (!staticColumns.isEmpty())
throw new InvalidRequestException("Static columns are not supported in COMPACT STORAGE tables");
@ -350,8 +364,13 @@ public class CreateTableStatement extends SchemaAlteringStatement
AbstractType type = columns.get(t);
if (type == null)
throw new InvalidRequestException(String.format("Unknown definition %s referenced in PRIMARY KEY", t));
if (type.isCollection() && type.isMultiCell())
throw new InvalidRequestException(String.format("Invalid collection type for PRIMARY KEY component %s", t));
if (type.isMultiCell())
{
if (type.isCollection())
throw new InvalidRequestException(String.format("Invalid non-frozen collection type for PRIMARY KEY component %s", t));
else
throw new InvalidRequestException(String.format("Invalid non-frozen user-defined type for PRIMARY KEY component %s", t));
}
columns.remove(t);
Boolean isReversed = properties.definedOrdering.get(t);

View File

@ -74,8 +74,12 @@ public class CreateTypeStatement extends SchemaAlteringStatement
throw new InvalidRequestException(String.format("A user type of name %s already exists", name));
for (CQL3Type.Raw type : columnTypes)
{
if (type.isCounter())
throw new InvalidRequestException("A user type cannot contain counters");
if (type.isUDT() && !type.isFrozen())
throw new InvalidRequestException("A user type cannot contain non-frozen UDTs");
}
}
public static void checkForDuplicateNames(UserType type) throws InvalidRequestException
@ -109,7 +113,7 @@ public class CreateTypeStatement extends SchemaAlteringStatement
for (CQL3Type.Raw type : columnTypes)
types.add(type.prepare(keyspace()).getType());
return new UserType(name.getKeyspace(), name.getUserTypeName(), names, types);
return new UserType(name.getKeyspace(), name.getUserTypeName(), names, types, true);
}
public Event.SchemaChange announceMigration(boolean isLocalOnly) throws InvalidRequestException, ConfigurationException

View File

@ -147,7 +147,7 @@ public class DeleteStatement extends ModificationStatement
// list. However, we support having the value name for coherence with the static/sparse case
checkFalse(def.isPrimaryKeyColumn(), "Invalid identifier %s for deletion (should not be a PRIMARY KEY part)", def.name);
Operation op = deletion.prepare(cfm.ksName, def);
Operation op = deletion.prepare(cfm.ksName, def, cfm);
op.collectMarkerSpecification(boundNames);
operations.add(op);
}

View File

@ -831,7 +831,7 @@ public abstract class ModificationStatement implements CQLStatement
ColumnDefinition def = metadata.getColumnDefinition(id);
checkNotNull(metadata.getColumnDefinition(id), "Unknown identifier %s in IF conditions", id);
ColumnCondition condition = entry.right.prepare(keyspace(), def);
ColumnCondition condition = entry.right.prepare(keyspace(), def, metadata);
condition.collectMarkerSpecification(boundNames);
checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY column '%s' cannot have IF conditions", id);

View File

@ -39,6 +39,7 @@ import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.db.rows.Row;
@ -759,13 +760,14 @@ public class SelectStatement implements CQLStatement
{
if (def.isComplex())
{
// Collections are the only complex types we have so far
assert def.type.isCollection() && def.type.isMultiCell();
assert def.type.isMultiCell();
ComplexColumnData complexData = row.getComplexColumnData(def);
if (complexData == null)
result.add((ByteBuffer)null);
result.add(null);
else if (def.type.isCollection())
result.add(((CollectionType) def.type).serializeForNativeProtocol(complexData.iterator(), protocolVersion));
else
result.add(((CollectionType)def.type).serializeForNativeProtocol(def, complexData.iterator(), protocolVersion));
result.add(((UserType) def.type).serializeForNativeProtocol(complexData.iterator(), protocolVersion));
}
else
{
@ -883,7 +885,7 @@ public class SelectStatement implements CQLStatement
whereClause,
boundNames,
selection.containsOnlyStaticColumns(),
selection.containsACollection(),
selection.containsAComplexColumn(),
parameters.allowFiltering,
forView);
}

View File

@ -170,7 +170,7 @@ public class UpdateStatement extends ModificationStatement
}
else
{
Operation operation = new Operation.SetValue(value).prepare(keyspace(), def);
Operation operation = new Operation.SetValue(value).prepare(cfm, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}
@ -239,7 +239,7 @@ public class UpdateStatement extends ModificationStatement
}
else
{
Operation operation = new Operation.SetValue(raw).prepare(keyspace(), def);
Operation operation = new Operation.SetValue(raw).prepare(cfm, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}
@ -308,7 +308,7 @@ public class UpdateStatement extends ModificationStatement
checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY part %s found in SET part", def.name);
Operation operation = entry.right.prepare(keyspace(), def);
Operation operation = entry.right.prepare(cfm, def);
operation.collectMarkerSpecification(boundNames);
operations.add(operation);
}

View File

@ -312,11 +312,21 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>
return false;
}
public boolean isUDT()
{
return false;
}
public boolean isMultiCell()
{
return false;
}
public boolean isFreezable()
{
return false;
}
public AbstractType<?> freeze()
{
return this;
@ -418,6 +428,17 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>
return getClass().getName();
}
/**
* Checks to see if two types are equal when ignoring or not ignoring differences in being frozen, depending on
* the value of the ignoreFreezing parameter.
* @param other type to compare
* @param ignoreFreezing if true, differences in the types being frozen will be ignored
*/
public boolean equals(Object other, boolean ignoreFreezing)
{
return this.equals(other);
}
public void checkComparable()
{
switch (comparisonType)

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import java.util.List;
import java.util.Iterator;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.Lists;
@ -133,13 +132,19 @@ public abstract class CollectionType<T> extends AbstractType<T>
return kind == Kind.MAP;
}
@Override
public boolean isFreezable()
{
return true;
}
// Overrided by maps
protected int collectionSize(List<ByteBuffer> values)
{
return values.size();
}
public ByteBuffer serializeForNativeProtocol(ColumnDefinition def, Iterator<Cell> cells, int version)
public ByteBuffer serializeForNativeProtocol(Iterator<Cell> cells, int version)
{
assert isMultiCell();
List<ByteBuffer> values = serializedValues(cells);
@ -203,6 +208,27 @@ public abstract class CollectionType<T> extends AbstractType<T>
return new CQL3Type.Collection(this);
}
@Override
public boolean equals(Object o, boolean ignoreFreezing)
{
if (this == o)
return true;
if (!(o instanceof CollectionType))
return false;
CollectionType other = (CollectionType)o;
if (kind != other.kind)
return false;
if (!ignoreFreezing && isMultiCell() != other.isMultiCell())
return false;
return nameComparator().equals(other.nameComparator(), ignoreFreezing) &&
valueComparator().equals(other.valueComparator(), ignoreFreezing);
}
@Override
public String toString()
{

View File

@ -23,11 +23,13 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.google.common.base.Objects;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.*;
import org.apache.cassandra.utils.ByteBufferUtil;
@ -50,11 +52,17 @@ public class TupleType extends AbstractType<ByteBuffer>
protected final List<AbstractType<?>> types;
public TupleType(List<AbstractType<?>> types)
{
this(types, true);
}
protected TupleType(List<AbstractType<?>> types, boolean freezeInner)
{
super(ComparisonType.CUSTOM);
for (int i = 0; i < types.size(); i++)
types.set(i, types.get(i).freeze());
this.types = types;
if (freezeInner)
this.types = types.stream().map(AbstractType::freeze).collect(Collectors.toList());
else
this.types = types;
}
public static TupleType getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
@ -118,11 +126,22 @@ public class TupleType extends AbstractType<ByteBuffer>
return cmp;
}
if (bb1.remaining() == 0)
return bb2.remaining() == 0 ? 0 : -1;
// handle trailing nulls
while (bb1.remaining() > 0)
{
int size = bb1.getInt();
if (size > 0) // non-null
return 1;
}
// bb1.remaining() > 0 && bb2.remaining() == 0
return 1;
while (bb2.remaining() > 0)
{
int size = bb2.getInt();
if (size > 0) // non-null
return -1;
}
return 0;
}
@Override
@ -171,6 +190,15 @@ public class TupleType extends AbstractType<ByteBuffer>
int size = input.getInt();
components[i] = size < 0 ? null : ByteBufferUtil.readBytes(input, size);
}
// error out if we got more values in the tuple/UDT than we expected
if (input.hasRemaining())
{
throw new InvalidRequestException(String.format(
"Expected %s %s for %s column, but got more",
size(), size() == 1 ? "value" : "values", this.asCQL3Type()));
}
return components;
}
@ -200,6 +228,9 @@ public class TupleType extends AbstractType<ByteBuffer>
@Override
public String getString(ByteBuffer value)
{
if (value == null)
return "null";
StringBuilder sb = new StringBuilder();
ByteBuffer input = value.duplicate();
for (int i = 0; i < size(); i++)

View File

@ -538,7 +538,8 @@ public class TypeParser
return sb.toString();
}
public static String stringifyUserTypeParameters(String keysace, ByteBuffer typeName, List<ByteBuffer> columnNames, List<AbstractType<?>> columnTypes)
public static String stringifyUserTypeParameters(String keysace, ByteBuffer typeName, List<ByteBuffer> columnNames,
List<AbstractType<?>> columnTypes, boolean ignoreFreezing)
{
StringBuilder sb = new StringBuilder();
sb.append('(').append(keysace).append(",").append(ByteBufferUtil.bytesToHex(typeName));
@ -547,8 +548,7 @@ public class TypeParser
{
sb.append(',');
sb.append(ByteBufferUtil.bytesToHex(columnNames.get(i))).append(":");
// omit FrozenType(...) from fields because it is currently implicit
sb.append(columnTypes.get(i).toString(true));
sb.append(columnTypes.get(i).toString(ignoreFreezing));
}
sb.append(')');
return sb.toString();

View File

@ -25,11 +25,15 @@ import java.util.*;
import com.google.common.base.Objects;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A user defined type.
@ -38,19 +42,24 @@ import org.apache.cassandra.utils.Pair;
*/
public class UserType extends TupleType
{
private static final Logger logger = LoggerFactory.getLogger(UserType.class);
public final String keyspace;
public final ByteBuffer name;
private final List<ByteBuffer> fieldNames;
private final List<String> stringFieldNames;
private final boolean isMultiCell;
public UserType(String keyspace, ByteBuffer name, List<ByteBuffer> fieldNames, List<AbstractType<?>> fieldTypes)
public UserType(String keyspace, ByteBuffer name, List<ByteBuffer> fieldNames, List<AbstractType<?>> fieldTypes, boolean isMultiCell)
{
super(fieldTypes);
super(fieldTypes, false);
assert fieldNames.size() == fieldTypes.size();
this.keyspace = keyspace;
this.name = name;
this.fieldNames = fieldNames;
this.stringFieldNames = new ArrayList<>(fieldNames.size());
this.isMultiCell = isMultiCell;
for (ByteBuffer fieldName : fieldNames)
{
try
@ -74,9 +83,28 @@ public class UserType extends TupleType
for (Pair<ByteBuffer, AbstractType> p : params.right)
{
columnNames.add(p.left);
columnTypes.add(p.right.freeze());
columnTypes.add(p.right);
}
return new UserType(keyspace, name, columnNames, columnTypes);
return new UserType(keyspace, name, columnNames, columnTypes, true);
}
@Override
public boolean isUDT()
{
return true;
}
@Override
public boolean isMultiCell()
{
return isMultiCell;
}
@Override
public boolean isFreezable()
{
return true;
}
public AbstractType<?> fieldType(int i)
@ -109,6 +137,55 @@ public class UserType extends TupleType
return UTF8Type.instance.compose(name);
}
public short fieldPosition(ColumnIdentifier field)
{
return fieldPosition(field.bytes);
}
public short fieldPosition(ByteBuffer fieldName)
{
for (short i = 0; i < fieldNames.size(); i++)
if (fieldName.equals(fieldNames.get(i)))
return i;
return -1;
}
public CellPath cellPathForField(ByteBuffer fieldName)
{
// we use the field position instead of the field name to allow for field renaming in ALTER TYPE statements
return CellPath.create(ByteBufferUtil.bytes(fieldPosition(fieldName)));
}
public ShortType nameComparator()
{
return ShortType.instance;
}
public ByteBuffer serializeForNativeProtocol(Iterator<Cell> cells, int protocolVersion)
{
assert isMultiCell;
ByteBuffer[] components = new ByteBuffer[size()];
short fieldPosition = 0;
while (cells.hasNext())
{
Cell cell = cells.next();
// handle null fields that aren't at the end
short fieldPositionOfCell = ByteBufferUtil.toShort(cell.path().get(0));
while (fieldPosition < fieldPositionOfCell)
components[fieldPosition++] = null;
components[fieldPosition++] = cell.value();
}
// append trailing nulls for missing cells
while (fieldPosition < size())
components[fieldPosition++] = null;
return TupleType.buildValue(components);
}
// 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
@ -216,20 +293,80 @@ public class UserType extends TupleType
return sb.append("}").toString();
}
@Override
public UserType freeze()
{
if (isMultiCell)
return new UserType(keyspace, name, fieldNames, fieldTypes(), false);
else
return this;
}
@Override
public int hashCode()
{
return Objects.hashCode(keyspace, name, fieldNames, types);
return Objects.hashCode(keyspace, name, fieldNames, types, isMultiCell);
}
@Override
public boolean isValueCompatibleWith(AbstractType<?> previous)
{
if (this == previous)
return true;
if (!(previous instanceof UserType))
return false;
UserType other = (UserType) previous;
if (isMultiCell != other.isMultiCell())
return false;
if (!keyspace.equals(other.keyspace))
return false;
Iterator<AbstractType<?>> thisTypeIter = types.iterator();
Iterator<AbstractType<?>> previousTypeIter = other.types.iterator();
while (thisTypeIter.hasNext() && previousTypeIter.hasNext())
{
if (!thisTypeIter.next().isCompatibleWith(previousTypeIter.next()))
return false;
}
// it's okay for the new type to have additional fields, but not for the old type to have additional fields
return !previousTypeIter.hasNext();
}
@Override
public boolean equals(Object o)
{
return o instanceof UserType && equals(o, false);
}
@Override
public boolean equals(Object o, boolean ignoreFreezing)
{
if(!(o instanceof UserType))
return false;
UserType that = (UserType)o;
return keyspace.equals(that.keyspace) && name.equals(that.name) && fieldNames.equals(that.fieldNames) && types.equals(that.types);
if (!keyspace.equals(that.keyspace) || !name.equals(that.name) || !fieldNames.equals(that.fieldNames))
return false;
if (!ignoreFreezing && isMultiCell != that.isMultiCell)
return false;
if (this.types.size() != that.types.size())
return false;
Iterator<AbstractType<?>> otherTypeIter = that.types.iterator();
for (AbstractType<?> type : types)
{
if (!type.equals(otherTypeIter.next(), ignoreFreezing))
return false;
}
return true;
}
@Override
@ -248,6 +385,21 @@ public class UserType extends TupleType
@Override
public String toString()
{
return getClass().getName() + TypeParser.stringifyUserTypeParameters(keyspace, name, fieldNames, types);
return this.toString(false);
}
@Override
public String toString(boolean ignoreFreezing)
{
boolean includeFrozenType = !ignoreFreezing && !isMultiCell();
StringBuilder sb = new StringBuilder();
if (includeFrozenType)
sb.append(FrozenType.class.getName()).append("(");
sb.append(getClass().getName());
sb.append(TypeParser.stringifyUserTypeParameters(keyspace, name, fieldNames, types, ignoreFreezing || !isMultiCell));
if (includeFrozenType)
sb.append(")");
return sb.toString();
}
}

View File

@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Objects;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.utils.ByteBufferUtil;
@ -39,11 +40,11 @@ public abstract class CellPath
public abstract int size();
public abstract ByteBuffer get(int i);
// The only complex we currently have are collections that have only one value.
// The only complex paths we currently have are collections and UDTs, which both have a depth of one
public static CellPath create(ByteBuffer value)
{
assert value != null;
return new CollectionCellPath(value);
return new SingleItemCellPath(value);
}
public int dataSize()
@ -98,13 +99,13 @@ public abstract class CellPath
public void skip(DataInputPlus in) throws IOException;
}
private static class CollectionCellPath extends CellPath
private static class SingleItemCellPath extends CellPath
{
private static final long EMPTY_SIZE = ObjectSizes.measure(new CollectionCellPath(ByteBufferUtil.EMPTY_BYTE_BUFFER));
private static final long EMPTY_SIZE = ObjectSizes.measure(new SingleItemCellPath(ByteBufferUtil.EMPTY_BYTE_BUFFER));
protected final ByteBuffer value;
private CollectionCellPath(ByteBuffer value)
private SingleItemCellPath(ByteBuffer value)
{
this.value = value;
}
@ -122,7 +123,7 @@ public abstract class CellPath
public CellPath copy(AbstractAllocator allocator)
{
return new CollectionCellPath(allocator.clone(value));
return new SingleItemCellPath(allocator.clone(value));
}
public long unsharedHeapSizeExcludingData()

View File

@ -251,7 +251,6 @@ public class ComplexColumnData extends ColumnData implements Iterable<Cell>
public void addCell(Cell cell)
{
assert cell.column().equals(column);
builder.add(cell);
}

View File

@ -131,7 +131,7 @@ public final class Functions implements Iterable<Function>
*/
public static boolean typesMatch(AbstractType<?> t1, AbstractType<?> t2)
{
return t1.asCQL3Type().toString().equals(t2.asCQL3Type().toString());
return t1.freeze().asCQL3Type().toString().equals(t2.freeze().asCQL3Type().toString());
}
public static boolean typesMatch(List<AbstractType<?>> t1, List<AbstractType<?>> t2)

View File

@ -827,7 +827,7 @@ public final class LegacySchemaMigrator
.map(LegacySchemaMigrator::parseType)
.collect(Collectors.toList());
return new UserType(keyspaceName, bytes(typeName), names, types);
return new UserType(keyspaceName, bytes(typeName), names, types, true);
}
/*

View File

@ -22,11 +22,7 @@ import java.util.*;
import javax.annotation.Nullable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.*;
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.db.marshal.AbstractType;
@ -139,7 +135,30 @@ public final class Types implements Iterable<UserType>
@Override
public boolean equals(Object o)
{
return this == o || (o instanceof Types && types.equals(((Types) o).types));
if (this == o)
return true;
if (!(o instanceof Types))
return false;
Types other = (Types) o;
if (types.size() != other.types.size())
return false;
Iterator<Map.Entry<ByteBuffer, UserType>> thisIter = this.types.entrySet().iterator();
Iterator<Map.Entry<ByteBuffer, UserType>> otherIter = other.types.entrySet().iterator();
while (thisIter.hasNext())
{
Map.Entry<ByteBuffer, UserType> thisNext = thisIter.next();
Map.Entry<ByteBuffer, UserType> otherNext = otherIter.next();
if (!thisNext.getKey().equals(otherNext.getKey()))
return false;
if (!thisNext.getValue().equals(otherNext.getValue(), true)) // ignore freezing
return false;
}
return true;
}
@Override
@ -156,7 +175,7 @@ public final class Types implements Iterable<UserType>
public static final class Builder
{
final ImmutableMap.Builder<ByteBuffer, UserType> types = ImmutableMap.builder();
final ImmutableSortedMap.Builder<ByteBuffer, UserType> types = ImmutableSortedMap.naturalOrder();
private Builder()
{
@ -169,6 +188,7 @@ public final class Types implements Iterable<UserType>
public Builder add(UserType type)
{
assert type.isMultiCell();
types.put(type.name, type);
return this;
}
@ -293,7 +313,7 @@ public final class Types implements Iterable<UserType>
.map(t -> t.prepareInternal(keyspace, types).getType())
.collect(toList());
return new UserType(keyspace, bytes(name), preparedFieldNames, preparedFieldTypes);
return new UserType(keyspace, bytes(name), preparedFieldNames, preparedFieldTypes, true);
}
@Override

View File

@ -431,6 +431,7 @@ public class MigrationManager
public static void announceTypeUpdate(UserType updatedType, boolean announceLocally)
{
logger.info(String.format("Update type '%s.%s' to %s", updatedType.keyspace, updatedType.getNameAsString(), updatedType));
announceNewType(updatedType, announceLocally);
}

View File

@ -116,7 +116,7 @@ public enum DataType implements OptionCodec.Codecable<DataType>
fieldNames.add(UTF8Type.instance.decompose(CBUtil.readString(cb)));
fieldTypes.add(DataType.toType(codec.decodeOne(cb, version)));
}
return new UserType(ks, name, fieldNames, fieldTypes);
return new UserType(ks, name, fieldNames, fieldTypes, true);
case TUPLE:
n = cb.readUnsignedShort();
List<AbstractType<?>> types = new ArrayList<>(n);

View File

@ -636,7 +636,7 @@ public class CQL3TypeLiteralTest
names.add(UTF8Type.instance.fromString('f' + randLetters(i)));
types.add(randomNestedType(level));
}
return new UserType("ks", UTF8Type.instance.fromString("u" + randInt(1000000)), names, types);
return new UserType("ks", UTF8Type.instance.fromString("u" + randInt(1000000)), names, types, true);
}
//

View File

@ -139,6 +139,11 @@ public abstract class CQLTester
private boolean usePrepared = USE_PREPARED_VALUES;
private static boolean reusePrepared = REUSE_PREPARED;
protected boolean usePrepared()
{
return usePrepared;
}
public static void prepareServer()
{
if (isServerPrepared)
@ -1117,6 +1122,24 @@ public abstract class CQLTester
e.getMessage().contains(text));
}
@FunctionalInterface
public interface CheckedFunction {
void apply() throws Throwable;
}
/**
* Runs the given function before and after a flush of sstables. This is useful for checking that behavior is
* the same whether data is in memtables or sstables.
* @param runnable
* @throws Throwable
*/
public void beforeAndAfterFlush(CheckedFunction runnable) throws Throwable
{
runnable.apply();
flush();
runnable.apply();
}
private static String replaceValues(String query, Object[] values)
{
StringBuilder sb = new StringBuilder();
@ -1327,7 +1350,7 @@ public abstract class CQLTester
if (value instanceof ByteBuffer)
return (ByteBuffer)value;
return type.decompose(value);
return type.decompose(serializeTuples(value));
}
private static String formatValue(ByteBuffer bb, AbstractType<?> type)
@ -1354,7 +1377,19 @@ public abstract class CQLTester
protected Object userType(Object... values)
{
return new TupleValue(values).toByteBuffer();
if (values.length % 2 != 0)
throw new IllegalArgumentException("userType() requires an even number of arguments");
String[] fieldNames = new String[values.length / 2];
Object[] fieldValues = new Object[values.length / 2];
int fieldNum = 0;
for (int i = 0; i < values.length; i += 2)
{
fieldNames[fieldNum] = (String) values[i];
fieldValues[fieldNum] = values[i + 1];
fieldNum++;
}
return new UserTypeValue(fieldNames, fieldValues);
}
protected Object list(Object...values)
@ -1468,7 +1503,7 @@ public abstract class CQLTester
private static class TupleValue
{
private final Object[] values;
protected final Object[] values;
TupleValue(Object[] values)
{
@ -1502,4 +1537,43 @@ public abstract class CQLTester
return "TupleValue" + toCQLString();
}
}
private static class UserTypeValue extends TupleValue
{
private final String[] fieldNames;
UserTypeValue(String[] fieldNames, Object[] fieldValues)
{
super(fieldValues);
this.fieldNames = fieldNames;
}
@Override
public String toCQLString()
{
StringBuilder sb = new StringBuilder();
sb.append("{");
boolean haveEntry = false;
for (int i = 0; i < values.length; i++)
{
if (values[i] != null)
{
if (haveEntry)
sb.append(", ");
sb.append(ColumnIdentifier.maybeQuote(fieldNames[i]));
sb.append(": ");
sb.append(formatForCQL(values[i]));
haveEntry = true;
}
}
assert haveEntry;
sb.append("}");
return sb.toString();
}
public String toString()
{
return "UserTypeValue" + toCQLString();
}
}
}

View File

@ -186,7 +186,7 @@ public class ColumnConditionTest
ColumnDefinition definition = ColumnDefinition.regularDef("ks", "cf", "c", ListType.getInstance(Int32Type.instance, true));
// EQ
ColumnCondition condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.EQ);
ColumnCondition condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.EQ);
ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(listAppliesTo(bound, list(ONE), list(ONE)));
assertTrue(listAppliesTo(bound, list(), list()));
@ -202,7 +202,7 @@ public class ColumnConditionTest
assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// NEQ
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.NEQ);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.NEQ);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(listAppliesTo(bound, list(ONE), list(ONE)));
assertFalse(listAppliesTo(bound, list(), list()));
@ -218,7 +218,7 @@ public class ColumnConditionTest
assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// LT
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.LT);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.LT);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(listAppliesTo(bound, list(ONE), list(ONE)));
assertFalse(listAppliesTo(bound, list(), list()));
@ -234,7 +234,7 @@ public class ColumnConditionTest
assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// LTE
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.LTE);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.LTE);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(listAppliesTo(bound, list(ONE), list(ONE)));
assertTrue(listAppliesTo(bound, list(), list()));
@ -250,7 +250,7 @@ public class ColumnConditionTest
assertTrue(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// GT
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.GT);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.GT);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(listAppliesTo(bound, list(ONE), list(ONE)));
assertFalse(listAppliesTo(bound, list(), list()));
@ -266,7 +266,7 @@ public class ColumnConditionTest
assertFalse(listAppliesTo(bound, list(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// GTE
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.GTE);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.GTE);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(listAppliesTo(bound, list(ONE), list(ONE)));
assertTrue(listAppliesTo(bound, list(), list()));
@ -315,7 +315,7 @@ public class ColumnConditionTest
ColumnDefinition definition = ColumnDefinition.regularDef("ks", "cf", "c", ListType.getInstance(Int32Type.instance, true));
// EQ
ColumnCondition condition = ColumnCondition.condition(definition, null, new Sets.Value(set(ONE)), Operator.EQ);
ColumnCondition condition = ColumnCondition.condition(definition, new Sets.Value(set(ONE)), Operator.EQ);
ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(setAppliesTo(bound, set(ONE), list(ONE)));
assertTrue(setAppliesTo(bound, set(), list()));
@ -331,7 +331,7 @@ public class ColumnConditionTest
assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// NEQ
condition = ColumnCondition.condition(definition, null, new Sets.Value(set(ONE)), Operator.NEQ);
condition = ColumnCondition.condition(definition, new Sets.Value(set(ONE)), Operator.NEQ);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(setAppliesTo(bound, set(ONE), list(ONE)));
assertFalse(setAppliesTo(bound, set(), list()));
@ -347,7 +347,7 @@ public class ColumnConditionTest
assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// LT
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.LT);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.LT);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(setAppliesTo(bound, set(ONE), list(ONE)));
assertFalse(setAppliesTo(bound, set(), list()));
@ -363,7 +363,7 @@ public class ColumnConditionTest
assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// LTE
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.LTE);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.LTE);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(setAppliesTo(bound, set(ONE), list(ONE)));
assertTrue(setAppliesTo(bound, set(), list()));
@ -379,7 +379,7 @@ public class ColumnConditionTest
assertTrue(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// GT
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.GT);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.GT);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(setAppliesTo(bound, set(ONE), list(ONE)));
assertFalse(setAppliesTo(bound, set(), list()));
@ -395,7 +395,7 @@ public class ColumnConditionTest
assertFalse(setAppliesTo(bound, set(ByteBufferUtil.EMPTY_BYTE_BUFFER), list(ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// GTE
condition = ColumnCondition.condition(definition, null, new Lists.Value(Arrays.asList(ONE)), Operator.GTE);
condition = ColumnCondition.condition(definition, new Lists.Value(Arrays.asList(ONE)), Operator.GTE);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(setAppliesTo(bound, set(ONE), list(ONE)));
assertTrue(setAppliesTo(bound, set(), list()));
@ -448,7 +448,7 @@ public class ColumnConditionTest
Maps.Value placeholder = new Maps.Value(placeholderMap);
// EQ
ColumnCondition condition = ColumnCondition.condition(definition, null, placeholder, Operator.EQ);
ColumnCondition condition = ColumnCondition.condition(definition, placeholder, Operator.EQ);
ColumnCondition.CollectionBound bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
@ -470,7 +470,7 @@ public class ColumnConditionTest
assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// NEQ
condition = ColumnCondition.condition(definition, null, placeholder, Operator.NEQ);
condition = ColumnCondition.condition(definition, placeholder, Operator.NEQ);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
@ -492,7 +492,7 @@ public class ColumnConditionTest
assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// LT
condition = ColumnCondition.condition(definition, null, placeholder, Operator.LT);
condition = ColumnCondition.condition(definition, placeholder, Operator.LT);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
@ -514,7 +514,7 @@ public class ColumnConditionTest
assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// LTE
condition = ColumnCondition.condition(definition, null, placeholder, Operator.LTE);
condition = ColumnCondition.condition(definition, placeholder, Operator.LTE);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
@ -536,7 +536,7 @@ public class ColumnConditionTest
assertTrue(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// GT
condition = ColumnCondition.condition(definition, null, placeholder, Operator.GT);
condition = ColumnCondition.condition(definition, placeholder, Operator.GT);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertFalse(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));
@ -558,7 +558,7 @@ public class ColumnConditionTest
assertFalse(mapAppliesTo(bound, map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER), map(ONE, ByteBufferUtil.EMPTY_BYTE_BUFFER)));
// GTE
condition = ColumnCondition.condition(definition, null, placeholder, Operator.GTE);
condition = ColumnCondition.condition(definition, placeholder, Operator.GTE);
bound = (ColumnCondition.CollectionBound) condition.bind(QueryOptions.DEFAULT);
assertTrue(mapAppliesTo(bound, map(ONE, ONE), map(ONE, ONE)));

View File

@ -48,7 +48,7 @@ public class SelectionColumnMappingTest extends CQLTester
" v1 int," +
" v2 ascii," +
" v3 frozen<" + typeName + ">)");
userType = Schema.instance.getKSMetaData(KEYSPACE).types.get(ByteBufferUtil.bytes(typeName)).get();
userType = Schema.instance.getKSMetaData(KEYSPACE).types.get(ByteBufferUtil.bytes(typeName)).get().freeze();
functionName = createFunction(KEYSPACE, "int, ascii",
"CREATE FUNCTION %s (i int, a ascii) " +
"CALLED ON NULL INPUT " +

View File

@ -73,32 +73,86 @@ public class UserTypesTest extends CQLTester
execute("INSERT INTO %s(k, v) VALUES (?, {x:?})", 1, -104.99251);
execute("UPDATE %s SET b = ? WHERE k = ?", true, 1);
assertRows(execute("SELECT v.x FROM %s WHERE k = ? AND v = {x:?}", 1, -104.99251),
row(-104.99251)
);
flush();
assertRows(execute("SELECT v.x FROM %s WHERE k = ? AND v = {x:?}", 1, -104.99251),
row(-104.99251)
beforeAndAfterFlush(() ->
assertRows(execute("SELECT v.x FROM %s WHERE k = ? AND v = {x:?}", 1, -104.99251),
row(-104.99251)
)
);
}
@Test
public void testCreateInvalidTablesWithUDT() throws Throwable
public void testInvalidUDTStatements() throws Throwable
{
String myType = createType("CREATE TYPE %s (f int)");
String typename = createType("CREATE TYPE %s (a int)");
String myType = KEYSPACE + '.' + typename;
// Using a UDT without frozen shouldn't work
assertInvalidMessage("Non-frozen User-Defined types are not supported, please use frozen<>",
"CREATE TABLE " + KEYSPACE + ".wrong (k int PRIMARY KEY, v " + KEYSPACE + '.' + myType + ")");
// non-frozen UDTs in a table PK
assertInvalidMessage("Invalid non-frozen user-defined type for PRIMARY KEY component k",
"CREATE TABLE " + KEYSPACE + ".wrong (k " + myType + " PRIMARY KEY , v int)");
assertInvalidMessage("Invalid non-frozen user-defined type for PRIMARY KEY component k2",
"CREATE TABLE " + KEYSPACE + ".wrong (k1 int, k2 " + myType + ", v int, PRIMARY KEY (k1, k2))");
// non-frozen UDTs in a collection
assertInvalidMessage("Non-frozen UDTs are not allowed inside collections: list<" + myType + ">",
"CREATE TABLE " + KEYSPACE + ".wrong (k int PRIMARY KEY, v list<" + myType + ">)");
assertInvalidMessage("Non-frozen UDTs are not allowed inside collections: set<" + myType + ">",
"CREATE TABLE " + KEYSPACE + ".wrong (k int PRIMARY KEY, v set<" + myType + ">)");
assertInvalidMessage("Non-frozen UDTs are not allowed inside collections: map<" + myType + ", int>",
"CREATE TABLE " + KEYSPACE + ".wrong (k int PRIMARY KEY, v map<" + myType + ", int>)");
assertInvalidMessage("Non-frozen UDTs are not allowed inside collections: map<int, " + myType + ">",
"CREATE TABLE " + KEYSPACE + ".wrong (k int PRIMARY KEY, v map<int, " + myType + ">)");
// non-frozen UDT in a collection (as part of a UDT definition)
assertInvalidMessage("Non-frozen UDTs are not allowed inside collections: list<" + myType + ">",
"CREATE TYPE " + KEYSPACE + ".wrong (a int, b list<" + myType + ">)");
// non-frozen UDT in a UDT
assertInvalidMessage("A user type cannot contain non-frozen UDTs",
"CREATE TYPE " + KEYSPACE + ".wrong (a int, b " + myType + ")");
// referencing a UDT in another keyspace
assertInvalidMessage("Statement on keyspace " + KEYSPACE + " cannot refer to a user type in keyspace otherkeyspace;" +
" user types can only be used in the keyspace they are defined in",
"CREATE TABLE " + KEYSPACE + ".wrong (k int PRIMARY KEY, v frozen<otherKeyspace.myType>)");
// referencing an unknown UDT
assertInvalidMessage("Unknown type " + KEYSPACE + ".unknowntype",
"CREATE TABLE " + KEYSPACE + ".wrong (k int PRIMARY KEY, v frozen<" + KEYSPACE + '.' + "unknownType>)");
// bad deletions on frozen UDTs
createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<" + myType + ">, c int)");
assertInvalidMessage("Frozen UDT column b does not support field deletion", "DELETE b.a FROM %s WHERE a = 0");
assertInvalidMessage("Invalid field deletion operation for non-UDT column c", "DELETE c.a FROM %s WHERE a = 0");
// bad updates on frozen UDTs
assertInvalidMessage("Invalid operation (b.a = 0) for frozen UDT column b", "UPDATE %s SET b.a = 0 WHERE a = 0");
assertInvalidMessage("Invalid operation (c.a = 0) for non-UDT column c", "UPDATE %s SET c.a = 0 WHERE a = 0");
// bad deletions on non-frozen UDTs
createTable("CREATE TABLE %s (a int PRIMARY KEY, b " + myType + ", c int)");
assertInvalidMessage("UDT column b does not have a field named foo", "DELETE b.foo FROM %s WHERE a = 0");
// bad updates on non-frozen UDTs
assertInvalidMessage("UDT column b does not have a field named foo", "UPDATE %s SET b.foo = 0 WHERE a = 0");
// bad insert on non-frozen UDTs
assertInvalidMessage("Unknown field 'foo' in value of user defined type", "INSERT INTO %s (a, b, c) VALUES (0, {a: 0, foo: 0}, 0)");
if (usePrepared())
{
assertInvalidMessage("Expected 1 value for " + typename + " column, but got more",
"INSERT INTO %s (a, b, c) VALUES (0, ?, 0)", userType("a", 0, "foo", 0));
}
else
{
assertInvalidMessage("Unknown field 'foo' in value of user defined type " + typename,
"INSERT INTO %s (a, b, c) VALUES (0, ?, 0)", userType("a", 0, "foo", 0));
}
// non-frozen UDT with non-frozen nested collection
String typename2 = createType("CREATE TYPE %s (bar int, foo list<int>)");
String myType2 = KEYSPACE + '.' + typename2;
assertInvalidMessage("Non-frozen UDTs with nested non-frozen collections are not supported",
"CREATE TABLE " + KEYSPACE + ".wrong (k int PRIMARY KEY, v " + myType2 + ")");
}
@Test
@ -106,24 +160,61 @@ public class UserTypesTest extends CQLTester
{
String myType = KEYSPACE + '.' + createType("CREATE TYPE %s (a int)");
createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<" + myType + ">)");
execute("INSERT INTO %s (a, b) VALUES (1, {a: 1})");
execute("INSERT INTO %s (a, b) VALUES (1, ?)", userType("a", 1));
assertRows(execute("SELECT b.a FROM %s"), row(1));
flush();
execute("ALTER TYPE " + myType + " ADD b int");
execute("INSERT INTO %s (a, b) VALUES (2, {a: 2, b :2})");
schemaChange("ALTER TYPE " + myType + " ADD b int");
execute("INSERT INTO %s (a, b) VALUES (2, ?)", userType("a", 2, "b", 2));
assertRows(execute("SELECT b.a, b.b FROM %s"),
row(1, null),
row(2, 2));
beforeAndAfterFlush(() ->
assertRows(execute("SELECT b.a, b.b FROM %s"),
row(1, null),
row(2, 2))
);
}
flush();
@Test
public void testAlterNonFrozenUDT() throws Throwable
{
String myType = KEYSPACE + '.' + createType("CREATE TYPE %s (a int, b text)");
createTable("CREATE TABLE %s (k int PRIMARY KEY, v " + myType + ")");
execute("INSERT INTO %s (k, v) VALUES (0, ?)", userType("a", 1, "b", "abc"));
assertRows(execute("SELECT b.a, b.b FROM %s"),
row(1, null),
row(2, 2));
beforeAndAfterFlush(() -> {
assertRows(execute("SELECT v FROM %s"), row(userType("a", 1, "b", "abc")));
assertRows(execute("SELECT v.a FROM %s"), row(1));
assertRows(execute("SELECT v.b FROM %s"), row("abc"));
});
schemaChange("ALTER TYPE " + myType + " RENAME b TO foo");
assertRows(execute("SELECT v FROM %s"), row(userType("a", 1, "b", "abc")));
assertRows(execute("SELECT v.a FROM %s"), row(1));
assertRows(execute("SELECT v.foo FROM %s"), row("abc"));
execute("UPDATE %s SET v.foo = 'def' WHERE k = 0");
assertRows(execute("SELECT v FROM %s"), row(userType("a", 1, "foo", "def")));
assertRows(execute("SELECT v.a FROM %s"), row(1));
assertRows(execute("SELECT v.foo FROM %s"), row("def"));
execute("INSERT INTO %s (k, v) VALUES (0, ?)", userType("a", 2, "foo", "def"));
assertRows(execute("SELECT v FROM %s"), row(userType("a", 2, "foo", "def")));
assertRows(execute("SELECT v.a FROM %s"), row(2));
assertRows(execute("SELECT v.foo FROM %s"), row("def"));
schemaChange("ALTER TYPE " + myType + " ADD c int");
assertRows(execute("SELECT v FROM %s"), row(userType("a", 2, "foo", "def", "c", null)));
assertRows(execute("SELECT v.a FROM %s"), row(2));
assertRows(execute("SELECT v.foo FROM %s"), row("def"));
assertRows(execute("SELECT v.c FROM %s"), row(new Object[] {null}));
execute("INSERT INTO %s (k, v) VALUES (0, ?)", userType("a", 3, "foo", "abc", "c", 0));
beforeAndAfterFlush(() -> {
assertRows(execute("SELECT v FROM %s"), row(userType("a", 3, "foo", "abc", "c", 0)));
assertRows(execute("SELECT v.c FROM %s"), row(0));
});
}
@Test
@ -134,11 +225,14 @@ public class UserTypesTest extends CQLTester
String myOtherType = createType("CREATE TYPE %s (a frozen<" + myType + ">)");
createTable("CREATE TABLE %s (k int PRIMARY KEY, v frozen<" + myType + ">, z frozen<" + myOtherType + ">)");
assertInvalidMessage("Invalid unset value for field 'y' of user defined type " + myType,
"INSERT INTO %s (k, v) VALUES (10, {x:?, y:?})", 1, unset());
if (usePrepared())
{
assertInvalidMessage("Invalid unset value for field 'y' of user defined type " + myType,
"INSERT INTO %s (k, v) VALUES (10, {x:?, y:?})", 1, unset());
assertInvalidMessage("Invalid unset value for field 'y' of user defined type " + myType,
"INSERT INTO %s (k, v, z) VALUES (10, {x:?, y:?}, {a:{x: ?, y: ?}})", 1, 1, 1, unset());
assertInvalidMessage("Invalid unset value for field 'y' of user defined type " + myType,
"INSERT INTO %s (k, v, z) VALUES (10, {x:?, y:?}, {a:{x: ?, y: ?}})", 1, 1, 1, unset());
}
}
@Test
@ -153,28 +247,22 @@ public class UserTypesTest extends CQLTester
createTable("CREATE TABLE %s (x int PRIMARY KEY, y " + columnType + ")");
execute("INSERT INTO %s (x, y) VALUES(1, {'firstValue':{a:1}})");
assertRows(execute("SELECT * FROM %s"), row(1, map("firstValue", userType(1))));
execute("INSERT INTO %s (x, y) VALUES(1, ?)", map("firstValue", userType("a", 1)));
assertRows(execute("SELECT * FROM %s"), row(1, map("firstValue", userType("a", 1))));
flush();
execute("ALTER TYPE " + KEYSPACE + "." + ut1 + " ADD b int");
execute("INSERT INTO %s (x, y) VALUES(2, {'secondValue':{a:2, b:2}})");
execute("INSERT INTO %s (x, y) VALUES(3, {'thirdValue':{a:3}})");
execute("INSERT INTO %s (x, y) VALUES(4, {'fourthValue':{b:4}})");
execute("INSERT INTO %s (x, y) VALUES(2, ?)", map("secondValue", userType("a", 2, "b", 2)));
execute("INSERT INTO %s (x, y) VALUES(3, ?)", map("thirdValue", userType("a", 3, "b", null)));
execute("INSERT INTO %s (x, y) VALUES(4, ?)", map("fourthValue", userType("a", null, "b", 4)));
assertRows(execute("SELECT * FROM %s"),
row(1, map("firstValue", userType(1))),
row(2, map("secondValue", userType(2, 2))),
row(3, map("thirdValue", userType(3, null))),
row(4, map("fourthValue", userType(null, 4))));
flush();
assertRows(execute("SELECT * FROM %s"),
row(1, map("firstValue", userType(1))),
row(2, map("secondValue", userType(2, 2))),
row(3, map("thirdValue", userType(3, null))),
row(4, map("fourthValue", userType(null, 4))));
beforeAndAfterFlush(() ->
assertRows(execute("SELECT * FROM %s"),
row(1, map("firstValue", userType("a", 1))),
row(2, map("secondValue", userType("a", 2, "b", 2))),
row(3, map("thirdValue", userType("a", 3, "b", null))),
row(4, map("fourthValue", userType("a", null, "b", 4))))
);
}
}
@ -190,28 +278,22 @@ public class UserTypesTest extends CQLTester
createTable("CREATE TABLE %s (x int PRIMARY KEY, y " + columnType + ")");
execute("INSERT INTO %s (x, y) VALUES(1, {1} )");
assertRows(execute("SELECT * FROM %s"), row(1, set(userType(1))));
execute("INSERT INTO %s (x, y) VALUES(1, ?)", set(userType("a", 1)));
assertRows(execute("SELECT * FROM %s"), row(1, set(userType("a", 1))));
flush();
execute("ALTER TYPE " + KEYSPACE + "." + ut1 + " ADD b int");
execute("INSERT INTO %s (x, y) VALUES(2, {{a:2, b:2}})");
execute("INSERT INTO %s (x, y) VALUES(3, {{a:3}})");
execute("INSERT INTO %s (x, y) VALUES(4, {{b:4}})");
execute("INSERT INTO %s (x, y) VALUES(2, ?)", set(userType("a", 2, "b", 2)));
execute("INSERT INTO %s (x, y) VALUES(3, ?)", set(userType("a", 3, "b", null)));
execute("INSERT INTO %s (x, y) VALUES(4, ?)", set(userType("a", null, "b", 4)));
assertRows(execute("SELECT * FROM %s"),
row(1, set(userType(1))),
row(2, set(userType(2, 2))),
row(3, set(userType(3, null))),
row(4, set(userType(null, 4))));
flush();
assertRows(execute("SELECT * FROM %s"),
row(1, set(userType(1))),
row(2, set(userType(2, 2))),
row(3, set(userType(3, null))),
row(4, set(userType(null, 4))));
beforeAndAfterFlush(() ->
assertRows(execute("SELECT * FROM %s"),
row(1, set(userType("a", 1))),
row(2, set(userType("a", 2, "b", 2))),
row(3, set(userType("a", 3, "b", null))),
row(4, set(userType("a", null, "b", 4))))
);
}
}
@ -227,28 +309,22 @@ public class UserTypesTest extends CQLTester
createTable("CREATE TABLE %s (x int PRIMARY KEY, y " + columnType + ")");
execute("INSERT INTO %s (x, y) VALUES(1, [1] )");
assertRows(execute("SELECT * FROM %s"), row(1, list(userType(1))));
execute("INSERT INTO %s (x, y) VALUES(1, ?)", list(userType("a", 1)));
assertRows(execute("SELECT * FROM %s"), row(1, list(userType("a", 1))));
flush();
execute("ALTER TYPE " + KEYSPACE + "." + ut1 + " ADD b int");
execute("INSERT INTO %s (x, y) VALUES(2, [{a:2, b:2}])");
execute("INSERT INTO %s (x, y) VALUES(3, [{a:3}])");
execute("INSERT INTO %s (x, y) VALUES(4, [{b:4}])");
execute("INSERT INTO %s (x, y) VALUES (2, ?)", list(userType("a", 2, "b", 2)));
execute("INSERT INTO %s (x, y) VALUES (3, ?)", list(userType("a", 3, "b", null)));
execute("INSERT INTO %s (x, y) VALUES (4, ?)", list(userType("a", null, "b", 4)));
assertRows(execute("SELECT * FROM %s"),
row(1, list(userType(1))),
row(2, list(userType(2, 2))),
row(3, list(userType(3, null))),
row(4, list(userType(null, 4))));
flush();
assertRows(execute("SELECT * FROM %s"),
row(1, list(userType(1))),
row(2, list(userType(2, 2))),
row(3, list(userType(3, null))),
row(4, list(userType(null, 4))));
beforeAndAfterFlush(() ->
assertRows(execute("SELECT * FROM %s"),
row(1, list(userType("a", 1))),
row(2, list(userType("a", 2, "b", 2))),
row(3, list(userType("a", 3, "b", null))),
row(4, list(userType("a", null, "b", 4))))
);
}
}
@ -259,28 +335,22 @@ public class UserTypesTest extends CQLTester
createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<tuple<int, " + KEYSPACE + "." + type + ">>)");
execute("INSERT INTO %s (a, b) VALUES(1, (1, {a:1, b:1}))");
assertRows(execute("SELECT * FROM %s"), row(1, tuple(1, userType(1, 1))));
execute("INSERT INTO %s (a, b) VALUES(1, (1, ?))", userType("a", 1, "b", 1));
assertRows(execute("SELECT * FROM %s"), row(1, tuple(1, userType("a", 1, "b", 1))));
flush();
execute("ALTER TYPE " + KEYSPACE + "." + type + " ADD c int");
execute("INSERT INTO %s (a, b) VALUES(2, (2, {a: 2, b: 2, c: 2}))");
execute("INSERT INTO %s (a, b) VALUES(3, (3, {a: 3, b: 3}))");
execute("INSERT INTO %s (a, b) VALUES(4, (4, {b:4}))");
execute("INSERT INTO %s (a, b) VALUES (2, (2, ?))", userType("a", 2, "b", 2, "c", 2));
execute("INSERT INTO %s (a, b) VALUES (3, (3, ?))", userType("a", 3, "b", 3, "c", null));
execute("INSERT INTO %s (a, b) VALUES (4, (4, ?))", userType("a", null, "b", 4, "c", null));
assertRows(execute("SELECT * FROM %s"),
row(1, tuple(1, userType(1, 1))),
row(2, tuple(2, userType(2, 2, 2))),
row(3, tuple(3, userType(3, 3, null))),
row(4, tuple(4, userType(null, 4, null))));
flush();
assertRows(execute("SELECT * FROM %s"),
row(1, tuple(1, userType(1, 1))),
row(2, tuple(2, userType(2, 2, 2))),
row(3, tuple(3, userType(3, 3, null))),
row(4, tuple(4, userType(null, 4, null))));
beforeAndAfterFlush(() ->
assertRows(execute("SELECT * FROM %s"),
row(1, tuple(1, userType("a", 1, "b", 1))),
row(2, tuple(2, userType("a", 2, "b", 2, "c", 2))),
row(3, tuple(3, userType("a", 3, "b", 3, "c", null))),
row(4, tuple(4, userType("a", null, "b", 4, "c", null))))
);
}
@Test
@ -290,28 +360,22 @@ public class UserTypesTest extends CQLTester
createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<tuple<int, tuple<int, " + KEYSPACE + "." + type + ">>>)");
execute("INSERT INTO %s (a, b) VALUES(1, (1, (1, {a:1, b:1})))");
assertRows(execute("SELECT * FROM %s"), row(1, tuple(1, tuple(1, userType(1, 1)))));
execute("INSERT INTO %s (a, b) VALUES(1, (1, (1, ?)))", userType("a", 1, "b", 1));
assertRows(execute("SELECT * FROM %s"), row(1, tuple(1, tuple(1, userType("a", 1, "b", 1)))));
flush();
execute("ALTER TYPE " + KEYSPACE + "." + type + " ADD c int");
execute("INSERT INTO %s (a, b) VALUES(2, (2, (1, {a: 2, b: 2, c: 2})))");
execute("INSERT INTO %s (a, b) VALUES(3, (3, (1, {a: 3, b: 3})))");
execute("INSERT INTO %s (a, b) VALUES(4, (4, (1, {b:4})))");
execute("INSERT INTO %s (a, b) VALUES(2, (2, (1, ?)))", userType("a", 2, "b", 2, "c", 2));
execute("INSERT INTO %s (a, b) VALUES(3, (3, ?))", tuple(1, userType("a", 3, "b", 3, "c", null)));
execute("INSERT INTO %s (a, b) VALUES(4, ?)", tuple(4, tuple(1, userType("a", null, "b", 4, "c", null))));
assertRows(execute("SELECT * FROM %s"),
row(1, tuple(1, tuple(1, userType(1, 1)))),
row(2, tuple(2, tuple(1, userType(2, 2, 2)))),
row(3, tuple(3, tuple(1, userType(3, 3, null)))),
row(4, tuple(4, tuple(1, userType(null, 4, null)))));
flush();
assertRows(execute("SELECT * FROM %s"),
row(1, tuple(1, tuple(1, userType(1, 1)))),
row(2, tuple(2, tuple(1, userType(2, 2, 2)))),
row(3, tuple(3, tuple(1, userType(3, 3, null)))),
row(4, tuple(4, tuple(1, userType(null, 4, null)))));
beforeAndAfterFlush(() ->
assertRows(execute("SELECT * FROM %s"),
row(1, tuple(1, tuple(1, userType("a", 1, "b", 1)))),
row(2, tuple(2, tuple(1, userType("a", 2, "b", 2, "c", 2)))),
row(3, tuple(3, tuple(1, userType("a", 3, "b", 3, "c", null)))),
row(4, tuple(4, tuple(1, userType("a", null, "b", 4, "c", null)))))
);
}
@Test
@ -322,28 +386,24 @@ public class UserTypesTest extends CQLTester
createTable("CREATE TABLE %s (a int PRIMARY KEY, b frozen<" + KEYSPACE + "." + otherType + ">)");
execute("INSERT INTO %s (a, b) VALUES(1, {x: {a:1, b:1}})");
execute("INSERT INTO %s (a, b) VALUES(1, {x: ?})", userType("a", 1, "b", 1));
assertRows(execute("SELECT b.x.a, b.x.b FROM %s"), row(1, 1));
execute("INSERT INTO %s (a, b) VALUES(1, ?)", userType("x", userType("a", 1, "b", 1)));
assertRows(execute("SELECT b.x.a, b.x.b FROM %s"), row(1, 1));
flush();
execute("ALTER TYPE " + KEYSPACE + "." + type + " ADD c int");
execute("INSERT INTO %s (a, b) VALUES(2, {x: {a: 2, b: 2, c: 2}})");
execute("INSERT INTO %s (a, b) VALUES(3, {x: {a: 3, b: 3}})");
execute("INSERT INTO %s (a, b) VALUES(4, {x: {b:4}})");
execute("INSERT INTO %s (a, b) VALUES(2, {x: ?})", userType("a", 2, "b", 2, "c", 2));
execute("INSERT INTO %s (a, b) VALUES(3, {x: ?})", userType("a", 3, "b", 3));
execute("INSERT INTO %s (a, b) VALUES(4, {x: ?})", userType("a", null, "b", 4));
assertRows(execute("SELECT b.x.a, b.x.b, b.x.c FROM %s"),
row(1, 1, null),
row(2, 2, 2),
row(3, 3, null),
row(null, 4, null));
flush();
assertRows(execute("SELECT b.x.a, b.x.b, b.x.c FROM %s"),
row(1, 1, null),
row(2, 2, 2),
row(3, 3, null),
row(null, 4, null));
beforeAndAfterFlush(() ->
assertRows(execute("SELECT b.x.a, b.x.b, b.x.c FROM %s"),
row(1, 1, null),
row(2, 2, 2),
row(3, 3, null),
row(null, 4, null))
);
}
/**
@ -383,10 +443,11 @@ public class UserTypesTest extends CQLTester
createTable("CREATE TABLE %s (id int PRIMARY KEY, val frozen<" + type2 + ">)");
execute("INSERT INTO %s (id, val) VALUES (0, { s : {{ s : {'foo', 'bar'}, m : { 'foo' : 'bar' }, l : ['foo', 'bar']} }})");
execute("INSERT INTO %s (id, val) VALUES (0, ?)",
userType("s", set(userType("s", set("foo", "bar"), "m", map("foo", "bar"), "l", list("foo", "bar")))));
// TODO: check result once we have an easy way to do it. For now we just check it doesn't crash
execute("SELECT * FROM %s");
assertRows(execute("SELECT * FROM %s"),
row(0, userType("s", set(userType("s", set("foo", "bar"), "m", map("foo", "bar"), "l", list("foo", "bar"))))));
}
/**
@ -398,9 +459,11 @@ public class UserTypesTest extends CQLTester
String typeName = createType("CREATE TYPE %s (fooint int, fooset set <text>)");
createTable("CREATE TABLE %s (key int PRIMARY KEY, data frozen <" + typeName + ">)");
execute("INSERT INTO %s (key, data) VALUES (1, {fooint: 1, fooset: {'2'}})");
execute("INSERT INTO %s (key, data) VALUES (1, ?)", userType("fooint", 1, "fooset", set("2")));
execute("ALTER TYPE " + keyspace() + "." + typeName + " ADD foomap map <int,text>");
execute("INSERT INTO %s (key, data) VALUES (1, {fooint: 1, fooset: {'2'}, foomap: {3 : 'bar'}})");
execute("INSERT INTO %s (key, data) VALUES (1, ?)", userType("fooint", 1, "fooset", set("2"), "foomap", map(3, "bar")));
assertRows(execute("SELECT * FROM %s"),
row(1, userType("fooint", 1, "fooset", set("2"), "foomap", map(3, "bar"))));
}
@Test
@ -464,13 +527,13 @@ public class UserTypesTest extends CQLTester
type1 = createType("CREATE TYPE %s (foo ascii)");
String type2 = createType("CREATE TYPE %s (foo frozen<" + type1 + ">)");
assertComplexInvalidAlterDropStatements(type1, type2, "{foo: 'abc'}");
assertComplexInvalidAlterDropStatements(type1, type2, "{foo: {foo: 'abc'}}");
type1 = createType("CREATE TYPE %s (foo ascii)");
type2 = createType("CREATE TYPE %s (foo frozen<" + type1 + ">)");
assertComplexInvalidAlterDropStatements(type1,
"list<frozen<" + type2 + ">>",
"[{foo: 'abc'}]");
"[{foo: {foo: 'abc'}}]");
type1 = createType("CREATE TYPE %s (foo ascii)");
type2 = createType("CREATE TYPE %s (foo frozen<set<" + type1 + ">>)");
@ -515,6 +578,110 @@ public class UserTypesTest extends CQLTester
assertInvalidMessage("Cannot drop user type " + typeWithKs(t), "DROP TYPE " + typeWithKs(t) + ';');
}
@Test
public void testInsertNonFrozenUDT() throws Throwable
{
String typeName = createType("CREATE TYPE %s (a int, b text)");
createTable("CREATE TABLE %s (k int PRIMARY KEY, v " + typeName + ")");
execute("INSERT INTO %s (k, v) VALUES (?, {a: ?, b: ?})", 0, 0, "abc");
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", "abc")));
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, userType("a", 0, "b", "abc"));
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", "abc")));
execute("INSERT INTO %s (k, v) VALUES (?, {a: ?, b: ?})", 0, 0, null);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", null)));
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, userType("a", null, "b", "abc"));
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", null, "b", "abc")));
}
@Test
public void testUpdateNonFrozenUDT() throws Throwable
{
String typeName = createType("CREATE TYPE %s (a int, b text)");
createTable("CREATE TABLE %s (k int PRIMARY KEY, v " + typeName + ")");
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, userType("a", 0, "b", "abc"));
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", "abc")));
// overwrite the whole UDT
execute("UPDATE %s SET v = ? WHERE k = ?", userType("a", 1, "b", "def"), 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 1, "b", "def")));
execute("UPDATE %s SET v = ? WHERE k = ?", userType("a", 0, "b", null), 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", null)));
execute("UPDATE %s SET v = ? WHERE k = ?", userType("a", null, "b", "abc"), 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", null, "b", "abc")));
// individually set fields to non-null values
execute("UPDATE %s SET v.a = ? WHERE k = ?", 1, 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 1, "b", "abc")));
execute("UPDATE %s SET v.b = ? WHERE k = ?", "def", 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 1, "b", "def")));
execute("UPDATE %s SET v.a = ?, v.b = ? WHERE k = ?", 0, "abc", 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", "abc")));
execute("UPDATE %s SET v.b = ?, v.a = ? WHERE k = ?", "abc", 0, 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", "abc")));
// individually set fields to null values
execute("UPDATE %s SET v.a = ? WHERE k = ?", null, 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", null, "b", "abc")));
execute("UPDATE %s SET v.a = ? WHERE k = ?", 0, 0);
execute("UPDATE %s SET v.b = ? WHERE k = ?", null, 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", null)));
execute("UPDATE %s SET v.a = ? WHERE k = ?", null, 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, null));
assertInvalid("UPDATE %s SET v.bad = ? FROM %s WHERE k = ?", 0, 0);
assertInvalid("UPDATE %s SET v = ? FROM %s WHERE k = ?", 0, 0);
assertInvalid("UPDATE %s SET v = ? FROM %s WHERE k = ?", userType("a", 1, "b", "abc", "bad", 123), 0);
}
@Test
public void testDeleteNonFrozenUDT() throws Throwable
{
String typeName = createType("CREATE TYPE %s (a int, b text)");
createTable("CREATE TABLE %s (k int PRIMARY KEY, v " + typeName + ")");
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, userType("a", 0, "b", "abc"));
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", "abc")));
execute("DELETE v.b FROM %s WHERE k = ?", 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", 0, "b", null)));
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, userType("a", 0, "b", "abc"));
execute("DELETE v.a FROM %s WHERE k = ?", 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, userType("a", null, "b", "abc")));
execute("DELETE v.b FROM %s WHERE k = ?", 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, null));
// delete both fields at once
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, userType("a", 0, "b", "abc"));
execute("DELETE v.a, v.b FROM %s WHERE k = ?", 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, null));
// same, but reverse field order
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, userType("a", 0, "b", "abc"));
execute("DELETE v.b, v.a FROM %s WHERE k = ?", 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, null));
// delete the whole thing at once
execute("INSERT INTO %s (k, v) VALUES (?, ?)", 0, userType("a", 0, "b", "abc"));
execute("DELETE v FROM %s WHERE k = ?", 0);
assertRows(execute("SELECT * FROM %s WHERE k = ?", 0), row(0, null));
assertInvalid("DELETE v.bad FROM %s WHERE k = ?", 0);
}
private String typeWithKs(String type1)
{
return keyspace() + '.' + type1;

View File

@ -384,6 +384,377 @@ public class InsertUpdateIfConditionTest extends CQLTester
assertRows(execute("INSERT INTO %s (partition, key, owner) VALUES ('a', 'c', 'x') IF NOT EXISTS"), row(true));
}
@Test
public void testWholeUDT() throws Throwable
{
String typename = createType("CREATE TYPE %s (a int, b text)");
String myType = KEYSPACE + '.' + typename;
for (boolean frozen : new boolean[] {false, true})
{
createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, v %s)",
frozen
? "frozen<" + myType + ">"
: myType));
Object v = userType("a", 0, "b", "abc");
execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);
checkAppliesUDT("v = {a: 0, b: 'abc'}", v);
checkAppliesUDT("v != null", v);
checkAppliesUDT("v != {a: 1, b: 'abc'}", v);
checkAppliesUDT("v != {a: 0, b: 'def'}", v);
checkAppliesUDT("v > {a: -1, b: 'abc'}", v);
checkAppliesUDT("v > {a: 0, b: 'aaa'}", v);
checkAppliesUDT("v > {a: 0}", v);
checkAppliesUDT("v >= {a: 0, b: 'aaa'}", v);
checkAppliesUDT("v >= {a: 0, b: 'abc'}", v);
checkAppliesUDT("v < {a: 0, b: 'zzz'}", v);
checkAppliesUDT("v < {a: 1, b: 'abc'}", v);
checkAppliesUDT("v < {a: 1}", v);
checkAppliesUDT("v <= {a: 0, b: 'zzz'}", v);
checkAppliesUDT("v <= {a: 0, b: 'abc'}", v);
checkAppliesUDT("v IN (null, {a: 0, b: 'abc'}, {a: 1})", v);
// multiple conditions
checkAppliesUDT("v > {a: -1, b: 'abc'} AND v > {a: 0}", v);
checkAppliesUDT("v != null AND v IN ({a: 0, b: 'abc'})", v);
// should not apply
checkDoesNotApplyUDT("v = {a: 0, b: 'def'}", v);
checkDoesNotApplyUDT("v = {a: 1, b: 'abc'}", v);
checkDoesNotApplyUDT("v = null", v);
checkDoesNotApplyUDT("v != {a: 0, b: 'abc'}", v);
checkDoesNotApplyUDT("v > {a: 1, b: 'abc'}", v);
checkDoesNotApplyUDT("v > {a: 0, b: 'zzz'}", v);
checkDoesNotApplyUDT("v >= {a: 1, b: 'abc'}", v);
checkDoesNotApplyUDT("v >= {a: 0, b: 'zzz'}", v);
checkDoesNotApplyUDT("v < {a: -1, b: 'abc'}", v);
checkDoesNotApplyUDT("v < {a: 0, b: 'aaa'}", v);
checkDoesNotApplyUDT("v <= {a: -1, b: 'abc'}", v);
checkDoesNotApplyUDT("v <= {a: 0, b: 'aaa'}", v);
checkDoesNotApplyUDT("v IN ({a: 0}, {b: 'abc'}, {a: 0, b: 'def'}, null)", v);
checkDoesNotApplyUDT("v IN ()", v);
// multiple conditions
checkDoesNotApplyUDT("v IN () AND v IN ({a: 0, b: 'abc'})", v);
checkDoesNotApplyUDT("v > {a: 0, b: 'aaa'} AND v < {a: 0, b: 'aaa'}", v);
// invalid conditions
checkInvalidUDT("v = {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
checkInvalidUDT("v = {foo: 'foo'}", v, InvalidRequestException.class);
checkInvalidUDT("v < {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
checkInvalidUDT("v < null", v, InvalidRequestException.class);
checkInvalidUDT("v <= {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
checkInvalidUDT("v <= null", v, InvalidRequestException.class);
checkInvalidUDT("v > {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
checkInvalidUDT("v > null", v, InvalidRequestException.class);
checkInvalidUDT("v >= {a: 1, b: 'abc', c: 'foo'}", v, InvalidRequestException.class);
checkInvalidUDT("v >= null", v, InvalidRequestException.class);
checkInvalidUDT("v IN null", v, SyntaxException.class);
checkInvalidUDT("v IN 367", v, SyntaxException.class);
checkInvalidUDT("v CONTAINS KEY 123", v, SyntaxException.class);
checkInvalidUDT("v CONTAINS 'bar'", v, SyntaxException.class);
/////////////////// null suffix on stored udt ////////////////////
v = userType("a", 0, "b", null);
execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);
checkAppliesUDT("v = {a: 0}", v);
checkAppliesUDT("v = {a: 0, b: null}", v);
checkAppliesUDT("v != null", v);
checkAppliesUDT("v != {a: 1, b: null}", v);
checkAppliesUDT("v != {a: 1}", v);
checkAppliesUDT("v != {a: 0, b: 'def'}", v);
checkAppliesUDT("v > {a: -1, b: 'abc'}", v);
checkAppliesUDT("v > {a: -1}", v);
checkAppliesUDT("v >= {a: 0}", v);
checkAppliesUDT("v >= {a: -1, b: 'abc'}", v);
checkAppliesUDT("v < {a: 0, b: 'zzz'}", v);
checkAppliesUDT("v < {a: 1, b: 'abc'}", v);
checkAppliesUDT("v < {a: 1}", v);
checkAppliesUDT("v <= {a: 0, b: 'zzz'}", v);
checkAppliesUDT("v <= {a: 0}", v);
checkAppliesUDT("v IN (null, {a: 0, b: 'abc'}, {a: 0})", v);
// multiple conditions
checkAppliesUDT("v > {a: -1, b: 'abc'} AND v >= {a: 0}", v);
checkAppliesUDT("v != null AND v IN ({a: 0}, {a: 0, b: null})", v);
// should not apply
checkDoesNotApplyUDT("v = {a: 0, b: 'def'}", v);
checkDoesNotApplyUDT("v = {a: 1}", v);
checkDoesNotApplyUDT("v = {b: 'abc'}", v);
checkDoesNotApplyUDT("v = null", v);
checkDoesNotApplyUDT("v != {a: 0}", v);
checkDoesNotApplyUDT("v != {a: 0, b: null}", v);
checkDoesNotApplyUDT("v > {a: 1, b: 'abc'}", v);
checkDoesNotApplyUDT("v > {a: 0}", v);
checkDoesNotApplyUDT("v >= {a: 1, b: 'abc'}", v);
checkDoesNotApplyUDT("v >= {a: 1}", v);
checkDoesNotApplyUDT("v < {a: -1, b: 'abc'}", v);
checkDoesNotApplyUDT("v < {a: -1}", v);
checkDoesNotApplyUDT("v < {a: 0}", v);
checkDoesNotApplyUDT("v <= {a: -1, b: 'abc'}", v);
checkDoesNotApplyUDT("v <= {a: -1}", v);
checkDoesNotApplyUDT("v IN ({a: 1}, {b: 'abc'}, {a: 0, b: 'def'}, null)", v);
checkDoesNotApplyUDT("v IN ()", v);
// multiple conditions
checkDoesNotApplyUDT("v IN () AND v IN ({a: 0})", v);
checkDoesNotApplyUDT("v > {a: -1} AND v < {a: 0}", v);
/////////////////// null prefix on stored udt ////////////////////
v = userType("a", null, "b", "abc");
execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);
checkAppliesUDT("v = {a: null, b: 'abc'}", v);
checkAppliesUDT("v = {b: 'abc'}", v);
checkAppliesUDT("v != null", v);
checkAppliesUDT("v != {a: 0, b: 'abc'}", v);
checkAppliesUDT("v != {a: 0}", v);
checkAppliesUDT("v != {b: 'def'}", v);
checkAppliesUDT("v > {a: null, b: 'aaa'}", v);
checkAppliesUDT("v > {b: 'aaa'}", v);
checkAppliesUDT("v >= {a: null, b: 'aaa'}", v);
checkAppliesUDT("v >= {b: 'abc'}", v);
checkAppliesUDT("v < {a: null, b: 'zzz'}", v);
checkAppliesUDT("v < {a: 0, b: 'abc'}", v);
checkAppliesUDT("v < {a: 0}", v);
checkAppliesUDT("v < {b: 'zzz'}", v);
checkAppliesUDT("v <= {a: null, b: 'zzz'}", v);
checkAppliesUDT("v <= {a: 0}", v);
checkAppliesUDT("v <= {b: 'abc'}", v);
checkAppliesUDT("v IN (null, {a: null, b: 'abc'}, {a: 0})", v);
checkAppliesUDT("v IN (null, {a: 0, b: 'abc'}, {b: 'abc'})", v);
// multiple conditions
checkAppliesUDT("v > {b: 'aaa'} AND v >= {b: 'abc'}", v);
checkAppliesUDT("v != null AND v IN ({a: 0}, {a: null, b: 'abc'})", v);
// should not apply
checkDoesNotApplyUDT("v = {a: 0, b: 'def'}", v);
checkDoesNotApplyUDT("v = {a: 1}", v);
checkDoesNotApplyUDT("v = {b: 'def'}", v);
checkDoesNotApplyUDT("v = null", v);
checkDoesNotApplyUDT("v != {b: 'abc'}", v);
checkDoesNotApplyUDT("v != {a: null, b: 'abc'}", v);
checkDoesNotApplyUDT("v > {a: 1, b: 'abc'}", v);
checkDoesNotApplyUDT("v > {a: null, b: 'zzz'}", v);
checkDoesNotApplyUDT("v > {b: 'zzz'}", v);
checkDoesNotApplyUDT("v >= {a: null, b: 'zzz'}", v);
checkDoesNotApplyUDT("v >= {a: 1}", v);
checkDoesNotApplyUDT("v >= {b: 'zzz'}", v);
checkDoesNotApplyUDT("v < {a: null, b: 'aaa'}", v);
checkDoesNotApplyUDT("v < {b: 'aaa'}", v);
checkDoesNotApplyUDT("v <= {a: null, b: 'aaa'}", v);
checkDoesNotApplyUDT("v <= {b: 'aaa'}", v);
checkDoesNotApplyUDT("v IN ({a: 1}, {a: 1, b: 'abc'}, {a: null, b: 'def'}, null)", v);
checkDoesNotApplyUDT("v IN ()", v);
// multiple conditions
checkDoesNotApplyUDT("v IN () AND v IN ({b: 'abc'})", v);
checkDoesNotApplyUDT("v IN () AND v IN ({a: null, b: 'abc'})", v);
checkDoesNotApplyUDT("v > {a: -1} AND v < {a: 0}", v);
/////////////////// null udt ////////////////////
v = null;
execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);
checkAppliesUDT("v = null", v);
checkAppliesUDT("v IN (null, {a: null, b: 'abc'}, {a: 0})", v);
checkAppliesUDT("v IN (null, {a: 0, b: 'abc'}, {b: 'abc'})", v);
// multiple conditions
checkAppliesUDT("v = null AND v IN (null, {a: 0}, {a: null, b: 'abc'})", v);
// should not apply
checkDoesNotApplyUDT("v = {a: 0, b: 'def'}", v);
checkDoesNotApplyUDT("v = {a: 1}", v);
checkDoesNotApplyUDT("v = {b: 'def'}", v);
checkDoesNotApplyUDT("v != null", v);
checkDoesNotApplyUDT("v > {a: 1, b: 'abc'}", v);
checkDoesNotApplyUDT("v > {a: null, b: 'zzz'}", v);
checkDoesNotApplyUDT("v > {b: 'zzz'}", v);
checkDoesNotApplyUDT("v >= {a: null, b: 'zzz'}", v);
checkDoesNotApplyUDT("v >= {a: 1}", v);
checkDoesNotApplyUDT("v >= {b: 'zzz'}", v);
checkDoesNotApplyUDT("v < {a: null, b: 'aaa'}", v);
checkDoesNotApplyUDT("v < {b: 'aaa'}", v);
checkDoesNotApplyUDT("v <= {a: null, b: 'aaa'}", v);
checkDoesNotApplyUDT("v <= {b: 'aaa'}", v);
checkDoesNotApplyUDT("v IN ({a: 1}, {a: 1, b: 'abc'}, {a: null, b: 'def'})", v);
checkDoesNotApplyUDT("v IN ()", v);
// multiple conditions
checkDoesNotApplyUDT("v IN () AND v IN ({b: 'abc'})", v);
checkDoesNotApplyUDT("v > {a: -1} AND v < {a: 0}", v);
}
}
@Test
public void testUDTField() throws Throwable
{
String typename = createType("CREATE TYPE %s (a int, b text)");
String myType = KEYSPACE + '.' + typename;
for (boolean frozen : new boolean[] {false, true})
{
createTable(String.format("CREATE TABLE %%s (k int PRIMARY KEY, v %s)",
frozen
? "frozen<" + myType + ">"
: myType));
Object v = userType("a", 0, "b", "abc");
execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);
checkAppliesUDT("v.a = 0", v);
checkAppliesUDT("v.b = 'abc'", v);
checkAppliesUDT("v.a < 1", v);
checkAppliesUDT("v.b < 'zzz'", v);
checkAppliesUDT("v.b <= 'bar'", v);
checkAppliesUDT("v.b > 'aaa'", v);
checkAppliesUDT("v.b >= 'abc'", v);
checkAppliesUDT("v.a != -1", v);
checkAppliesUDT("v.b != 'xxx'", v);
checkAppliesUDT("v.a != null", v);
checkAppliesUDT("v.b != null", v);
checkAppliesUDT("v.a IN (null, 0, 1)", v);
checkAppliesUDT("v.b IN (null, 'xxx', 'abc')", v);
checkAppliesUDT("v.b > 'aaa' AND v.b < 'zzz'", v);
checkAppliesUDT("v.a = 0 AND v.b > 'aaa'", v);
// do not apply
checkDoesNotApplyUDT("v.a = -1", v);
checkDoesNotApplyUDT("v.b = 'xxx'", v);
checkDoesNotApplyUDT("v.a < -1", v);
checkDoesNotApplyUDT("v.b < 'aaa'", v);
checkDoesNotApplyUDT("v.b <= 'aaa'", v);
checkDoesNotApplyUDT("v.b > 'zzz'", v);
checkDoesNotApplyUDT("v.b >= 'zzz'", v);
checkDoesNotApplyUDT("v.a != 0", v);
checkDoesNotApplyUDT("v.b != 'abc'", v);
checkDoesNotApplyUDT("v.a IN (null, -1)", v);
checkDoesNotApplyUDT("v.b IN (null, 'xxx')", v);
checkDoesNotApplyUDT("v.a IN ()", v);
checkDoesNotApplyUDT("v.b IN ()", v);
checkDoesNotApplyUDT("v.b != null AND v.b IN ()", v);
// invalid
checkInvalidUDT("v.c = null", v, InvalidRequestException.class);
checkInvalidUDT("v.a < null", v, InvalidRequestException.class);
checkInvalidUDT("v.a <= null", v, InvalidRequestException.class);
checkInvalidUDT("v.a > null", v, InvalidRequestException.class);
checkInvalidUDT("v.a >= null", v, InvalidRequestException.class);
checkInvalidUDT("v.a IN null", v, SyntaxException.class);
checkInvalidUDT("v.a IN 367", v, SyntaxException.class);
checkInvalidUDT("v.b IN (1, 2, 3)", v, InvalidRequestException.class);
checkInvalidUDT("v.a CONTAINS 367", v, SyntaxException.class);
checkInvalidUDT("v.a CONTAINS KEY 367", v, SyntaxException.class);
/////////////// null suffix on udt ////////////////
v = userType("a", 0, "b", null);
execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);
checkAppliesUDT("v.a = 0", v);
checkAppliesUDT("v.b = null", v);
checkAppliesUDT("v.b != 'xxx'", v);
checkAppliesUDT("v.a != null", v);
checkAppliesUDT("v.a IN (null, 0, 1)", v);
checkAppliesUDT("v.b IN (null, 'xxx', 'abc')", v);
checkAppliesUDT("v.a = 0 AND v.b = null", v);
// do not apply
checkDoesNotApplyUDT("v.b = 'abc'", v);
checkDoesNotApplyUDT("v.a < -1", v);
checkDoesNotApplyUDT("v.b < 'aaa'", v);
checkDoesNotApplyUDT("v.b <= 'aaa'", v);
checkDoesNotApplyUDT("v.b > 'zzz'", v);
checkDoesNotApplyUDT("v.b >= 'zzz'", v);
checkDoesNotApplyUDT("v.a != 0", v);
checkDoesNotApplyUDT("v.b != null", v);
checkDoesNotApplyUDT("v.a IN (null, -1)", v);
checkDoesNotApplyUDT("v.b IN ('xxx', 'abc')", v);
checkDoesNotApplyUDT("v.a IN ()", v);
checkDoesNotApplyUDT("v.b IN ()", v);
checkDoesNotApplyUDT("v.b != null AND v.b IN ()", v);
/////////////// null prefix on udt ////////////////
v = userType("a", null, "b", "abc");
execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);
checkAppliesUDT("v.a = null", v);
checkAppliesUDT("v.b = 'abc'", v);
checkAppliesUDT("v.a != 0", v);
checkAppliesUDT("v.b != null", v);
checkAppliesUDT("v.a IN (null, 0, 1)", v);
checkAppliesUDT("v.b IN (null, 'xxx', 'abc')", v);
checkAppliesUDT("v.a = null AND v.b = 'abc'", v);
// do not apply
checkDoesNotApplyUDT("v.a = 0", v);
checkDoesNotApplyUDT("v.a < -1", v);
checkDoesNotApplyUDT("v.b >= 'zzz'", v);
checkDoesNotApplyUDT("v.a != null", v);
checkDoesNotApplyUDT("v.b != 'abc'", v);
checkDoesNotApplyUDT("v.a IN (-1, 0)", v);
checkDoesNotApplyUDT("v.b IN (null, 'xxx')", v);
checkDoesNotApplyUDT("v.a IN ()", v);
checkDoesNotApplyUDT("v.b IN ()", v);
checkDoesNotApplyUDT("v.b != null AND v.b IN ()", v);
/////////////// null udt ////////////////
v = null;
execute("INSERT INTO %s (k, v) VALUES (0, ?)", v);
checkAppliesUDT("v.a = null", v);
checkAppliesUDT("v.b = null", v);
checkAppliesUDT("v.a != 0", v);
checkAppliesUDT("v.b != 'abc'", v);
checkAppliesUDT("v.a IN (null, 0, 1)", v);
checkAppliesUDT("v.b IN (null, 'xxx', 'abc')", v);
checkAppliesUDT("v.a = null AND v.b = null", v);
// do not apply
checkDoesNotApplyUDT("v.a = 0", v);
checkDoesNotApplyUDT("v.a < -1", v);
checkDoesNotApplyUDT("v.b >= 'zzz'", v);
checkDoesNotApplyUDT("v.a != null", v);
checkDoesNotApplyUDT("v.b != null", v);
checkDoesNotApplyUDT("v.a IN (-1, 0)", v);
checkDoesNotApplyUDT("v.b IN ('xxx', 'abc')", v);
checkDoesNotApplyUDT("v.a IN ()", v);
checkDoesNotApplyUDT("v.b IN ()", v);
checkDoesNotApplyUDT("v.b != null AND v.b IN ()", v);
}
}
void checkAppliesUDT(String condition, Object value) throws Throwable
{
assertRows(execute("UPDATE %s SET v = ? WHERE k = 0 IF " + condition, value), row(true));
assertRows(execute("SELECT * FROM %s"), row(0, value));
}
void checkDoesNotApplyUDT(String condition, Object value) throws Throwable
{
assertRows(execute("UPDATE %s SET v = ? WHERE k = 0 IF " + condition, value),
row(false, value));
assertRows(execute("SELECT * FROM %s"), row(0, value));
}
void checkInvalidUDT(String condition, Object value, Class<? extends Throwable> expected) throws Throwable
{
assertInvalidThrow(expected, "UPDATE %s SET v = ? WHERE k = 0 IF " + condition, value);
assertRows(execute("SELECT * FROM %s"), row(0, value));
}
/**
* Migrated from cql_tests.py:TestCQL.whole_list_conditional_test()
*/

View File

@ -87,7 +87,7 @@ public class LegacySchemaMigratorTest
}
// make sure that we've read *exactly* the same set of keyspaces/tables/types/functions
assertEquals(expected, actual);
assertEquals(expected.diff(actual).toString(), expected, actual);
// check that the build status of all indexes has been updated to use the new
// format of index name: the index_name column of system.IndexInfo used to
@ -311,17 +311,20 @@ public class LegacySchemaMigratorTest
UserType udt1 = new UserType(keyspace,
bytes("udt1"),
new ArrayList<ByteBuffer>() {{ add(bytes("col1")); add(bytes("col2")); }},
new ArrayList<AbstractType<?>>() {{ add(UTF8Type.instance); add(Int32Type.instance); }});
new ArrayList<AbstractType<?>>() {{ add(UTF8Type.instance); add(Int32Type.instance); }},
true);
UserType udt2 = new UserType(keyspace,
bytes("udt2"),
new ArrayList<ByteBuffer>() {{ add(bytes("col3")); add(bytes("col4")); }},
new ArrayList<AbstractType<?>>() {{ add(BytesType.instance); add(BooleanType.instance); }});
new ArrayList<AbstractType<?>>() {{ add(BytesType.instance); add(BooleanType.instance); }},
true);
UserType udt3 = new UserType(keyspace,
bytes("udt3"),
new ArrayList<ByteBuffer>() {{ add(bytes("col5")); }},
new ArrayList<AbstractType<?>>() {{ add(AsciiType.instance); }});
new ArrayList<AbstractType<?>>() {{ add(AsciiType.instance); }},
true);
return KeyspaceMetadata.create(keyspace,
KeyspaceParams.simple(1),
@ -431,12 +434,14 @@ public class LegacySchemaMigratorTest
UserType udt1 = new UserType(keyspace,
bytes("udt1"),
new ArrayList<ByteBuffer>() {{ add(bytes("col1")); add(bytes("col2")); }},
new ArrayList<AbstractType<?>>() {{ add(UTF8Type.instance); add(Int32Type.instance); }});
new ArrayList<AbstractType<?>>() {{ add(UTF8Type.instance); add(Int32Type.instance); }},
true);
UserType udt2 = new UserType(keyspace,
bytes("udt2"),
new ArrayList<ByteBuffer>() {{ add(bytes("col1")); add(bytes("col2")); }},
new ArrayList<AbstractType<?>>() {{ add(ListType.getInstance(udt1, false)); add(Int32Type.instance); }});
new ArrayList<AbstractType<?>>() {{ add(ListType.getInstance(udt1, false)); add(Int32Type.instance); }},
true);
UDFunction udf1 = UDFunction.create(new FunctionName(keyspace, "udf"),
ImmutableList.of(new ColumnIdentifier("col1", false), new ColumnIdentifier("col2", false)),
@ -478,12 +483,14 @@ public class LegacySchemaMigratorTest
UserType udt1 = new UserType(keyspace,
bytes("udt1"),
new ArrayList<ByteBuffer>() {{ add(bytes("col1")); add(bytes("col2")); }},
new ArrayList<AbstractType<?>>() {{ add(UTF8Type.instance); add(Int32Type.instance); }});
new ArrayList<AbstractType<?>>() {{ add(UTF8Type.instance); add(Int32Type.instance); }},
true);
UserType udt2 = new UserType(keyspace,
bytes("udt2"),
new ArrayList<ByteBuffer>() {{ add(bytes("col1")); add(bytes("col2")); }},
new ArrayList<AbstractType<?>>() {{ add(ListType.getInstance(udt1, false)); add(Int32Type.instance); }});
new ArrayList<AbstractType<?>>() {{ add(ListType.getInstance(udt1, false)); add(Int32Type.instance); }},
true);
UDFunction udf1 = UDFunction.create(new FunctionName(keyspace, "udf1"),
ImmutableList.of(new ColumnIdentifier("col1", false), new ColumnIdentifier("col2", false)),

View File

@ -184,7 +184,8 @@ public class SerDeserTest
UserType udt = new UserType("ks",
bb("myType"),
Arrays.asList(bb("f1"), bb("f2"), bb("f3"), bb("f4")),
Arrays.asList(LongType.instance, lt, st, mt));
Arrays.asList(LongType.instance, lt, st, mt),
true);
Map<ColumnIdentifier, Term.Raw> value = new HashMap<>();
value.put(ci("f1"), lit(42));