mirror of https://github.com/apache/cassandra
CQL3 refactor to allow conversion function
patch by slebresne; reviewed by iamaleksey for CASSANDRA-5226
This commit is contained in:
parent
1423fb1103
commit
31e669ab42
|
|
@ -13,6 +13,7 @@
|
|||
* Expose secondary indicies to the rest of nodetool (CASSANDRA-4464)
|
||||
* Binary protocol: avoid sending notification for 0.0.0.0 (CASSANDRA-5227)
|
||||
* add UseCondCardMark XX jvm settings on jdk 1.7 (CASSANDRA-4366)
|
||||
* CQL3 refactor to allow conversion function (CASSANDRA-5226)
|
||||
|
||||
|
||||
1.2.1
|
||||
|
|
|
|||
|
|
@ -84,37 +84,45 @@ bc(syntax)..
|
|||
<number> ::= <integer> | <float>
|
||||
<uuid> ::= a uuid constant
|
||||
<boolean> ::= a boolean constant
|
||||
<hex> ::= a blob constant
|
||||
|
||||
<final-term> ::= <string>
|
||||
| <number>
|
||||
| <uuid>
|
||||
| <boolean>
|
||||
<term> ::= <final-term>
|
||||
| '?'
|
||||
<int-term> ::= <integer>
|
||||
| '?'
|
||||
<constant> ::= <string>
|
||||
| <number>
|
||||
| <uuid>
|
||||
| <boolean>
|
||||
| <hex>
|
||||
<variable> ::= '?'
|
||||
<term> ::= <constant>
|
||||
| <collection-literal>
|
||||
| <variable>
|
||||
| <function> '(' (<term> (',' <term>)*)? ')'
|
||||
|
||||
<collection-literal> ::= <map-literal>
|
||||
| <set-literal>
|
||||
| <list-literal>
|
||||
<map-literal> ::= '{' ( <final-term> ':' <final-term> ( ',' <final-term> ':' <final-term> )* )? '}'
|
||||
<set-literal> ::= '{' ( <final-term> ( ',' <final-term> )* )? '}'
|
||||
<list-literal> ::= '[' ( <final-term> ( ',' <final-term> )* )? ']'
|
||||
<map-literal> ::= '{' ( <term> ':' <term> ( ',' <term> ':' <term> )* )? '}'
|
||||
<set-literal> ::= '{' ( <term> ( ',' <term> )* )? '}'
|
||||
<list-literal> ::= '[' ( <term> ( ',' <term> )* )? ']'
|
||||
|
||||
<function> ::= <ident>
|
||||
|
||||
<properties> ::= <property> (AND <property>)*
|
||||
<property> ::= <identifier> '=' ( <value> | <map-literal> )
|
||||
<value> ::= <identifier> | <string> | <number> | <boolean>
|
||||
<property> ::= <identifier> '=' ( <identifier> | <constant> | <map-literal> )
|
||||
p.
|
||||
The question mark (@?@) in the syntax above is a bind variables for "prepared statements":#preparedStatement.
|
||||
Please note that not every possible productions of the grammar above will be valid in practice. Most notably, @<variable>@ and nested @<collection-literal>@ are currently not allowed inside @<collection-literal>@.
|
||||
|
||||
The @<properties>@ production is use by statement that create and alter keyspaces and tables. Each @<property>@ is either a _simple_ one, in which case it just has a value, or a _map_ one, in which case it's value is a map grouping sub-options. The following will refer to one or the other as the _kind_ (_simple_ or _map_) of the property.
|
||||
p. The question mark (@?@) of @<variable>@ is a bind variables for "prepared statements":#preparedStatement.
|
||||
|
||||
p. The @<properties>@ production is use by statement that create and alter keyspaces and tables. Each @<property>@ is either a _simple_ one, in which case it just has a value, or a _map_ one, in which case it's value is a map grouping sub-options. The following will refer to one or the other as the _kind_ (_simple_ or _map_) of the property.
|
||||
|
||||
p. A @<tablename>@ will be used to identify a table. This is an identifier representing the table name that can be preceded by a keyspace name. The keyspace name, if provided, allow to identify a table in another keyspace than the currently active one (the currently active keyspace is set through the <a href="#useStmt"><tt>USE</tt></a> statement).
|
||||
|
||||
p. For supported @<function>@, see the section on "functions":#functions.
|
||||
|
||||
|
||||
h3(#preparedStatement). Prepared Statement
|
||||
|
||||
CQL supports _prepared statements_. Prepared statement is an optimization that allows to parse a query only once but execute it multiple times with differente concrete values.
|
||||
CQL supports _prepared statements_. Prepared statement is an optimization that allows to parse a query only once but execute it multiple times with different concrete values.
|
||||
|
||||
In a statement, each time a column value is expected (in the data manipulation and query statements), a bind variable marker (denoted by a @?@ symbol) can be used instead. A statement with bind variables must then be _prepared_. Once it has been prepared, it can executed by providing concrete values for the bind variables (values for bind variables must be provided in the order the bind variables are defined in the query string). The exact procedure to prepare a statement and execute a prepared statement depends on the CQL driver used and is beyond the scope of this document.
|
||||
|
||||
|
|
@ -569,15 +577,16 @@ bc(syntax)..
|
|||
( LIMIT <integer> )?
|
||||
( ALLOW FILTERING )?
|
||||
|
||||
<select-clause> ::= <column-list>
|
||||
<select-clause> ::= <selection-list>
|
||||
| COUNT '(' ( '*' | '1' ) ')'
|
||||
|
||||
<column-list> ::= <selected_id> ( ',' <selected_id> )*
|
||||
| '*'
|
||||
<selection-list> ::= <selector> ( ',' <selector> )*
|
||||
| '*'
|
||||
|
||||
<selected_id> ::= <identifier>
|
||||
| WRITETIME '(' <identifier> ')'
|
||||
| TTL '(' <identifier> ')'
|
||||
<selector> ::= <identifier>
|
||||
| WRITETIME '(' <identifier> ')'
|
||||
| TTL '(' <identifier> ')'
|
||||
| <function> '(' (<selector> (',' <selector>)*)? ')'
|
||||
|
||||
<where-clause> ::= <relation> ( "AND" <relation> )*
|
||||
|
||||
|
|
@ -605,9 +614,9 @@ The @SELECT@ statements reads one or more columns for one or more rows in a tabl
|
|||
|
||||
h4(#selectSelection). @<select-clause>@
|
||||
|
||||
The @<select-clause>@ determines which columns needs to be queried and returned in the result-set. It consists of either the comma-separated list of column names to query, or the wildcard character (@*@) to select all the columns defined for the table.
|
||||
The @<select-clause>@ determines which columns needs to be queried and returned in the result-set. It consists of either the comma-separated list of <selector> or the wildcard character (@*@) to select all the columns defined for the table.
|
||||
|
||||
In addition to selecting columns, the @WRITETIME@ (resp. @TTL@) function allows to select the timestamp of when the column was inserted (resp. the time to live (in seconds) for the column (or null if the column has no expiration set)).
|
||||
A @<selector>@ is either a column name to retrieve, or a @<function>@ of one or multiple column names. The functions allows are the same that for @<term>@ and are describe in the "function section":#function. In addition to these generic functions, the @WRITETIME@ (resp. @TTL@) function allows to select the timestamp of when the column was inserted (resp. the time to live (in seconds) for the column (or null if the column has no expiration set)).
|
||||
|
||||
The @COUNT@ keyword can be used with parenthesis enclosing @*@. If so, the query will return a single result: the number of rows matching the query. Note that @COUNT(1)@ is supported as an alias.
|
||||
|
||||
|
|
@ -739,7 +748,7 @@ p. The following table gives additional informations on the native data types, a
|
|||
|@int@ | integers |32-bit signed int|
|
||||
|@text@ | strings |UTF8 encoded string|
|
||||
|@timestamp@| integers, strings |A timestamp. Strings constant are allow to input timestamps as dates, see "Working with dates":#usingdates below for more information.|
|
||||
|@timeuuid@ | uuids |Type 1 UUID. This is generally used as a "conflict-free" timestamp. See "Working with @timeuuid@":#usingtimeuuid below.|
|
||||
|@timeuuid@ | uuids |Type 1 UUID. This is generally used as a "conflict-free" timestamp. Also see the "functions on Timeuuid":#timeuuidFun|
|
||||
|@uuid@ | uuids |Type 1 or type 4 UUID|
|
||||
|@varchar@ | strings |UTF8 encoded string|
|
||||
|@varint@ | integers |Arbitrary-precision integer|
|
||||
|
|
@ -775,30 +784,6 @@ The time of day may also be omitted, if the date is the only piece that matters:
|
|||
|
||||
In that case, the time of day will default to 00:00:00, in the specified or default time zone.
|
||||
|
||||
h3(#usingtimeuuid). Working with @timeuuid@
|
||||
|
||||
Values of the @timeuuid@ type are type 1 "UUID":http://en.wikipedia.org/wiki/Universally_unique_identifier, i.e. UUID that include the timestamp of their generation, and they sort accordingly to said timestamp. They thus serve as conflict-free timestamps.
|
||||
|
||||
Valid @timeuuid@ values should be inputed using UUID constants described "here":#constants. However, a number of convenience method are provided to interact with @timeuuid@.
|
||||
|
||||
First, the method @now@ generates a new unique timeuuid (at the time where the statement using it is executed). Note that this method is useful for insertion but is largely non-sensical in @WHERE@ clauses. For instance, a query of the form
|
||||
|
||||
bc(sample).
|
||||
SELECT * FROM myTable WHERE t = now()
|
||||
|
||||
will never return any result by design, since the value returned by @now()@ is guaranteed to be unique.
|
||||
|
||||
For querying, the method @minTimeuuid@ (resp. @maxTimeuuid@) takes a date @d@ in argument and returns a _fake_ @timeuuid@ corresponding to the _smallest_ (resp. _biggest_) possible @timeuuid@ having for date @d@. So for instance:
|
||||
|
||||
bc(sample).
|
||||
SELECT * FROM myTable WHERE t > maxTimeuuid('2013-01-01 00:05+0000') AND t < minTimeuuid('2013-02-02 10:00+0000')
|
||||
|
||||
will select all rows where the @timeuuid@ column @t@ is strictly older than '2013-01-01 00:05+0000' but stricly younger than '2013-02-02 10:00+0000'. Please note that @t >= maxTimeuuid('2013-01-01 00:05+0000')@ would still _not_ select a @timeuuid@ generated exactly at '2013-01-01 00:05+0000' and is essentially equivalent to @t > maxTimeuuid('2013-01-01 00:05+0000')@.
|
||||
|
||||
_Warning_: We called the values generated by @minTimeuuid@ and @maxTimeuuid@ _fake_ UUID because they do no respect the Time-Based UUID generation process specified by the "RFC 4122":http://www.ietf.org/rfc/rfc4122.txt. In particular, the value returned by these 2 methods will not be unique. This means you should only use those methods for querying (as in the example above). Inserting the result of those methods is almost certainly _a bad idea_.
|
||||
|
||||
Lastly, the @dateOf@ and @unixTimestampOf@ methods can used in @SELECT@ clauses to extract the timestamp of a @timeuuid@ column in a resultset. The difference between the @dateOf@ and @unixTimestampOf@ is that the former return the extract timestamp as a date, while the latter returns it as a raw timestamp (i.e. a 64 bits integer).
|
||||
|
||||
|
||||
h3(#counters). Counters
|
||||
|
||||
|
|
@ -913,6 +898,61 @@ UPDATE plays SET scores = scores - [ 12, 21 ] WHERE id = '123-afde'; // removes
|
|||
As with "maps":#map, TTLs if used only apply to the newly inserted/updated _values_.
|
||||
|
||||
|
||||
h2(#functions). Functions
|
||||
|
||||
CQL3 supports a few functions (more to come). Currently, it only support functions on values (functions that transform one or more column values into a new value) and in particular aggregation functions are not supported. The functions supported are described below:
|
||||
|
||||
h3(#tokenFun). Token
|
||||
|
||||
The @token@ function allows to compute the token for a given partition key. The exact signature of the token function depends on the table concerned and of the partitioner used by the cluster.
|
||||
|
||||
The type of the arguments of the @token@ depend on the type of the partition key columns. The return type depend on the partitioner in use:
|
||||
* For Murmur3Partitioner, the return type is @bigint@.
|
||||
* For RandomPartitioner, the return type is @varint@.
|
||||
* For ByteOrderedPartitioner, the return type is @blob@.
|
||||
|
||||
For instance, in a cluster using the default Murmur3Partitioner, if a table is defined by
|
||||
|
||||
bc(sample).
|
||||
CREATE TABLE users (
|
||||
userid text PRIMARY KEY,
|
||||
username text,
|
||||
...
|
||||
)
|
||||
|
||||
then the @token@ function will take a single argument of type @text@ (in that case, the partition key is @userid@ (there is no clustering key so the partition key is the same than the primary key)), and the return type will be @bigint@.
|
||||
|
||||
h3(#timeuuidFun). Timeuuid functions
|
||||
|
||||
h4. @now@
|
||||
|
||||
The @now@ function takes no arguments and generates a new unique timeuuid (at the time where the statement using it is executed). Note that this method is useful for insertion but is largely non-sensical in @WHERE@ clauses. For instance, a query of the form
|
||||
|
||||
bc(sample).
|
||||
SELECT * FROM myTable WHERE t = now()
|
||||
|
||||
will never return any result by design, since the value returned by @now()@ is guaranteed to be unique.
|
||||
|
||||
h4. @minTimeuuid@ and @maxTimeuuid@
|
||||
|
||||
The @minTimeuuid@ (resp. @maxTimeuuid@) function takes a @timestamp@ value @t@ (which can be "either a timestamp or a date string":#usingdates) and return a _fake_ @timeuuid@ corresponding to the _smallest_ (resp. _biggest_) possible @timeuuid@ having for timestamp @t@. So for instance:
|
||||
|
||||
bc(sample).
|
||||
SELECT * FROM myTable WHERE t > maxTimeuuid('2013-01-01 00:05+0000') AND t < minTimeuuid('2013-02-02 10:00+0000')
|
||||
|
||||
will select all rows where the @timeuuid@ column @t@ is strictly older than '2013-01-01 00:05+0000' but stricly younger than '2013-02-02 10:00+0000'. Please note that @t >= maxTimeuuid('2013-01-01 00:05+0000')@ would still _not_ select a @timeuuid@ generated exactly at '2013-01-01 00:05+0000' and is essentially equivalent to @t > maxTimeuuid('2013-01-01 00:05+0000')@.
|
||||
|
||||
_Warning_: We called the values generated by @minTimeuuid@ and @maxTimeuuid@ _fake_ UUID because they do no respect the Time-Based UUID generation process specified by the "RFC 4122":http://www.ietf.org/rfc/rfc4122.txt. In particular, the value returned by these 2 methods will not be unique. This means you should only use those methods for querying (as in the example above). Inserting the result of those methods is almost certainly _a bad idea_.
|
||||
|
||||
h4. @dateOf@ and @unixTimestampOf@
|
||||
|
||||
The @dateOf@ and @unixTimestampOf@ functions take a @timeuuid@ argument and extract the embeded timestamp. However, while the @dateof@ function return it with the @timestamp@ type (that most client, including cqlsh, interpret as a date), the @unixTimestampOf@ function returns it as a @bigint@ raw value.
|
||||
|
||||
h3(#blobFun). Blob conversion functions
|
||||
|
||||
A number of functions are provided to "convert" the native types into binary data (@blob@). For every @<native-type>@ @type@ supported by CQL3 (a notable exceptions is @blob@, for obvious reasons), the function @typeAsBlob@ takes a argument of type @type@ and return it as a @blob@. Conversely, the function @blobAsType@ takes a 64-bit @blob@ argument and convert it to a @bigint@ value. And so for instance, @bigintAsBlob(3)@ is @0x0000000000000003@ and @blobAsBigint(0x0000000000000003)@ is @3@.
|
||||
|
||||
|
||||
h2(#appendixA). Appendix A: CQL Keywords
|
||||
|
||||
CQL distinguishes between _reserved_ and _non-reserved_ keywords. Reserved keywords cannot be used as identifier, they are truly reserved for the language (but one can enclose a reserved keyword by double-quotes to use it as an identifier). Non-reserved keywords however only have a specific meaning in certain context but can used as identifer otherwise. The only _raison d'être_ of these non-reserved keywords is convenience: some keyword are non-reserved when it was always easy for the parser to decide whether they were used as keywords or not.
|
||||
|
|
@ -1007,13 +1047,14 @@ The following describes the addition/changes brought for each version of CQL.
|
|||
|
||||
h3. 3.0.2
|
||||
|
||||
- Type validation for the "constants":#constants has been fixed. For instance, the implementation used to allow @'2'@ as a valid value for an @int@ column (interpreting it has the equivalent of @2@), or @42@ as a valid @blob@ value (in which case @42@ was interpreted as an hexadecimal representation of the blob). This is no longer the case, type validation of constants is now more strict. See the "data types":#dataTypes section for details on which constant is allowed for which type.
|
||||
- The type validation fixed of the previous point has lead to the introduction of "blobs constants":#constants to allow inputing blobs. Do note that while inputing blobs as strings constant is still supported by this version (to allow smoother transition to blob constant), it is now deprecated (in particular the "data types":#dataTypes section does not list strings constants as valid blobs) and will be removed by a future version. If you were using strings as blobs, you should thus update your client code asap to switch blob constants.
|
||||
* Type validation for the "constants":#constants has been fixed. For instance, the implementation used to allow @'2'@ as a valid value for an @int@ column (interpreting it has the equivalent of @2@), or @42@ as a valid @blob@ value (in which case @42@ was interpreted as an hexadecimal representation of the blob). This is no longer the case, type validation of constants is now more strict. See the "data types":#types section for details on which constant is allowed for which type.
|
||||
* The type validation fixed of the previous point has lead to the introduction of "blobs constants":#constants to allow inputing blobs. Do note that while inputing blobs as strings constant is still supported by this version (to allow smoother transition to blob constant), it is now deprecated (in particular the "data types":#types section does not list strings constants as valid blobs) and will be removed by a future version. If you were using strings as blobs, you should thus update your client code asap to switch blob constants.
|
||||
* A number of functions to convert native types to blobs have also been introduced. Furthermore the token function is now also allowed in select clauses. See the "section on functions":#functions for details.
|
||||
|
||||
h3. 3.0.1
|
||||
|
||||
- "Date strings":#usingdates (and timestamps) are no longer accepted as valid @timeuuid@ values. Doing so was a bug in the sense that date string are not valid @timeuuid@, and it was thus resulting in "confusing behaviors":https://issues.apache.org/jira/browse/CASSANDRA-4936. However, the following new methods have been added to help working with @timeuuid@: @now@, @minTimeuuid@, @maxTimeuuid@ , @dateOf@ and @unixTimestampOf@. See the "section dedicated to these methods":#usingtimeuuid for more detail.
|
||||
- "Float constants"#constants now support the exponent notation. In other words, @4.2E10@ is now a valid floating point value.
|
||||
* "Date strings":#usingdates (and timestamps) are no longer accepted as valid @timeuuid@ values. Doing so was a bug in the sense that date string are not valid @timeuuid@, and it was thus resulting in "confusing behaviors":https://issues.apache.org/jira/browse/CASSANDRA-4936. However, the following new methods have been added to help working with @timeuuid@: @now@, @minTimeuuid@, @maxTimeuuid@ , @dateOf@ and @unixTimestampOf@. See the "section dedicated to these methods":#usingtimeuuid for more detail.
|
||||
* "Float constants"#constants now support the exponent notation. In other words, @4.2E10@ is now a valid floating point value.
|
||||
|
||||
|
||||
h2. Versioning
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
|
||||
/**
|
||||
* A single bind marker.
|
||||
*/
|
||||
public abstract class AbstractMarker extends Term.NonTerminal
|
||||
{
|
||||
protected final int bindIndex;
|
||||
protected final ColumnSpecification receiver;
|
||||
|
||||
protected AbstractMarker(int bindIndex, ColumnSpecification receiver)
|
||||
{
|
||||
this.bindIndex = bindIndex;
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
public void collectMarkerSpecification(ColumnSpecification[] boundNames)
|
||||
{
|
||||
boundNames[bindIndex] = receiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* A parsed, but non prepared, bind marker.
|
||||
*/
|
||||
public static class Raw implements Term.Raw
|
||||
{
|
||||
protected final int bindIndex;
|
||||
|
||||
public Raw(int bindIndex)
|
||||
{
|
||||
this.bindIndex = bindIndex;
|
||||
}
|
||||
|
||||
public AbstractMarker prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!(receiver.type instanceof CollectionType))
|
||||
return new Constants.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);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
public interface AssignementTestable
|
||||
{
|
||||
/**
|
||||
* @return whether this object can be assigned to the provided receiver
|
||||
*/
|
||||
public boolean isAssignableTo(ColumnSpecification receiver);
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
|
|||
import java.util.*;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
|
|
@ -293,6 +294,26 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
|
|||
|
||||
public final Kind kind;
|
||||
public final int position; // only make sense for KEY_ALIAS and COLUMN_ALIAS
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(!(o instanceof Name))
|
||||
return false;
|
||||
Name that = (Name)o;
|
||||
return Objects.equal(ksName, that.ksName)
|
||||
&& Objects.equal(cfName, that.cfName)
|
||||
&& Objects.equal(name, that.name)
|
||||
&& Objects.equal(type, that.type)
|
||||
&& kind == that.kind
|
||||
&& position == that.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode()
|
||||
{
|
||||
return Objects.hashCode(ksName, cfName, name, type, kind, position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -329,14 +350,9 @@ public class CFDefinition implements Iterable<CFDefinition.Name>
|
|||
return this;
|
||||
}
|
||||
|
||||
public NonCompositeBuilder add(Term t, Relation.Type op, List<ByteBuffer> variables) throws InvalidRequestException
|
||||
public NonCompositeBuilder add(ByteBuffer bb, Relation.Type op)
|
||||
{
|
||||
if (columnName != null)
|
||||
throw new IllegalStateException("Column name is already constructed");
|
||||
|
||||
// We don't support the relation type yet, i.e., there is no distinction between x > 3 and x >= 3.
|
||||
columnName = t.getByteBuffer(type, variables);
|
||||
return this;
|
||||
return add(bb);
|
||||
}
|
||||
|
||||
public int componentCount()
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ import java.nio.ByteBuffer;
|
|||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
import org.apache.cassandra.cql3.statements.Selector;
|
||||
import org.apache.cassandra.cql3.statements.RawSelector;
|
||||
|
||||
/**
|
||||
* Represents an identifer for a CQL column definition.
|
||||
*/
|
||||
public class ColumnIdentifier extends Selector implements Comparable<ColumnIdentifier>
|
||||
public class ColumnIdentifier implements RawSelector, Comparable<ColumnIdentifier>
|
||||
{
|
||||
public final ByteBuffer key;
|
||||
private final String text;
|
||||
|
|
@ -70,9 +70,4 @@ public class ColumnIdentifier extends Selector implements Comparable<ColumnIdent
|
|||
{
|
||||
return key.compareTo(other.key);
|
||||
}
|
||||
|
||||
public ColumnIdentifier id()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,13 @@ public interface ColumnNameBuilder
|
|||
public ColumnNameBuilder add(ByteBuffer bb);
|
||||
|
||||
/**
|
||||
* Add a new Term as the next component for this name.
|
||||
* @param t the Term to add
|
||||
* Add a new ByteBuffer as the next component for this name.
|
||||
* @param bb the ByteBuffer to add
|
||||
* @param op the relationship this component should respect.
|
||||
* @param variables the variables corresponding to prepared markers
|
||||
* @throws IllegalStateException if the builder if full, i.e. if enough component has been added.
|
||||
* @return this builder
|
||||
*/
|
||||
public ColumnNameBuilder add(Term t, Relation.Type op, List<ByteBuffer> variables) throws InvalidRequestException;
|
||||
public ColumnNameBuilder add(ByteBuffer t, Relation.Type op);
|
||||
|
||||
/**
|
||||
* Returns the number of component already added to this builder.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.filter.QueryPath;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.BytesType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.CounterColumnType;
|
||||
import org.apache.cassandra.db.marshal.LongType;
|
||||
import org.apache.cassandra.db.marshal.MarshalException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
/**
|
||||
* Static helper methods and classes for constants.
|
||||
*/
|
||||
public abstract class Constants
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Constants.class);
|
||||
|
||||
public enum Type
|
||||
{
|
||||
STRING, INTEGER, UUID, FLOAT, BOOLEAN, HEX;
|
||||
}
|
||||
|
||||
public static class Literal implements Term.Raw
|
||||
{
|
||||
private final Type type;
|
||||
private final String text;
|
||||
|
||||
// For transition post-5198, see below
|
||||
private static volatile boolean stringAsBlobWarningLogged = false;
|
||||
|
||||
private Literal(Type type, String text)
|
||||
{
|
||||
assert type != null && text != null;
|
||||
this.type = type;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public static Literal string(String text)
|
||||
{
|
||||
return new Literal(Type.STRING, text);
|
||||
}
|
||||
|
||||
public static Literal integer(String text)
|
||||
{
|
||||
return new Literal(Type.INTEGER, text);
|
||||
}
|
||||
|
||||
public static Literal floatingPoint(String text)
|
||||
{
|
||||
return new Literal(Type.FLOAT, text);
|
||||
}
|
||||
|
||||
public static Literal uuid(String text)
|
||||
{
|
||||
return new Literal(Type.UUID, text);
|
||||
}
|
||||
|
||||
public static Literal bool(String text)
|
||||
{
|
||||
return new Literal(Type.BOOLEAN, text);
|
||||
}
|
||||
|
||||
public static Literal hex(String text)
|
||||
{
|
||||
return new Literal(Type.HEX, text);
|
||||
}
|
||||
|
||||
public Value prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!isAssignableTo(receiver))
|
||||
throw new InvalidRequestException(String.format("Invalid %s constant (%s) for %s of type %s", type, text, receiver, receiver.type.asCQL3Type()));
|
||||
|
||||
return new Value(parsedValue(receiver.type));
|
||||
}
|
||||
|
||||
private ByteBuffer parsedValue(AbstractType<?> validator) throws InvalidRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
// BytesType doesn't want it's input prefixed by '0x'.
|
||||
if (type == Type.HEX && validator instanceof BytesType)
|
||||
return validator.fromString(text.substring(2));
|
||||
if (validator instanceof CounterColumnType)
|
||||
return LongType.instance.fromString(text);
|
||||
return validator.fromString(text);
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String getRawText()
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
CQL3Type receiverType = receiver.type.asCQL3Type();
|
||||
if (receiverType.isCollection())
|
||||
return false;
|
||||
|
||||
if (!(receiverType instanceof CQL3Type.Native))
|
||||
// Skip type validation for custom types. May or may not be a good idea
|
||||
return true;
|
||||
|
||||
CQL3Type.Native nt = (CQL3Type.Native)receiverType;
|
||||
switch (type)
|
||||
{
|
||||
case STRING:
|
||||
switch (nt)
|
||||
{
|
||||
case ASCII:
|
||||
case TEXT:
|
||||
case INET:
|
||||
case VARCHAR:
|
||||
case TIMESTAMP:
|
||||
return true;
|
||||
case BLOB:
|
||||
// Blobs should now be inputed as hexadecimal constants. However, to allow people to upgrade, we still allow
|
||||
// blob-as-strings, even though it is deprecated (see #5198).
|
||||
if (!stringAsBlobWarningLogged)
|
||||
{
|
||||
stringAsBlobWarningLogged = true;
|
||||
logger.warn("Inputing CLQ3 blobs as strings (like {} = '{}') is now deprecated and will be removed in a future version. "
|
||||
+ "You should convert client code to use a blob constant ({} = {}) instead (see http://cassandra.apache.org/doc/cql3/CQL.html "
|
||||
+ "changelog section for more info).",
|
||||
new Object[]{receiver, text, receiver, "0x" + text});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case INTEGER:
|
||||
switch (nt)
|
||||
{
|
||||
case BIGINT:
|
||||
case COUNTER:
|
||||
case DECIMAL:
|
||||
case DOUBLE:
|
||||
case FLOAT:
|
||||
case INT:
|
||||
case TIMESTAMP:
|
||||
case VARINT:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case UUID:
|
||||
switch (nt)
|
||||
{
|
||||
case UUID:
|
||||
case TIMEUUID:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case FLOAT:
|
||||
switch (nt)
|
||||
{
|
||||
case DECIMAL:
|
||||
case DOUBLE:
|
||||
case FLOAT:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case BOOLEAN:
|
||||
switch (nt)
|
||||
{
|
||||
case BOOLEAN:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case HEX:
|
||||
switch (nt)
|
||||
{
|
||||
case BLOB:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return type == Type.STRING ? String.format("'%s'", text) : text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A constant value, i.e. a ByteBuffer.
|
||||
*/
|
||||
public static class Value extends Term.Terminal
|
||||
{
|
||||
public final ByteBuffer bytes;
|
||||
|
||||
public Value(ByteBuffer bytes)
|
||||
{
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public ByteBuffer get()
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer bindAndGet(List<ByteBuffer> values)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Marker extends AbstractMarker
|
||||
{
|
||||
protected Marker(int bindIndex, ColumnSpecification receiver)
|
||||
{
|
||||
super(bindIndex, receiver);
|
||||
assert !(receiver.type instanceof CollectionType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer bindAndGet(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteBuffer value = values.get(bindIndex);
|
||||
receiver.type.validate(value);
|
||||
return value;
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public Value bind(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
return new Constants.Value(bindAndGet(values));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Setter extends Operation
|
||||
{
|
||||
public Setter(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
ByteBuffer cname = columnName == null ? prefix.build() : prefix.add(columnName.key).build();
|
||||
cf.addColumn(params.makeColumn(cname, t.bindAndGet(params.variables)));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Adder extends Operation
|
||||
{
|
||||
public Adder(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
long increment = ByteBufferUtil.toLong(t.bindAndGet(params.variables));
|
||||
ByteBuffer cname = columnName == null ? prefix.build() : prefix.add(columnName.key).build();
|
||||
cf.addCounter(new QueryPath(cf.metadata().cfName, null, cname), increment);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Substracter extends Operation
|
||||
{
|
||||
public Substracter(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
long increment = ByteBufferUtil.toLong(t.bindAndGet(params.variables));
|
||||
if (increment == Long.MIN_VALUE)
|
||||
throw new InvalidRequestException("The negation of " + increment + " overflows supported counter precision (signed 8 bytes integer)");
|
||||
|
||||
ByteBuffer cname = columnName == null ? prefix.build() : prefix.add(columnName.key).build();
|
||||
cf.addCounter(new QueryPath(cf.metadata().cfName, null, cname), -increment);
|
||||
}
|
||||
}
|
||||
|
||||
// This happens to also handle collection because it doesn't felt worth
|
||||
// duplicating this further
|
||||
public static class Deleter extends Operation
|
||||
{
|
||||
private final boolean isCollection;
|
||||
|
||||
public Deleter(ColumnIdentifier column, boolean isCollection)
|
||||
{
|
||||
super(column, null);
|
||||
this.isCollection = isCollection;
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
ColumnNameBuilder column = prefix.add(columnName.key);
|
||||
|
||||
if (isCollection)
|
||||
cf.addAtom(params.makeRangeTombstone(column.build(), column.buildAsEndOfRange()));
|
||||
else
|
||||
cf.addColumn(params.makeTombstone(column.build()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -39,8 +39,9 @@ options {
|
|||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.auth.DataResource;
|
||||
import org.apache.cassandra.auth.IResource;
|
||||
import org.apache.cassandra.cql3.operations.*;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.statements.*;
|
||||
import org.apache.cassandra.cql3.functions.FunctionCall;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
|
@ -75,32 +76,47 @@ options {
|
|||
throw new SyntaxException(recognitionErrors.get((recognitionErrors.size()-1)));
|
||||
}
|
||||
|
||||
// used by UPDATE of the counter columns to validate if '-' was supplied by user
|
||||
public void validateMinusSupplied(Object op, final Term value, IntStream stream) throws MissingTokenException
|
||||
public Map<String, String> convertPropertyMap(Maps.Literal map)
|
||||
{
|
||||
if (op == null && Long.parseLong(value.getText()) > 0)
|
||||
throw new MissingTokenException(102, stream, value);
|
||||
}
|
||||
|
||||
public Map<String, String> convertMap(Map<Term, Term> terms)
|
||||
{
|
||||
if (terms == null || terms.isEmpty())
|
||||
if (map == null || map.entries == null || map.entries.isEmpty())
|
||||
return Collections.<String, String>emptyMap();
|
||||
|
||||
Map<String, String> res = new HashMap<String, String>(terms.size());
|
||||
Map<String, String> res = new HashMap<String, String>(map.entries.size());
|
||||
|
||||
for (Map.Entry<Term, Term> entry : terms.entrySet())
|
||||
for (Pair<Term.Raw, Term.Raw> entry : map.entries)
|
||||
{
|
||||
// Because the parser tries to be smart and recover on error (to
|
||||
// allow displaying more than one error I suppose), we have null
|
||||
// entries in there. Just skip those, a proper error will be thrown in the end.
|
||||
if (entry.getKey() == null || entry.getValue() == null)
|
||||
if (entry.left == null || entry.right == null)
|
||||
break;
|
||||
res.put(entry.getKey().getText(), entry.getValue().getText());
|
||||
|
||||
if (!(entry.left instanceof Constants.Literal))
|
||||
{
|
||||
addRecognitionError("Invalid property name: " + entry.left);
|
||||
break;
|
||||
}
|
||||
if (!(entry.right instanceof Constants.Literal))
|
||||
{
|
||||
addRecognitionError("Invalid property value: " + entry.right);
|
||||
break;
|
||||
}
|
||||
|
||||
res.put(((Constants.Literal)entry.left).getRawText(), ((Constants.Literal)entry.right).getRawText());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public void addRawUpdate(List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations, ColumnIdentifier key, Operation.RawUpdate update)
|
||||
{
|
||||
for (Pair<ColumnIdentifier, Operation.RawUpdate> p : operations)
|
||||
{
|
||||
if (p.left.equals(key) && !p.right.isCompatibleWith(update))
|
||||
addRecognitionError("Multiple incompatible setting of column " + key);
|
||||
}
|
||||
operations.add(Pair.create(key, update));
|
||||
}
|
||||
}
|
||||
|
||||
@lexer::header {
|
||||
|
|
@ -214,22 +230,28 @@ selectStatement returns [SelectStatement.RawStatement expr]
|
|||
}
|
||||
;
|
||||
|
||||
selectClause returns [List<Selector> expr]
|
||||
: t1=selector { $expr = new ArrayList<Selector>(); $expr.add(t1); } (',' tN=selector { $expr.add(tN); })*
|
||||
| '\*' { $expr = Collections.<Selector>emptyList();}
|
||||
selectClause returns [List<RawSelector> expr]
|
||||
: t1=selector { $expr = new ArrayList<RawSelector>(); $expr.add(t1); } (',' tN=selector { $expr.add(tN); })*
|
||||
| '\*' { $expr = Collections.<RawSelector>emptyList();}
|
||||
;
|
||||
|
||||
selector returns [Selector s]
|
||||
: c=cident { $s = c; }
|
||||
| K_WRITETIME '(' c=cident ')' { $s = new Selector.WithFunction(c, Selector.Function.WRITE_TIME); }
|
||||
| K_TTL '(' c=cident ')' { $s = new Selector.WithFunction(c, Selector.Function.TTL); }
|
||||
| K_DATE_OF '(' c=cident ')' { $s = new Selector.WithFunction(c, Selector.Function.DATE_OF); }
|
||||
| K_UNIXTIMESTAMP_OF '(' c=cident ')' { $s = new Selector.WithFunction(c, Selector.Function.UNIXTIMESTAMP_OF); }
|
||||
selectionFunctionArgs returns [List<RawSelector> a]
|
||||
: '(' ')' { $a = Collections.emptyList(); }
|
||||
| '(' s1=selector { List<RawSelector> args = new ArrayList<RawSelector>(); args.add(s1); }
|
||||
( ',' sn=selector { args.add(sn); } )*
|
||||
')' { $a = args; }
|
||||
;
|
||||
|
||||
selectCountClause returns [List<Selector> expr]
|
||||
: '\*' { $expr = Collections.<Selector>emptyList();}
|
||||
| i=INTEGER { if (!i.getText().equals("1")) addRecognitionError("Only COUNT(1) is supported, got COUNT(" + i.getText() + ")"); $expr = Collections.<Selector>emptyList();}
|
||||
selector returns [RawSelector s]
|
||||
: c=cident { $s = c; }
|
||||
| K_WRITETIME '(' c=cident ')' { $s = new RawSelector.WritetimeOrTTL(c, true); }
|
||||
| K_TTL '(' c=cident ')' { $s = new RawSelector.WritetimeOrTTL(c, false); }
|
||||
| f=functionName args=selectionFunctionArgs { $s = new RawSelector.WithFunction(f, args); }
|
||||
;
|
||||
|
||||
selectCountClause returns [List<RawSelector> expr]
|
||||
: '\*' { $expr = Collections.<RawSelector>emptyList();}
|
||||
| i=INTEGER { if (!i.getText().equals("1")) addRecognitionError("Only COUNT(1) is supported, got COUNT(" + i.getText() + ")"); $expr = Collections.<RawSelector>emptyList();}
|
||||
;
|
||||
|
||||
whereClause returns [List<Relation> clause]
|
||||
|
|
@ -255,15 +277,15 @@ insertStatement returns [UpdateStatement expr]
|
|||
@init {
|
||||
Attributes attrs = new Attributes();
|
||||
List<ColumnIdentifier> columnNames = new ArrayList<ColumnIdentifier>();
|
||||
List<Operation> columnOperations = new ArrayList<Operation>();
|
||||
List<Term.Raw> values = new ArrayList<Term.Raw>();
|
||||
}
|
||||
: K_INSERT K_INTO cf=columnFamilyName
|
||||
'(' c1=cident { columnNames.add(c1); } ( ',' cn=cident { columnNames.add(cn); } )* ')'
|
||||
K_VALUES
|
||||
'(' v1=set_operation { columnOperations.add(v1); } ( ',' vn=set_operation { columnOperations.add(vn); } )* ')'
|
||||
'(' v1=term { values.add(v1); } ( ',' vn=term { values.add(vn); } )* ')'
|
||||
( usingClause[attrs] )?
|
||||
{
|
||||
$expr = new UpdateStatement(cf, attrs, columnNames, columnOperations);
|
||||
$expr = new UpdateStatement(cf, attrs, columnNames, values);
|
||||
}
|
||||
;
|
||||
|
||||
|
|
@ -293,14 +315,14 @@ usingClauseObjective[Attributes attrs]
|
|||
updateStatement returns [UpdateStatement expr]
|
||||
@init {
|
||||
Attributes attrs = new Attributes();
|
||||
List<Pair<ColumnIdentifier, Operation>> columns = new ArrayList<Pair<ColumnIdentifier, Operation>>();
|
||||
List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations = new ArrayList<Pair<ColumnIdentifier, Operation.RawUpdate>>();
|
||||
}
|
||||
: K_UPDATE cf=columnFamilyName
|
||||
( usingClause[attrs] )?
|
||||
K_SET termPairWithOperation[columns] (',' termPairWithOperation[columns])*
|
||||
K_SET columnOperation[operations] (',' columnOperation[operations])*
|
||||
K_WHERE wclause=whereClause
|
||||
{
|
||||
return new UpdateStatement(cf, columns, wclause, attrs);
|
||||
return new UpdateStatement(cf, operations, wclause, attrs);
|
||||
}
|
||||
;
|
||||
|
||||
|
|
@ -313,24 +335,26 @@ updateStatement returns [UpdateStatement expr]
|
|||
deleteStatement returns [DeleteStatement expr]
|
||||
@init {
|
||||
Attributes attrs = new Attributes();
|
||||
List<Selector> columnsList = Collections.emptyList();
|
||||
List<Operation.RawDeletion> columnDeletions = Collections.emptyList();
|
||||
}
|
||||
: K_DELETE ( ids=deleteSelection { columnsList = ids; } )?
|
||||
: K_DELETE ( dels=deleteSelection { columnDeletions = dels; } )?
|
||||
K_FROM cf=columnFamilyName
|
||||
( usingClauseDelete[attrs] )?
|
||||
K_WHERE wclause=whereClause
|
||||
{
|
||||
return new DeleteStatement(cf, columnsList, wclause, attrs);
|
||||
return new DeleteStatement(cf, columnDeletions, wclause, attrs);
|
||||
}
|
||||
;
|
||||
|
||||
deleteSelection returns [List<Selector> expr]
|
||||
: t1=deleteSelector { $expr = new ArrayList<Selector>(); $expr.add(t1); } (',' tN=deleteSelector { $expr.add(tN); })*
|
||||
deleteSelection returns [List<Operation.RawDeletion> operations]
|
||||
: { $operations = new ArrayList<Operation.RawDeletion>(); }
|
||||
t1=deleteOp { $operations.add(t1); }
|
||||
(',' tN=deleteOp { $operations.add(tN); })*
|
||||
;
|
||||
|
||||
deleteSelector returns [Selector s]
|
||||
: c=cident { $s = c; }
|
||||
| c=cident '[' t=term ']' { $s = new Selector.WithKey(c, t); }
|
||||
deleteOp returns [Operation.RawDeletion op]
|
||||
: c=cident { $op = new Operation.ColumnDeletion(c); }
|
||||
| c=cident '[' t=term ']' { $op = new Operation.ElementDeletion(c, t); }
|
||||
;
|
||||
|
||||
/**
|
||||
|
|
@ -638,97 +662,102 @@ cfOrKsName[CFName name, boolean isKs]
|
|||
| k=unreserved_keyword { if (isKs) $name.setKeyspace(k, false); else $name.setColumnFamily(k, false); }
|
||||
;
|
||||
|
||||
set_operation returns [Operation op]
|
||||
: t=finalTerm { $op = ColumnOperation.Set(t); }
|
||||
| mk=QMARK { $op = new PreparedOperation(new Term($mk.text, $mk.type, ++currentBindMarkerIdx), PreparedOperation.Kind.SET); }
|
||||
| m=map_literal { $op = MapOperation.Set(m); }
|
||||
| l=list_literal { $op = ListOperation.Set(l); }
|
||||
| s=set_literal { $op = SetOperation.Set(s); }
|
||||
constant returns [Constants.Literal constant]
|
||||
: t=STRING_LITERAL { $constant = Constants.Literal.string($t.text); }
|
||||
| t=INTEGER { $constant = Constants.Literal.integer($t.text); }
|
||||
| t=FLOAT { $constant = Constants.Literal.floatingPoint($t.text); }
|
||||
| t=BOOLEAN { $constant = Constants.Literal.bool($t.text); }
|
||||
| t=UUID { $constant = Constants.Literal.uuid($t.text); }
|
||||
| t=HEXNUMBER { $constant = Constants.Literal.hex($t.text); }
|
||||
;
|
||||
|
||||
list_literal returns [List<Term> value]
|
||||
: '[' { List<Term> l = new ArrayList<Term>(); } ( t1=finalTerm { l.add(t1); } ( ',' tn=finalTerm { l.add(tn); } )* )? ']' { $value = l; }
|
||||
set_tail[List<Term.Raw> s]
|
||||
: '}'
|
||||
| ',' t=term { s.add(t); } set_tail[s]
|
||||
;
|
||||
|
||||
set_literal returns [List<Term> value]
|
||||
: '{' { List<Term> s = new ArrayList<Term>(); } ( t1=finalTerm { s.add(t1); } ( ',' tn=finalTerm { s.add(tn); } )* )? '}' { $value = s; }
|
||||
map_tail[List<Pair<Term.Raw, Term.Raw>> m]
|
||||
: '}'
|
||||
| ',' k=term ':' v=term { m.add(Pair.create(k, v)); } map_tail[m]
|
||||
;
|
||||
|
||||
map_literal returns [Map<Term, Term> value]
|
||||
// Note that we have an ambiguity between maps and set for "{}". So we force it to a set, and deal with it later based on the type of the column
|
||||
: '{' { Map<Term, Term> m = new HashMap<Term, Term>(); }
|
||||
k1=finalTerm ':' v1=finalTerm { m.put(k1, v1); } ( ',' kn=finalTerm ':' vn=finalTerm { m.put(kn, vn); } )* '}'
|
||||
{ $value = m; }
|
||||
map_literal returns [Maps.Literal map]
|
||||
: '{' '}' { $map = new Maps.Literal(Collections.<Pair<Term.Raw, Term.Raw>>emptyList()); }
|
||||
| '{' { List<Pair<Term.Raw, Term.Raw>> m = new ArrayList<Pair<Term.Raw, Term.Raw>>(); }
|
||||
k1=term ':' v1=term { m.add(Pair.create(k1, v1)); } map_tail[m]
|
||||
{ $map = new Maps.Literal(m); }
|
||||
;
|
||||
|
||||
finalTerm returns [Term term]
|
||||
: t=(STRING_LITERAL | UUID | INTEGER | FLOAT | BOOLEAN | HEXNUMBER ) { $term = new Term($t.text, $t.type); }
|
||||
| f=(K_MIN_TIMEUUID | K_MAX_TIMEUUID | K_NOW) '(' (v=(STRING_LITERAL | INTEGER))? ')' { $term = new Term($f.text + "(" + ($v == null ? "" : $v.text) + ")", Term.Type.UUID, true); }
|
||||
set_or_map[Term.Raw t] returns [Term.Raw value]
|
||||
: ':' v=term { List<Pair<Term.Raw, Term.Raw>> m = new ArrayList<Pair<Term.Raw, Term.Raw>>(); m.add(Pair.create(t, v)); } map_tail[m] { $value = new Maps.Literal(m); }
|
||||
| { List<Term.Raw> s = new ArrayList<Term.Raw>(); s.add(t); } set_tail[s] { $value = new Sets.Literal(s); }
|
||||
;
|
||||
|
||||
term returns [Term term]
|
||||
: ft=finalTerm { $term = ft; }
|
||||
| t=QMARK { $term = new Term($t.text, $t.type, ++currentBindMarkerIdx); }
|
||||
// This is a bit convoluted but that's because I haven't found a much better to have antl disambiguate between sets and maps
|
||||
collection_literal returns [Term.Raw value]
|
||||
: '[' { List<Term.Raw> l = new ArrayList<Term.Raw>(); } ( t1=term { l.add(t1); } ( ',' tn=term { l.add(tn); } )* )? ']' { $value = new Lists.Literal(l); }
|
||||
| '{' t=term v=set_or_map[t] { $value = v; }
|
||||
// Note that we have an ambiguity between maps and set for "{}". So we force it to a set literal, and deal with it later based on the type of the column (SetLiteral.java).
|
||||
| '{' '}' { $value = new Sets.Literal(Collections.<Term.Raw>emptyList()); }
|
||||
;
|
||||
|
||||
termPairWithOperation[List<Pair<ColumnIdentifier, Operation>> columns]
|
||||
: key=cident '='
|
||||
( set_op=set_operation { columns.add(Pair.<ColumnIdentifier, Operation>create(key, set_op)); }
|
||||
| c=cident op=operation
|
||||
{
|
||||
if (!key.equals(c))
|
||||
addRecognitionError("Only expressions like X = X <op> <value> are supported.");
|
||||
columns.add(Pair.<ColumnIdentifier, Operation>create(key, op));
|
||||
}
|
||||
| ll=list_literal '+' c=cident
|
||||
{
|
||||
if (!key.equals(c))
|
||||
addRecognitionError("Only expressions like X = <value> + X are supported.");
|
||||
columns.add(Pair.<ColumnIdentifier, Operation>create(key, ListOperation.Prepend(ll)));
|
||||
}
|
||||
| mk=QMARK '+' c=cident
|
||||
{
|
||||
if (!key.equals(c))
|
||||
addRecognitionError("Only expressions like X = <value> + X are supported.");
|
||||
PreparedOperation pop = new PreparedOperation(new Term($mk.text, $mk.type, ++currentBindMarkerIdx), PreparedOperation.Kind.PREPARED_PLUS);
|
||||
columns.add(Pair.<ColumnIdentifier, Operation>create(key, pop));
|
||||
}
|
||||
)
|
||||
| key=cident '[' t=term ']' '=' vv=term
|
||||
value returns [Term.Raw value]
|
||||
: c=constant { $value = c; }
|
||||
| l=collection_literal { $value = l; }
|
||||
| QMARK { $value = new AbstractMarker.Raw(++currentBindMarkerIdx); }
|
||||
;
|
||||
|
||||
functionName returns [String s]
|
||||
: f=IDENT { $s = $f.text; }
|
||||
| u=unreserved_function_keyword { $s = u; }
|
||||
| K_TOKEN { $s = "token"; }
|
||||
;
|
||||
|
||||
functionArgs returns [List<Term.Raw> a]
|
||||
: '(' ')' { $a = Collections.emptyList(); }
|
||||
| '(' t1=term { List<Term.Raw> args = new ArrayList<Term.Raw>(); args.add(t1); }
|
||||
( ',' tn=term { args.add(tn); } )*
|
||||
')' { $a = args; }
|
||||
;
|
||||
|
||||
term returns [Term.Raw term]
|
||||
: v=value { $term = v; }
|
||||
| f=functionName args=functionArgs { $term = new FunctionCall.Raw(f, args); }
|
||||
| '(' c=comparatorType ')' t=term { $term = new TypeCast(c, t); }
|
||||
;
|
||||
|
||||
columnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations]
|
||||
: key=cident '=' t=term ('+' c=cident )?
|
||||
{
|
||||
// This is ambiguous, this can either set a list by index, or be a map put.
|
||||
// So we always return a list setIndex and we'll check later and
|
||||
// backtrack to a map operation if need be.
|
||||
columns.add(Pair.<ColumnIdentifier, Operation>create(key, ListOperation.SetIndex(Arrays.asList(t, vv))));
|
||||
if (c == null)
|
||||
{
|
||||
addRawUpdate(operations, key, new Operation.SetValue(t));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!key.equals(c))
|
||||
addRecognitionError("Only expressions of the form X = <value> + X are supported.");
|
||||
addRawUpdate(operations, key, new Operation.Prepend(t));
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
intTerm returns [Term integer]
|
||||
: t=INTEGER { $integer = new Term($t.text, $t.type); }
|
||||
| t=QMARK { $integer = new Term($t.text, $t.type, ++currentBindMarkerIdx); }
|
||||
;
|
||||
|
||||
|
||||
operation returns [Operation op]
|
||||
: '+' i=INTEGER { $op = ColumnOperation.CounterInc(new Term($i.text, $i.type)); }
|
||||
| sign='-'? i=INTEGER
|
||||
| key=cident '=' c=cident sig=('+' | '-') t=term
|
||||
{
|
||||
Term t = new Term($i.text, $i.type);
|
||||
validateMinusSupplied(sign, t, input);
|
||||
if (sign == null)
|
||||
t = new Term(-(Long.valueOf(t.getText())), t.getType());
|
||||
$op = ColumnOperation.CounterDec(t);
|
||||
if (!key.equals(c))
|
||||
addRecognitionError("Only expressions of the form X = X " + $sig.text + "<value> are supported.");
|
||||
addRawUpdate(operations, key, $sig.text.equals("+") ? new Operation.Addition(t) : new Operation.Substraction(t));
|
||||
}
|
||||
| key=cident '=' c=cident i=INTEGER
|
||||
{
|
||||
// Note that this production *is* necessary because X = X - 3 will in fact be lexed as [ X, '=', X, INTEGER].
|
||||
if (!key.equals(c))
|
||||
// We don't yet allow a '+' in front of an integer, but we could in the future really, so let's be future-proof in our error message
|
||||
addRecognitionError("Only expressions of the form X = X " + ($i.text.charAt(0) == '-' ? '-' : '+') + " <value> are supported.");
|
||||
addRawUpdate(operations, key, new Operation.Addition(Constants.Literal.integer($i.text)));
|
||||
}
|
||||
| key=cident '[' k=term ']' '=' t=term
|
||||
{
|
||||
addRawUpdate(operations, key, new Operation.SetElement(k, t));
|
||||
}
|
||||
| '+' mk=QMARK { $op = new PreparedOperation(new Term($mk.text, $mk.type, ++currentBindMarkerIdx), PreparedOperation.Kind.PLUS_PREPARED); }
|
||||
| '-' mk=QMARK { $op = new PreparedOperation(new Term($mk.text, $mk.type, ++currentBindMarkerIdx), PreparedOperation.Kind.MINUS_PREPARED); }
|
||||
|
||||
| '+' ll=list_literal { $op = ListOperation.Append(ll); }
|
||||
| '-' ll=list_literal { $op = ListOperation.Discard(ll); }
|
||||
|
||||
| '+' sl=set_literal { $op = SetOperation.Add(sl); }
|
||||
| '-' sl=set_literal { $op = SetOperation.Discard(sl); }
|
||||
|
||||
| '+' ml=map_literal { $op = MapOperation.Put(ml); }
|
||||
;
|
||||
|
||||
properties[PropertyDefinitions props]
|
||||
|
|
@ -737,41 +766,32 @@ properties[PropertyDefinitions props]
|
|||
|
||||
property[PropertyDefinitions props]
|
||||
: k=cident '=' (simple=propertyValue { try { $props.addProperty(k.toString(), simple); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } }
|
||||
| map=map_literal { try { $props.addProperty(k.toString(), convertMap(map)); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } })
|
||||
| map=map_literal { try { $props.addProperty(k.toString(), convertPropertyMap(map)); } catch (SyntaxException e) { addRecognitionError(e.getMessage()); } })
|
||||
;
|
||||
|
||||
propertyValue returns [String str]
|
||||
: v=(STRING_LITERAL | IDENT | INTEGER | FLOAT | BOOLEAN | HEXNUMBER) { $str = $v.text; }
|
||||
| u=unreserved_keyword { $str = u; }
|
||||
: c=constant { $str = c.getRawText(); }
|
||||
| u=unreserved_keyword { $str = u; }
|
||||
;
|
||||
|
||||
// Either a term or a list of terms
|
||||
tokenDefinition returns [Pair<Term, List<Term>> tkdef]
|
||||
: K_TOKEN { List<Term> l = new ArrayList<Term>(); }
|
||||
'(' t1=term { l.add(t1); } ( ',' tn=term { l.add(tn); } )* ')' { $tkdef = Pair.<Term, List<Term>>create(null, l); }
|
||||
| t=term { $tkdef = Pair.<Term, List<Term>>create(t, null); }
|
||||
relationType returns [Relation.Type op]
|
||||
: '=' { $op = Relation.Type.EQ; }
|
||||
| '<' { $op = Relation.Type.LT; }
|
||||
| '<=' { $op = Relation.Type.LTE; }
|
||||
| '>' { $op = Relation.Type.GT; }
|
||||
| '>=' { $op = Relation.Type.GTE; }
|
||||
;
|
||||
|
||||
relation[List<Relation> clauses]
|
||||
: name=cident type=('=' | '<' | '<=' | '>=' | '>') t=term { $clauses.add(new Relation($name.id, $type.text, $t.term)); }
|
||||
| K_TOKEN { List<ColumnIdentifier> l = new ArrayList<ColumnIdentifier>(); }
|
||||
'(' name1=cident { l.add(name1); } ( ',' namen=cident { l.add(namen); })* ')'
|
||||
type=('=' |'<' | '<=' | '>=' | '>') tkd=tokenDefinition
|
||||
{
|
||||
if (tkd.right != null && tkd.right.size() != l.size())
|
||||
{
|
||||
addRecognitionError("The number of arguments to the token() function don't match");
|
||||
}
|
||||
else
|
||||
{
|
||||
Term tokenLitteral = tkd.left;
|
||||
for (int i = 0; i < l.size(); i++)
|
||||
{
|
||||
Term tt = tokenLitteral == null ? Term.tokenOf(tkd.right.get(i)) : tokenLitteral;
|
||||
$clauses.add(new Relation(l.get(i), $type.text, tt, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
: name=cident type=relationType t=term { $clauses.add(new Relation(name, type, t)); }
|
||||
| K_TOKEN
|
||||
{ List<ColumnIdentifier> l = new ArrayList<ColumnIdentifier>(); }
|
||||
'(' name1=cident { l.add(name1); } ( ',' namen=cident { l.add(namen); })* ')'
|
||||
type=relationType t=term
|
||||
{
|
||||
for (ColumnIdentifier id : l)
|
||||
$clauses.add(new Relation(id, type, t, true));
|
||||
}
|
||||
| name=cident K_IN { Relation rel = Relation.createInRelation($name.id); }
|
||||
'(' f1=term { rel.addInValue(f1); } (',' fN=term { rel.addInValue(fN); } )* ')' { $clauses.add(rel); }
|
||||
;
|
||||
|
|
@ -829,15 +849,17 @@ username
|
|||
;
|
||||
|
||||
unreserved_keyword returns [String str]
|
||||
: u=unreserved_function_keyword { $str = u; }
|
||||
| k=(K_TTL | K_COUNT | K_WRITETIME) { $str = $k.text; }
|
||||
;
|
||||
|
||||
unreserved_function_keyword returns [String str]
|
||||
: k=( K_KEY
|
||||
| K_CLUSTERING
|
||||
| K_COUNT
|
||||
| K_TTL
|
||||
| K_COMPACT
|
||||
| K_STORAGE
|
||||
| K_TYPE
|
||||
| K_VALUES
|
||||
| K_WRITETIME
|
||||
| K_MAP
|
||||
| K_LIST
|
||||
| K_FILTERING
|
||||
|
|
@ -850,9 +872,6 @@ unreserved_keyword returns [String str]
|
|||
| K_SUPERUSER
|
||||
| K_NOSUPERUSER
|
||||
| K_PASSWORD
|
||||
| K_MIN_TIMEUUID
|
||||
| K_MAX_TIMEUUID
|
||||
| K_NOW
|
||||
) { $str = $k.text; }
|
||||
| t=native_type { $str = t.toString(); }
|
||||
;
|
||||
|
|
@ -945,12 +964,6 @@ K_WRITETIME: W R I T E T I M E;
|
|||
K_MAP: M A P;
|
||||
K_LIST: L I S T;
|
||||
|
||||
K_MIN_TIMEUUID: M I N T I M E U U I D;
|
||||
K_MAX_TIMEUUID: M A X T I M E U U I D;
|
||||
K_NOW: N O W;
|
||||
K_DATE_OF: D A T E O F;
|
||||
K_UNIXTIMESTAMP_OF: U N I X T I M E S T A M P O F;
|
||||
|
||||
// Case-insensitive alpha characters
|
||||
fragment A: ('a'|'A');
|
||||
fragment B: ('b'|'B');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,382 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.db.marshal.MarshalException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
/**
|
||||
* Static helper methods and classes for lists.
|
||||
*/
|
||||
public abstract class Lists
|
||||
{
|
||||
private Lists() {}
|
||||
|
||||
public static ColumnSpecification indexSpecOf(ColumnSpecification column)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("idx(" + column.name + ")", true), Int32Type.instance);
|
||||
}
|
||||
|
||||
public static ColumnSpecification valueSpecOf(ColumnSpecification column)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((ListType)column.type).elements);
|
||||
}
|
||||
|
||||
public static class Literal implements Term.Raw
|
||||
{
|
||||
private final List<Term.Raw> elements;
|
||||
|
||||
public Literal(List<Term.Raw> elements)
|
||||
{
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public Value prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
validateAssignableTo(receiver);
|
||||
|
||||
ColumnSpecification valueSpec = Lists.valueSpecOf(receiver);
|
||||
List<ByteBuffer> values = new ArrayList<ByteBuffer>(elements.size());
|
||||
for (Term.Raw rt : elements)
|
||||
{
|
||||
Term t = rt.prepare(valueSpec);
|
||||
|
||||
if (!(t instanceof Constants.Value))
|
||||
{
|
||||
if (t instanceof Term.NonTerminal)
|
||||
throw new InvalidRequestException(String.format("Invalid list literal for %s: bind variables are not supported inside collection literals", receiver));
|
||||
else
|
||||
throw new InvalidRequestException(String.format("Invalid list literal for %s: nested collections are not supported", receiver));
|
||||
}
|
||||
|
||||
// We don't allow prepared marker in collections, nor nested collections
|
||||
assert t instanceof Constants.Value;
|
||||
values.add(((Constants.Value)t).bytes);
|
||||
}
|
||||
return new Value(values);
|
||||
}
|
||||
|
||||
private void validateAssignableTo(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!(receiver.type instanceof ListType))
|
||||
throw new InvalidRequestException(String.format("Invalid list literal for %s of type %s", receiver, receiver.type.asCQL3Type()));
|
||||
|
||||
ColumnSpecification valueSpec = Lists.valueSpecOf(receiver);
|
||||
for (Term.Raw rt : elements)
|
||||
{
|
||||
if (!rt.isAssignableTo(valueSpec))
|
||||
throw new InvalidRequestException(String.format("Invalid list literal for %s: value %s is not of type %s", receiver, rt, valueSpec.type.asCQL3Type()));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
try
|
||||
{
|
||||
validateAssignableTo(receiver);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidRequestException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return elements.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Value extends Term.Terminal
|
||||
{
|
||||
public final List<ByteBuffer> elements;
|
||||
|
||||
public Value(List<ByteBuffer> elements)
|
||||
{
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public static Value fromSerialized(ByteBuffer value, ListType type) throws InvalidRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
// Collections have this small hack that validate cannot be called on a serialized object,
|
||||
// but compose does the validation (so we're fine).
|
||||
List<?> l = type.compose(value);
|
||||
List<ByteBuffer> elements = new ArrayList<ByteBuffer>(l.size());
|
||||
for (Object element : l)
|
||||
elements.add(type.elements.decompose(element));
|
||||
return new Value(elements);
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuffer get()
|
||||
{
|
||||
return CollectionType.pack(elements, elements.size());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Marker extends AbstractMarker
|
||||
{
|
||||
protected Marker(int bindIndex, ColumnSpecification receiver)
|
||||
{
|
||||
super(bindIndex, receiver);
|
||||
assert receiver.type instanceof ListType;
|
||||
}
|
||||
|
||||
public Value bind(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
ByteBuffer value = values.get(bindIndex);
|
||||
return Value.fromSerialized(value, (ListType)receiver.type);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For prepend, we need to be able to generate unique but decreasing time
|
||||
* UUID, which is a bit challenging. To do that, given a time in milliseconds,
|
||||
* we adds a number representing the 100-nanoseconds precision and make sure
|
||||
* that within the same millisecond, that number is always decreasing. We
|
||||
* do rely on the fact that the user will only provide decreasing
|
||||
* milliseconds timestamp for that purpose.
|
||||
*/
|
||||
private static class PrecisionTime
|
||||
{
|
||||
// Our reference time (1 jan 2010, 00:00:00) in milliseconds.
|
||||
private static final long REFERENCE_TIME = 1262304000000L;
|
||||
private static final AtomicReference<PrecisionTime> last = new AtomicReference<PrecisionTime>(new PrecisionTime(Long.MAX_VALUE, 0));
|
||||
|
||||
public final long millis;
|
||||
public final int nanos;
|
||||
|
||||
PrecisionTime(long millis, int nanos)
|
||||
{
|
||||
this.millis = millis;
|
||||
this.nanos = nanos;
|
||||
}
|
||||
|
||||
static PrecisionTime getNext(long millis)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
PrecisionTime current = last.get();
|
||||
|
||||
assert millis <= current.millis;
|
||||
PrecisionTime next = millis < current.millis
|
||||
? new PrecisionTime(millis, 9999)
|
||||
: new PrecisionTime(millis, Math.max(0, current.nanos - 1));
|
||||
|
||||
if (last.compareAndSet(current, next))
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Setter extends Operation
|
||||
{
|
||||
public Setter(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
// delete + append
|
||||
ColumnNameBuilder column = prefix.add(columnName.key);
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(column.build(), column.buildAsEndOfRange()));
|
||||
Appender.doAppend(t, cf, column, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SetterByIndex extends Operation
|
||||
{
|
||||
private final Term idx;
|
||||
|
||||
public SetterByIndex(ColumnIdentifier column, Term idx, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
this.idx = idx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresRead()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collectMarkerSpecification(ColumnSpecification[] boundNames)
|
||||
{
|
||||
super.collectMarkerSpecification(boundNames);
|
||||
idx.collectMarkerSpecification(boundNames);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal index = idx.bind(params.variables);
|
||||
Term.Terminal value = t.bind(params.variables);
|
||||
assert index instanceof Constants.Value && value instanceof Constants.Value;
|
||||
|
||||
List<Pair<ByteBuffer, IColumn>> existingList = params.getPrefetchedList(rowKey, columnName.key);
|
||||
int idx = ByteBufferUtil.toInt(((Constants.Value)index).bytes);
|
||||
if (idx < 0 || idx >= existingList.size())
|
||||
throw new InvalidRequestException(String.format("List index %d out of bound, list has size %d", idx, existingList.size()));
|
||||
|
||||
ByteBuffer elementName = existingList.get(idx).right.name();
|
||||
cf.addColumn(params.makeColumn(elementName, ((Constants.Value)value).bytes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Appender extends Operation
|
||||
{
|
||||
public Appender(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
doAppend(t, cf, prefix.add(columnName.key), params);
|
||||
}
|
||||
|
||||
static void doAppend(Term t, ColumnFamily cf, ColumnNameBuilder columnName, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal value = t.bind(params.variables);
|
||||
assert value instanceof Lists.Value;
|
||||
|
||||
List<ByteBuffer> toAdd = ((Lists.Value)value).elements;
|
||||
for (int i = 0; i < toAdd.size(); i++)
|
||||
{
|
||||
ColumnNameBuilder b = i == toAdd.size() - 1 ? columnName : columnName.copy();
|
||||
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
|
||||
ByteBuffer cellName = b.add(uuid).build();
|
||||
cf.addColumn(params.makeColumn(cellName, toAdd.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Prepender extends Operation
|
||||
{
|
||||
public Prepender(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal value = t.bind(params.variables);
|
||||
assert value instanceof Lists.Value;
|
||||
|
||||
long time = PrecisionTime.REFERENCE_TIME - (System.currentTimeMillis() - PrecisionTime.REFERENCE_TIME);
|
||||
|
||||
List<ByteBuffer> toAdd = ((Lists.Value)value).elements;
|
||||
ColumnNameBuilder column = prefix.add(columnName.key);
|
||||
for (int i = 0; i < toAdd.size(); i++)
|
||||
{
|
||||
ColumnNameBuilder b = i == toAdd.size() - 1 ? column : column.copy();
|
||||
PrecisionTime pt = PrecisionTime.getNext(time);
|
||||
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(pt.millis, pt.nanos));
|
||||
ByteBuffer cellName = b.add(uuid).build();
|
||||
cf.addColumn(params.makeColumn(cellName, toAdd.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Discarder extends Operation
|
||||
{
|
||||
public Discarder(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresRead()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
List<Pair<ByteBuffer, IColumn>> existingList = params.getPrefetchedList(rowKey, columnName.key);
|
||||
if (existingList.isEmpty())
|
||||
return;
|
||||
|
||||
Term.Terminal value = t.bind(params.variables);
|
||||
assert value instanceof Lists.Value;
|
||||
|
||||
// Note: below, we will call 'contains' on this toDiscard list for each element of existingList.
|
||||
// Meaning that if toDiscard is big, converting it to a HashSet might be more efficient. However,
|
||||
// the read-before-write this operation requires limits its usefulness on big lists, so in practice
|
||||
// toDiscard will be small and keeping a list will be more efficient.
|
||||
List<ByteBuffer> toDiscard = ((Lists.Value)value).elements;
|
||||
for (Pair<ByteBuffer, IColumn> p : existingList)
|
||||
{
|
||||
IColumn element = p.right;
|
||||
if (toDiscard.contains(element.value()))
|
||||
cf.addColumn(params.makeTombstone(element.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DiscarderByIndex extends Operation
|
||||
{
|
||||
public DiscarderByIndex(ColumnIdentifier column, Term idx)
|
||||
{
|
||||
super(column, idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresRead()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal index = t.bind(params.variables);
|
||||
assert index instanceof Constants.Value;
|
||||
|
||||
List<Pair<ByteBuffer, IColumn>> existingList = params.getPrefetchedList(rowKey, columnName.key);
|
||||
int idx = ByteBufferUtil.toInt(((Constants.Value)index).bytes);
|
||||
if (idx < 0 || idx >= existingList.size())
|
||||
throw new InvalidRequestException(String.format("List index %d out of bound, list has size %d", idx, existingList.size()));
|
||||
|
||||
ByteBuffer elementName = existingList.get(idx).right.name();
|
||||
cf.addColumn(params.makeTombstone(elementName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.MarshalException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
/**
|
||||
* Static helper methods and classes for maps.
|
||||
*/
|
||||
public abstract class Maps
|
||||
{
|
||||
private Maps() {}
|
||||
|
||||
public static ColumnSpecification keySpecOf(ColumnSpecification column)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), ((MapType)column.type).keys);
|
||||
}
|
||||
|
||||
public static ColumnSpecification valueSpecOf(ColumnSpecification column)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((MapType)column.type).values);
|
||||
}
|
||||
|
||||
public static class Literal implements Term.Raw
|
||||
{
|
||||
public final List<Pair<Term.Raw, Term.Raw>> entries;
|
||||
|
||||
public Literal(List<Pair<Term.Raw, Term.Raw>> entries)
|
||||
{
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
public Value prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
validateAssignableTo(receiver);
|
||||
|
||||
ColumnSpecification keySpec = Maps.keySpecOf(receiver);
|
||||
ColumnSpecification valueSpec = Maps.valueSpecOf(receiver);
|
||||
Map<ByteBuffer, ByteBuffer> values = new TreeMap<ByteBuffer, ByteBuffer>(((MapType)receiver.type).keys);
|
||||
for (Pair<Term.Raw, Term.Raw> entry : entries)
|
||||
{
|
||||
Term k = entry.left.prepare(keySpec);
|
||||
Term v = entry.right.prepare(valueSpec);
|
||||
|
||||
if (!(k instanceof Constants.Value && v instanceof Constants.Value))
|
||||
{
|
||||
if (k instanceof Term.NonTerminal || v instanceof Term.NonTerminal)
|
||||
throw new InvalidRequestException(String.format("Invalid map literal for %s: bind variables are not supported inside collection literals", receiver));
|
||||
else
|
||||
throw new InvalidRequestException(String.format("Invalid map literal for %s: nested collections are not supported", receiver));
|
||||
}
|
||||
|
||||
if (values.put(((Constants.Value)k).bytes, ((Constants.Value)v).bytes) != null)
|
||||
throw new InvalidRequestException(String.format("Invalid map literal: duplicate entry for key %s", entry.left));
|
||||
}
|
||||
return new Value(values);
|
||||
}
|
||||
|
||||
private void validateAssignableTo(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!(receiver.type instanceof MapType))
|
||||
throw new InvalidRequestException(String.format("Invalid map literal for %s of type %s", receiver, receiver.type.asCQL3Type()));
|
||||
|
||||
ColumnSpecification keySpec = Maps.keySpecOf(receiver);
|
||||
ColumnSpecification valueSpec = Maps.valueSpecOf(receiver);
|
||||
for (Pair<Term.Raw, Term.Raw> entry : entries)
|
||||
{
|
||||
if (!entry.left.isAssignableTo(keySpec))
|
||||
throw new InvalidRequestException(String.format("Invalid map literal for %s: key %s is not of type %s", receiver, entry.left, keySpec.type.asCQL3Type()));
|
||||
if (!entry.right.isAssignableTo(valueSpec))
|
||||
throw new InvalidRequestException(String.format("Invalid map literal for %s: value %s is not of type %s", receiver, entry.right, valueSpec.type.asCQL3Type()));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
try
|
||||
{
|
||||
validateAssignableTo(receiver);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidRequestException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
for (int i = 0; i < entries.size(); i++)
|
||||
{
|
||||
if (i > 0) sb.append(", ");
|
||||
sb.append(entries.get(i).left).append(":").append(entries.get(i).right);
|
||||
}
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Value extends Term.Terminal
|
||||
{
|
||||
public final Map<ByteBuffer, ByteBuffer> map;
|
||||
|
||||
public Value(Map<ByteBuffer, ByteBuffer> map)
|
||||
{
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public static Value fromSerialized(ByteBuffer value, MapType type) throws InvalidRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
// Collections have this small hack that validate cannot be called on a serialized object,
|
||||
// but compose does the validation (so we're fine).
|
||||
Map<?, ?> m = type.compose(value);
|
||||
Map<ByteBuffer, ByteBuffer> map = new LinkedHashMap<ByteBuffer, ByteBuffer>(m.size());
|
||||
for (Map.Entry<?, ?> entry : m.entrySet())
|
||||
map.put(type.keys.decompose(entry.getKey()), type.values.decompose(entry.getValue()));
|
||||
return new Value(map);
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuffer get()
|
||||
{
|
||||
List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(2 * map.size());
|
||||
for (Map.Entry<ByteBuffer, ByteBuffer> entry : map.entrySet())
|
||||
{
|
||||
buffers.add(entry.getKey());
|
||||
buffers.add(entry.getValue());
|
||||
}
|
||||
return CollectionType.pack(buffers, map.size());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Marker extends AbstractMarker
|
||||
{
|
||||
protected Marker(int bindIndex, ColumnSpecification receiver)
|
||||
{
|
||||
super(bindIndex, receiver);
|
||||
assert receiver.type instanceof MapType;
|
||||
}
|
||||
|
||||
public Value bind(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
ByteBuffer value = values.get(bindIndex);
|
||||
return Value.fromSerialized(value, (MapType)receiver.type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Setter extends Operation
|
||||
{
|
||||
public Setter(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
// delete + put
|
||||
ColumnNameBuilder column = prefix.add(columnName.key);
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(column.build(), column.buildAsEndOfRange()));
|
||||
Putter.doPut(t, cf, column, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SetterByKey extends Operation
|
||||
{
|
||||
private final Term k;
|
||||
|
||||
public SetterByKey(ColumnIdentifier column, Term k, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
this.k = k;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collectMarkerSpecification(ColumnSpecification[] boundNames)
|
||||
{
|
||||
super.collectMarkerSpecification(boundNames);
|
||||
k.collectMarkerSpecification(boundNames);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal key = k.bind(params.variables);
|
||||
Term.Terminal value = t.bind(params.variables);
|
||||
assert key instanceof Constants.Value && value instanceof Constants.Value;
|
||||
|
||||
ByteBuffer cellName = prefix.add(columnName.key).add(((Constants.Value)key).bytes).build();
|
||||
cf.addColumn(params.makeColumn(cellName, ((Constants.Value)value).bytes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Putter extends Operation
|
||||
{
|
||||
public Putter(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
doPut(t, cf, prefix.add(columnName.key), params);
|
||||
}
|
||||
|
||||
static void doPut(Term t, ColumnFamily cf, ColumnNameBuilder columnName, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal value = t.bind(params.variables);
|
||||
assert value instanceof Maps.Value;
|
||||
|
||||
Map<ByteBuffer, ByteBuffer> toAdd = ((Maps.Value)value).map;
|
||||
for (Map.Entry<ByteBuffer, ByteBuffer> entry : toAdd.entrySet())
|
||||
{
|
||||
ByteBuffer cellName = columnName.copy().add(entry.getKey()).build();
|
||||
cf.addColumn(params.makeColumn(cellName, entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DiscarderByKey extends Operation
|
||||
{
|
||||
public DiscarderByKey(ColumnIdentifier column, Term k)
|
||||
{
|
||||
super(column, k);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal key = t.bind(params.variables);
|
||||
assert key instanceof Constants.Value;
|
||||
|
||||
ByteBuffer cellName = prefix.add(columnName.key).add(((Constants.Value)key).bytes).build();
|
||||
cf.addColumn(params.makeTombstone(cellName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.CounterColumnType;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
/**
|
||||
* An UPDATE or DELETE operation.
|
||||
*
|
||||
* For UPDATE this includes:
|
||||
* - setting a constant
|
||||
* - counter operations
|
||||
* - collections operations
|
||||
* and for DELETE:
|
||||
* - deleting a column
|
||||
* - deleting an element of collection column
|
||||
*
|
||||
* Fine grained operation are obtained from their raw counterpart (Operation.Raw, which
|
||||
* correspond to a parsed, non-checked operation) by provided the receiver for the operation.
|
||||
*/
|
||||
public abstract class Operation
|
||||
{
|
||||
// Name of the column the operation applies to
|
||||
public final ColumnIdentifier columnName;
|
||||
|
||||
// Term involved in the operation. In theory this should not be here since some operation
|
||||
// may require none of more than one term, but most need 1 so it simplify things a bit.
|
||||
protected final Term t;
|
||||
|
||||
protected Operation(ColumnIdentifier columnName, Term t)
|
||||
{
|
||||
this.columnName = columnName;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the operation requires a read of the previous value to be executed
|
||||
* (only lists setterByIdx, discard and discardByIdx requires that).
|
||||
*/
|
||||
public boolean requiresRead()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the column specification for the bind variables of this operation.
|
||||
*
|
||||
* @param boundNames the list of column specification where to collect the
|
||||
* bind variables of this term in.
|
||||
*/
|
||||
public void collectMarkerSpecification(ColumnSpecification[] boundNames)
|
||||
{
|
||||
if (t != null)
|
||||
t.collectMarkerSpecification(boundNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the operation.
|
||||
*
|
||||
* @param rowKey row key for the update.
|
||||
* @param cf the column family to which to add the updates generated by this operation.
|
||||
* @param namePrefix the prefix that identify the CQL3 row this operation applies to (callers should not reuse
|
||||
* the ColumnNameBuilder they pass here).
|
||||
* @param params parameters of the update.
|
||||
*/
|
||||
public abstract void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder namePrefix, UpdateParameters params) throws InvalidRequestException;
|
||||
|
||||
/**
|
||||
* A parsed raw UPDATE operation.
|
||||
*
|
||||
* This can be one of:
|
||||
* - Setting a value: c = v
|
||||
* - Setting an element of a collection: c[x] = v
|
||||
* - An addition/substraction to a variable: c = c +/- v (where v can be a collection literal)
|
||||
* - An prepend operation: c = v + c
|
||||
*/
|
||||
public interface RawUpdate
|
||||
{
|
||||
/**
|
||||
* This method validates the operation (i.e. validate it is well typed)
|
||||
* based on the specification of the receiver of the 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.
|
||||
* @return the prepared update operation.
|
||||
*/
|
||||
public Operation prepare(CFDefinition.Name receiver) throws InvalidRequestException;
|
||||
|
||||
/**
|
||||
* @return whether this operation can be applied alongside the {@code
|
||||
* other} update (in the same UPDATE statement for the same column).
|
||||
*/
|
||||
public boolean isCompatibleWith(RawUpdate other);
|
||||
}
|
||||
|
||||
/**
|
||||
* A parsed raw DELETE operation.
|
||||
*
|
||||
* This can be one of:
|
||||
* - Deleting a column
|
||||
* - Deleting an element of a collection
|
||||
*/
|
||||
public interface RawDeletion
|
||||
{
|
||||
/**
|
||||
* The name of the column affected by this delete operation.
|
||||
*/
|
||||
public ColumnIdentifier affectedColumn();
|
||||
|
||||
/**
|
||||
* This method validates the operation (i.e. validate it is well typed)
|
||||
* based on the specification of the column affected by the operation (i.e the
|
||||
* one returned by affectedColumn()).
|
||||
*
|
||||
* It returns an Operation which can be though as post-preparation well-typed
|
||||
* Operation.
|
||||
*
|
||||
* @param receiver the "column" this operation applies to.
|
||||
* @return the prepared delete operation.
|
||||
*/
|
||||
public Operation prepare(ColumnSpecification receiver) throws InvalidRequestException;
|
||||
}
|
||||
|
||||
public static class SetValue implements RawUpdate
|
||||
{
|
||||
private final Term.Raw value;
|
||||
|
||||
public SetValue(Term.Raw value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Operation prepare(CFDefinition.Name receiver) throws InvalidRequestException
|
||||
{
|
||||
Term v = value.prepare(receiver);
|
||||
|
||||
if (!(receiver.type instanceof CollectionType))
|
||||
return new Constants.Setter(receiver.kind == CFDefinition.Name.Kind.VALUE_ALIAS ? null : receiver.name, v);
|
||||
|
||||
switch (((CollectionType)receiver.type).kind)
|
||||
{
|
||||
case LIST:
|
||||
return new Lists.Setter(receiver.name, v);
|
||||
case SET:
|
||||
return new Sets.Setter(receiver.name, v);
|
||||
case MAP:
|
||||
return new Maps.Setter(receiver.name, v);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
protected String toString(ColumnSpecification column)
|
||||
{
|
||||
return String.format("%s = %s", column, value);
|
||||
}
|
||||
|
||||
public boolean isCompatibleWith(RawUpdate other)
|
||||
{
|
||||
// We don't allow setting multiple time the same column, because 1)
|
||||
// it's stupid and 2) the result would seem random to the user.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SetElement implements RawUpdate
|
||||
{
|
||||
private final Term.Raw selector;
|
||||
private final Term.Raw value;
|
||||
|
||||
public SetElement(Term.Raw selector, Term.Raw value)
|
||||
{
|
||||
this.selector = selector;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Operation prepare(CFDefinition.Name receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!(receiver.type instanceof CollectionType))
|
||||
throw new InvalidRequestException(String.format("Invalid operation (%s) for non collection column %s", toString(receiver), receiver));
|
||||
|
||||
switch (((CollectionType)receiver.type).kind)
|
||||
{
|
||||
case LIST:
|
||||
Term idx = selector.prepare(Lists.indexSpecOf(receiver));
|
||||
Term lval = value.prepare(Lists.valueSpecOf(receiver));
|
||||
return new Lists.SetterByIndex(receiver.name, idx, lval);
|
||||
case SET:
|
||||
throw new InvalidRequestException(String.format("Invalid operation (%s) for set column %s", toString(receiver), receiver));
|
||||
case MAP:
|
||||
Term key = selector.prepare(Maps.keySpecOf(receiver));
|
||||
Term mval = value.prepare(Maps.valueSpecOf(receiver));
|
||||
return new Maps.SetterByKey(receiver.name, key, mval);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
protected String toString(ColumnSpecification column)
|
||||
{
|
||||
return String.format("%s[%s] = %s", column, selector, value);
|
||||
}
|
||||
|
||||
public boolean isCompatibleWith(RawUpdate other)
|
||||
{
|
||||
// TODO: we could check that the other operation is not setting the same element
|
||||
// too (but since the index/key set may be a bind variables we can't always do it at this point)
|
||||
return !(other instanceof SetValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Addition implements RawUpdate
|
||||
{
|
||||
private final Term.Raw value;
|
||||
|
||||
public Addition(Term.Raw value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Operation prepare(CFDefinition.Name receiver) throws InvalidRequestException
|
||||
{
|
||||
Term v = value.prepare(receiver);
|
||||
|
||||
if (!(receiver.type instanceof CollectionType))
|
||||
{
|
||||
if (!(receiver.type instanceof CounterColumnType))
|
||||
throw new InvalidRequestException(String.format("Invalid operation for non counter column %s", toString(receiver), receiver));
|
||||
return new Constants.Adder(receiver.kind == CFDefinition.Name.Kind.VALUE_ALIAS ? null : receiver.name, v);
|
||||
}
|
||||
|
||||
switch (((CollectionType)receiver.type).kind)
|
||||
{
|
||||
case LIST:
|
||||
return new Lists.Appender(receiver.name, v);
|
||||
case SET:
|
||||
return new Sets.Adder(receiver.name, v);
|
||||
case MAP:
|
||||
return new Maps.Putter(receiver.name, v);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
protected String toString(ColumnSpecification column)
|
||||
{
|
||||
return String.format("%s = %s + %s", column, column, value);
|
||||
}
|
||||
|
||||
public boolean isCompatibleWith(RawUpdate other)
|
||||
{
|
||||
return !(other instanceof SetValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Substraction implements RawUpdate
|
||||
{
|
||||
private final Term.Raw value;
|
||||
|
||||
public Substraction(Term.Raw value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Operation prepare(CFDefinition.Name receiver) throws InvalidRequestException
|
||||
{
|
||||
Term v = value.prepare(receiver);
|
||||
|
||||
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));
|
||||
return new Constants.Substracter(receiver.kind == CFDefinition.Name.Kind.VALUE_ALIAS ? null : receiver.name, v);
|
||||
}
|
||||
|
||||
switch (((CollectionType)receiver.type).kind)
|
||||
{
|
||||
case LIST:
|
||||
return new Lists.Discarder(receiver.name, v);
|
||||
case SET:
|
||||
return new Sets.Discarder(receiver.name, v);
|
||||
case MAP:
|
||||
throw new InvalidRequestException(String.format("Invalid operation (%s) for map column %s", toString(receiver), receiver));
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
protected String toString(ColumnSpecification column)
|
||||
{
|
||||
return String.format("%s = %s - %s", column, column, value);
|
||||
}
|
||||
|
||||
public boolean isCompatibleWith(RawUpdate other)
|
||||
{
|
||||
return !(other instanceof SetValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Prepend implements RawUpdate
|
||||
{
|
||||
private final Term.Raw value;
|
||||
|
||||
public Prepend(Term.Raw value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Operation prepare(CFDefinition.Name receiver) throws InvalidRequestException
|
||||
{
|
||||
Term v = value.prepare(receiver);
|
||||
|
||||
if (!(receiver.type instanceof ListType))
|
||||
throw new InvalidRequestException(String.format("Invalid operation (%s) for non list column %s", toString(receiver), receiver));
|
||||
|
||||
return new Lists.Prepender(receiver.name, v);
|
||||
}
|
||||
|
||||
protected String toString(ColumnSpecification column)
|
||||
{
|
||||
return String.format("%s = %s - %s", column, value, column);
|
||||
}
|
||||
|
||||
public boolean isCompatibleWith(RawUpdate other)
|
||||
{
|
||||
return !(other instanceof SetValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ColumnDeletion implements RawDeletion
|
||||
{
|
||||
private final ColumnIdentifier id;
|
||||
|
||||
public ColumnDeletion(ColumnIdentifier id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ColumnIdentifier affectedColumn()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Operation prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
// No validation, deleting a column is always "well typed"
|
||||
return new Constants.Deleter(id, receiver.type instanceof CollectionType);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ElementDeletion implements RawDeletion
|
||||
{
|
||||
private final ColumnIdentifier id;
|
||||
private final Term.Raw element;
|
||||
|
||||
public ElementDeletion(ColumnIdentifier id, Term.Raw element)
|
||||
{
|
||||
this.id = id;
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
public ColumnIdentifier affectedColumn()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Operation prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!(receiver.type instanceof CollectionType))
|
||||
throw new InvalidRequestException(String.format("Invalid deletion operation for non collection column %s", receiver));
|
||||
|
||||
switch (((CollectionType)receiver.type).kind)
|
||||
{
|
||||
case LIST:
|
||||
Term idx = element.prepare(Lists.indexSpecOf(receiver));
|
||||
return new Lists.DiscarderByIndex(id, idx);
|
||||
case SET:
|
||||
Term elt = element.prepare(Sets.valueSpecOf(receiver));
|
||||
return new Lists.Discarder(id, elt);
|
||||
case MAP:
|
||||
Term key = element.prepare(Maps.keySpecOf(receiver));
|
||||
return new Maps.DiscarderByKey(id, key);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +137,10 @@ public class QueryProcessor
|
|||
throws RequestExecutionException, RequestValidationException
|
||||
{
|
||||
logger.trace("CQL QUERY: {}", queryString);
|
||||
return processStatement(getStatement(queryString, queryState.getClientState()).statement, cl, queryState, Collections.<ByteBuffer>emptyList());
|
||||
CQLStatement prepared = getStatement(queryString, queryState.getClientState()).statement;
|
||||
if (prepared.getBoundsTerms() > 0)
|
||||
throw new InvalidRequestException("Cannot execute query with bind variables");
|
||||
return processStatement(prepared, cl, queryState, Collections.<ByteBuffer>emptyList());
|
||||
}
|
||||
|
||||
public static UntypedResultSet process(String query) throws RequestExecutionException
|
||||
|
|
|
|||
|
|
@ -29,32 +29,16 @@ public class Relation
|
|||
{
|
||||
private final ColumnIdentifier entity;
|
||||
private final Type relationType;
|
||||
private final Term value;
|
||||
private final List<Term> inValues;
|
||||
private final Term.Raw value;
|
||||
private final List<Term.Raw> inValues;
|
||||
public final boolean onToken;
|
||||
|
||||
public static enum Type
|
||||
{
|
||||
EQ, LT, LTE, GTE, GT, IN;
|
||||
|
||||
public static Type forString(String s)
|
||||
{
|
||||
if (s.equals("="))
|
||||
return EQ;
|
||||
else if (s.equals("<"))
|
||||
return LT;
|
||||
else if (s.equals("<="))
|
||||
return LTE;
|
||||
else if (s.equals(">="))
|
||||
return GTE;
|
||||
else if (s.equals(">"))
|
||||
return GT;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Relation(ColumnIdentifier entity, Type type, Term value, List<Term> inValues, boolean onToken)
|
||||
private Relation(ColumnIdentifier entity, Type type, Term.Raw value, List<Term.Raw> inValues, boolean onToken)
|
||||
{
|
||||
this.entity = entity;
|
||||
this.relationType = type;
|
||||
|
|
@ -70,19 +54,19 @@ public class Relation
|
|||
* @param type the type that describes how this entity relates to the value.
|
||||
* @param value the value being compared.
|
||||
*/
|
||||
public Relation(ColumnIdentifier entity, String type, Term value)
|
||||
public Relation(ColumnIdentifier entity, Type type, Term.Raw value)
|
||||
{
|
||||
this(entity, Type.forString(type), value, null, false);
|
||||
this(entity, type, value, null, false);
|
||||
}
|
||||
|
||||
public Relation(ColumnIdentifier entity, String type, Term value, boolean onToken)
|
||||
public Relation(ColumnIdentifier entity, Type type, Term.Raw value, boolean onToken)
|
||||
{
|
||||
this(entity, Type.forString(type), value, null, onToken);
|
||||
this(entity, type, value, null, onToken);
|
||||
}
|
||||
|
||||
public static Relation createInRelation(ColumnIdentifier entity)
|
||||
{
|
||||
return new Relation(entity, Type.IN, null, new ArrayList<Term>(), false);
|
||||
return new Relation(entity, Type.IN, null, new ArrayList<Term.Raw>(), false);
|
||||
}
|
||||
|
||||
public Type operator()
|
||||
|
|
@ -95,19 +79,19 @@ public class Relation
|
|||
return entity;
|
||||
}
|
||||
|
||||
public Term getValue()
|
||||
public Term.Raw getValue()
|
||||
{
|
||||
assert relationType != Type.IN;
|
||||
return value;
|
||||
}
|
||||
|
||||
public List<Term> getInValues()
|
||||
public List<Term.Raw> getInValues()
|
||||
{
|
||||
assert relationType == Type.IN;
|
||||
return inValues;
|
||||
}
|
||||
|
||||
public void addInValue(Term t)
|
||||
public void addInValue(Term.Raw t)
|
||||
{
|
||||
inValues.add(t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,12 @@ public class ResultSet
|
|||
return rows.size();
|
||||
}
|
||||
|
||||
public void addRow(List<ByteBuffer> row)
|
||||
{
|
||||
assert row.size() == metadata.names.size();
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
public void addColumnValue(ByteBuffer value)
|
||||
{
|
||||
if (rows.isEmpty() || lastRow().size() == metadata.names.size())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.MarshalException;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
/**
|
||||
* Static helper methods and classes for sets.
|
||||
*/
|
||||
public abstract class Sets
|
||||
{
|
||||
private Sets() {}
|
||||
|
||||
public static ColumnSpecification valueSpecOf(ColumnSpecification column)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), ((SetType)column.type).elements);
|
||||
}
|
||||
|
||||
public static class Literal implements Term.Raw
|
||||
{
|
||||
private final List<Term.Raw> elements;
|
||||
|
||||
public Literal(List<Term.Raw> elements)
|
||||
{
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public Term.Terminal prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
validateAssignableTo(receiver);
|
||||
|
||||
// We've parsed empty maps as a set literal to break the ambiguity so
|
||||
// handle that case now
|
||||
if (receiver.type instanceof MapType && elements.isEmpty())
|
||||
return new Maps.Value(Collections.<ByteBuffer, ByteBuffer>emptyMap());
|
||||
|
||||
ColumnSpecification valueSpec = Sets.valueSpecOf(receiver);
|
||||
Set<ByteBuffer> values = new TreeSet<ByteBuffer>(((SetType)receiver.type).elements);
|
||||
for (Term.Raw rt : elements)
|
||||
{
|
||||
Term t = rt.prepare(valueSpec);
|
||||
|
||||
if (!(t instanceof Constants.Value))
|
||||
{
|
||||
if (t instanceof Term.NonTerminal)
|
||||
throw new InvalidRequestException(String.format("Invalid set literal for %s: bind variables are not supported inside collection literals", receiver));
|
||||
else
|
||||
throw new InvalidRequestException(String.format("Invalid set literal for %s: nested collections are not supported", receiver));
|
||||
}
|
||||
|
||||
if (!values.add(((Constants.Value)t).bytes))
|
||||
throw new InvalidRequestException(String.format("Invalid set literal: duplicate value %s", rt));
|
||||
}
|
||||
return new Value(values);
|
||||
}
|
||||
|
||||
private void validateAssignableTo(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!(receiver.type instanceof SetType))
|
||||
{
|
||||
// We've parsed empty maps as a set literal to break the ambiguity so
|
||||
// handle that case now
|
||||
if (receiver.type instanceof MapType && elements.isEmpty())
|
||||
return;
|
||||
|
||||
throw new InvalidRequestException(String.format("Invalid set literal for %s of type %s", receiver, receiver.type.asCQL3Type()));
|
||||
}
|
||||
|
||||
ColumnSpecification valueSpec = Sets.valueSpecOf(receiver);
|
||||
for (Term.Raw rt : elements)
|
||||
{
|
||||
if (!rt.isAssignableTo(valueSpec))
|
||||
throw new InvalidRequestException(String.format("Invalid set literal for %s: value %s is not of type %s", receiver, rt, valueSpec.type.asCQL3Type()));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
try
|
||||
{
|
||||
validateAssignableTo(receiver);
|
||||
return true;
|
||||
}
|
||||
catch (InvalidRequestException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "{" + Joiner.on(", ").join(elements) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class Value extends Term.Terminal
|
||||
{
|
||||
public final Set<ByteBuffer> elements;
|
||||
|
||||
public Value(Set<ByteBuffer> elements)
|
||||
{
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public static Value fromSerialized(ByteBuffer value, SetType type) throws InvalidRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
// Collections have this small hack that validate cannot be called on a serialized object,
|
||||
// but compose does the validation (so we're fine).
|
||||
Set<?> s = type.compose(value);
|
||||
Set<ByteBuffer> elements = new LinkedHashSet<ByteBuffer>(s.size());
|
||||
for (Object element : s)
|
||||
elements.add(type.elements.decompose(element));
|
||||
return new Value(elements);
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuffer get()
|
||||
{
|
||||
return CollectionType.pack(new ArrayList<ByteBuffer>(elements), elements.size());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Marker extends AbstractMarker
|
||||
{
|
||||
protected Marker(int bindIndex, ColumnSpecification receiver)
|
||||
{
|
||||
super(bindIndex, receiver);
|
||||
assert receiver.type instanceof SetType;
|
||||
}
|
||||
|
||||
public Value bind(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
ByteBuffer value = values.get(bindIndex);
|
||||
return Value.fromSerialized(value, (SetType)receiver.type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Setter extends Operation
|
||||
{
|
||||
public Setter(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
// delete + add
|
||||
ColumnNameBuilder column = prefix.add(columnName.key);
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(column.build(), column.buildAsEndOfRange()));
|
||||
Adder.doAdd(t, cf, column, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Adder extends Operation
|
||||
{
|
||||
public Adder(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
doAdd(t, cf, prefix.add(columnName.key), params);
|
||||
}
|
||||
|
||||
static void doAdd(Term t, ColumnFamily cf, ColumnNameBuilder columnName, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal value = t.bind(params.variables);
|
||||
assert value instanceof Sets.Value;
|
||||
|
||||
Set<ByteBuffer> toAdd = ((Sets.Value)value).elements;
|
||||
for (ByteBuffer bb : toAdd)
|
||||
{
|
||||
ByteBuffer cellName = columnName.copy().add(bb).build();
|
||||
cf.addColumn(params.makeColumn(cellName, ByteBufferUtil.EMPTY_BYTE_BUFFER));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Discarder extends Operation
|
||||
{
|
||||
public Discarder(ColumnIdentifier column, Term t)
|
||||
{
|
||||
super(column, t);
|
||||
}
|
||||
|
||||
public void execute(ByteBuffer rowKey, ColumnFamily cf, ColumnNameBuilder prefix, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
Term.Terminal value = t.bind(params.variables);
|
||||
|
||||
// This can be either a set or a single element
|
||||
Set<ByteBuffer> toDiscard = value instanceof Constants.Value
|
||||
? Collections.singleton(((Constants.Value)value).bytes)
|
||||
: ((Sets.Value)value).elements;
|
||||
|
||||
ColumnNameBuilder column = prefix.add(columnName.key);
|
||||
for (ByteBuffer bb : toDiscard)
|
||||
{
|
||||
ByteBuffer cellName = column.copy().add(bb).build();
|
||||
cf.addColumn(params.makeTombstone(cellName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,236 +22,110 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.BytesType;
|
||||
import org.apache.cassandra.db.marshal.MarshalException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
/** A term parsed from a CQL statement. */
|
||||
public class Term
|
||||
/**
|
||||
* A CQL3 term, i.e. a column value with or without bind variables.
|
||||
*
|
||||
* A Term can be either terminal or non terminal. A term object is one that is typed and is obtained
|
||||
* from a raw term (Term.Raw) by poviding the actual receiver to which the term is supposed to be a
|
||||
* value of.
|
||||
*/
|
||||
public interface Term
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Term.class);
|
||||
/**
|
||||
* Collects the column specification for the bind variables in this Term.
|
||||
* This is obviously a no-op if the term is Terminal.
|
||||
*
|
||||
* @param boundNames the list of column specification where to collect the
|
||||
* bind variables of this term in.
|
||||
*/
|
||||
public void collectMarkerSpecification(ColumnSpecification[] boundNames);
|
||||
|
||||
public enum Type
|
||||
/**
|
||||
* Bind the values in this term to the values contained in {@code values}.
|
||||
* This is obviously a no-op if the term is Terminal.
|
||||
*
|
||||
* @param values the values to bind markers to.
|
||||
* @return the result of binding all the variables of this NonTerminal (or
|
||||
* 'this' if the term is terminal).
|
||||
*/
|
||||
public Terminal bind(List<ByteBuffer> values) throws InvalidRequestException;
|
||||
|
||||
/**
|
||||
* A shorter for bind(values).get().
|
||||
* We expose it mainly because for constants it can avoids allocating a temporary
|
||||
* object between the bind and the get (note that we still want to be able
|
||||
* to separate bind and get for collections).
|
||||
*/
|
||||
public ByteBuffer bindAndGet(List<ByteBuffer> values) throws InvalidRequestException;
|
||||
|
||||
/**
|
||||
* A parsed, non prepared (thus untyped) term.
|
||||
*
|
||||
* This can be one of:
|
||||
* - a constant
|
||||
* - a collection literal
|
||||
* - a function call
|
||||
* - a marker
|
||||
*/
|
||||
public interface Raw extends AssignementTestable
|
||||
{
|
||||
STRING, INTEGER, UUID, FLOAT, BOOLEAN, HEX, QMARK;
|
||||
|
||||
static Type forInt(int type)
|
||||
{
|
||||
if (type == CqlParser.STRING_LITERAL)
|
||||
return STRING;
|
||||
else if (type == CqlParser.INTEGER)
|
||||
return INTEGER;
|
||||
else if (type == CqlParser.UUID)
|
||||
return UUID;
|
||||
else if (type == CqlParser.FLOAT)
|
||||
return FLOAT;
|
||||
else if (type == CqlParser.BOOLEAN)
|
||||
return BOOLEAN;
|
||||
else if (type == CqlParser.HEXNUMBER)
|
||||
return HEX;
|
||||
else if (type == CqlParser.QMARK)
|
||||
return QMARK;
|
||||
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private final String text;
|
||||
private final Type type;
|
||||
public final int bindIndex;
|
||||
public final boolean isToken;
|
||||
|
||||
// For transition post-5198, see below
|
||||
private static volatile boolean stringAsBlobWarningLogged = false;
|
||||
|
||||
// This is a hack for the timeuuid functions (minTimeuuid, maxTimeuuid, now) because instead of handling them as
|
||||
// true function we let the TimeUUID.fromString() method handle it. We should probably clean that up someday
|
||||
private final boolean skipTypeValidation;
|
||||
|
||||
private Term(String text, Type type, int bindIndex, boolean isToken, boolean skipTypeValidation)
|
||||
{
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
this.bindIndex = bindIndex;
|
||||
this.isToken = isToken;
|
||||
this.skipTypeValidation = skipTypeValidation;
|
||||
}
|
||||
|
||||
public Term(String text, Type type, boolean skipTypeValidation)
|
||||
{
|
||||
this(text, type, -1, false, skipTypeValidation);
|
||||
}
|
||||
|
||||
public Term(String text, Type type)
|
||||
{
|
||||
this(text, type, -1, false, false);
|
||||
/**
|
||||
* This method validates this RawTerm is valid for provided column
|
||||
* specification and "prepare" this RawTerm, returning the resulting
|
||||
* prepared Term.
|
||||
*
|
||||
* @param receiver the "column" this RawTerm is supposed to be a value of. Note
|
||||
* that the ColumnSpecification may not correspond to a real column in the
|
||||
* case this RawTerm describe a list index or a map key, etc...
|
||||
* @return the prepared term.
|
||||
*/
|
||||
public Term prepare(ColumnSpecification receiver) throws InvalidRequestException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Term instance from a string, and an integer that corresponds
|
||||
* with the token ID from CQLParser.
|
||||
* A terminal term, i.e. one without any bind marker.
|
||||
*
|
||||
* @param text the text representation of the term.
|
||||
* @param type the term's type as an integer token ID.
|
||||
*/
|
||||
public Term(String text, int type)
|
||||
{
|
||||
this(text, Type.forInt(type));
|
||||
}
|
||||
|
||||
public Term(long value, Type type)
|
||||
{
|
||||
this(String.valueOf(value), type);
|
||||
}
|
||||
|
||||
public Term(String text, int type, int index)
|
||||
{
|
||||
this(text, Type.forInt(type), index, false, false);
|
||||
}
|
||||
|
||||
public static Term tokenOf(Term t)
|
||||
{
|
||||
return new Term(t.text, t.type, t.bindIndex, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text parsed to create this term.
|
||||
* This can be only one of:
|
||||
* - a constant value
|
||||
* - a collection value
|
||||
*
|
||||
* @return the string term acquired from a CQL statement.
|
||||
* Note that a terminal term will always have been type checked, and thus
|
||||
* consumer can (and should) assume so.
|
||||
*/
|
||||
public String getText()
|
||||
public abstract class Terminal implements Term
|
||||
{
|
||||
return isToken ? "token(" + text + ")" : text;
|
||||
}
|
||||
public void collectMarkerSpecification(ColumnSpecification[] boundNames) {}
|
||||
public Terminal bind(List<ByteBuffer> values) { return this; }
|
||||
|
||||
/**
|
||||
* Returns the typed value, serialized to a ByteBuffer according to a
|
||||
* comparator/validator.
|
||||
*
|
||||
* @return a ByteBuffer of the value.
|
||||
* @throws InvalidRequestException if unable to coerce the string to its type.
|
||||
*/
|
||||
public ByteBuffer getByteBuffer(AbstractType<?> validator, List<ByteBuffer> variables) throws InvalidRequestException
|
||||
{
|
||||
try
|
||||
/**
|
||||
* @return the serialized value of this terminal.
|
||||
*/
|
||||
public abstract ByteBuffer get();
|
||||
|
||||
public ByteBuffer bindAndGet(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
if (!isBindMarker())
|
||||
{
|
||||
// BytesType doesn't want it's input prefixed by '0x'.
|
||||
if (type == Type.HEX && validator instanceof BytesType)
|
||||
return validator.fromString(text.substring(2));
|
||||
return validator.fromString(text);
|
||||
}
|
||||
|
||||
// must be a marker term so check for a CqlBindValue stored in the term
|
||||
if (bindIndex == -1)
|
||||
throw new AssertionError("a marker Term was encountered with no index value");
|
||||
|
||||
ByteBuffer value = variables.get(bindIndex);
|
||||
// We don't yet support null values in prepared statements
|
||||
if (value == null)
|
||||
throw new InvalidRequestException("Invalid null value for prepared variable " + bindIndex);
|
||||
validator.validate(value);
|
||||
return value;
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateType(String identifier, AbstractType<?> validator) throws InvalidRequestException
|
||||
{
|
||||
if (skipTypeValidation)
|
||||
return;
|
||||
|
||||
Set<Type> supported = validator.supportedCQL3Constants();
|
||||
// Treat null specially as this mean "I don't have a supportedCQL3Type method"
|
||||
if (supported == null)
|
||||
return;
|
||||
|
||||
if (!supported.contains(type))
|
||||
{
|
||||
// Blobs should now be inputed as hexadecimal constants. However, to allow people to upgrade, we still allow
|
||||
// blob-as-strings, even though it is deprecated (see #5198).
|
||||
if (type == Type.STRING && validator instanceof BytesType)
|
||||
{
|
||||
if (!stringAsBlobWarningLogged)
|
||||
{
|
||||
stringAsBlobWarningLogged = true;
|
||||
logger.warn("Inputing CLQ3 blobs as strings (like %s = '%s') is now deprecated and will be removed in a future version. "
|
||||
+ "You should convert client code to use a blob constant (%s = %s) instead (see http://cassandra.apache.org/doc/cql3/CQL.html changelog section for more info).",
|
||||
identifier, text, identifier, "0x" + text);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Ideallly we'd keep the declared CQL3 type of columns and use that in the following message, instead of the AbstracType class name.
|
||||
throw new InvalidRequestException(String.format("Invalid %s constant for %s of type %s", type, identifier, validator.asCQL3Type()));
|
||||
return get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the term's type.
|
||||
* A non terminal term, i.e. one that contains at least one bind marker.
|
||||
*
|
||||
* @return the type
|
||||
* We distinguish between the following type of NonTerminal:
|
||||
* - marker for a constant value
|
||||
* - marker for a collection value (list, set, map)
|
||||
* - a function having bind marker
|
||||
*/
|
||||
public Type getType()
|
||||
public abstract class NonTerminal implements Term
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isBindMarker()
|
||||
{
|
||||
return type == Type.QMARK;
|
||||
}
|
||||
|
||||
public List<Term> asList()
|
||||
{
|
||||
return Collections.singletonList(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Term(%s, type=%s%s)", getText(), type, isToken ? ", isToken" : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1 + (isToken ? 1 : 0);
|
||||
result = prime * result + ((text == null) ? 0 : text.hashCode());
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Term other = (Term) obj;
|
||||
if (type==Type.QMARK) return false; // markers are never equal
|
||||
if (text == null)
|
||||
public ByteBuffer bindAndGet(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
if (other.text != null)
|
||||
return false;
|
||||
} else if (!text.equals(other.text))
|
||||
return false;
|
||||
if (type != other.type)
|
||||
return false;
|
||||
if (isToken != other.isToken)
|
||||
return false;
|
||||
return true;
|
||||
return bind(values).get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3;
|
||||
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
public class TypeCast implements Term.Raw
|
||||
{
|
||||
private final CQL3Type type;
|
||||
private final Term.Raw term;
|
||||
|
||||
public TypeCast(CQL3Type type, Term.Raw term)
|
||||
{
|
||||
this.type = type;
|
||||
this.term = term;
|
||||
}
|
||||
|
||||
public Term prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!term.isAssignableTo(castedSpecOf(receiver)))
|
||||
throw new InvalidRequestException(String.format("Cannot cast value %s to type %s", term, type));
|
||||
|
||||
if (!isAssignableTo(receiver))
|
||||
throw new InvalidRequestException(String.format("Cannot assign value %s to %s of type %s", this, receiver, receiver.type.asCQL3Type()));
|
||||
|
||||
return term.prepare(receiver);
|
||||
}
|
||||
|
||||
private ColumnSpecification castedSpecOf(ColumnSpecification receiver)
|
||||
{
|
||||
return new ColumnSpecification(receiver.ksName, receiver.cfName, new ColumnIdentifier(toString(), true), type.getType());
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
return receiver.type.equals(type.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "(" + type + ")" + term;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,9 +18,13 @@
|
|||
package org.apache.cassandra.cql3;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.cql3.statements.ColumnGroupMap;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
/**
|
||||
* A simple container that simplify passing parameters for collections methods.
|
||||
|
|
@ -32,12 +36,16 @@ public class UpdateParameters
|
|||
private final int ttl;
|
||||
public final int localDeletionTime;
|
||||
|
||||
public UpdateParameters(List<ByteBuffer> variables, long timestamp, int ttl)
|
||||
// For lists operation that require a read-before-write. Will be null otherwise.
|
||||
private final Map<ByteBuffer, ColumnGroupMap> prefetchedLists;
|
||||
|
||||
public UpdateParameters(List<ByteBuffer> variables, long timestamp, int ttl, Map<ByteBuffer, ColumnGroupMap> prefetchedLists)
|
||||
{
|
||||
this.variables = variables;
|
||||
this.timestamp = timestamp;
|
||||
this.ttl = ttl;
|
||||
this.localDeletionTime = (int)(System.currentTimeMillis() / 1000);
|
||||
this.prefetchedLists = prefetchedLists;
|
||||
}
|
||||
|
||||
public Column makeColumn(ByteBuffer name, ByteBuffer value)
|
||||
|
|
@ -61,4 +69,13 @@ public class UpdateParameters
|
|||
{
|
||||
return new RangeTombstone(start, end, timestamp - 1, localDeletionTime);
|
||||
}
|
||||
|
||||
public List<Pair<ByteBuffer, IColumn>> getPrefetchedList(ByteBuffer rowKey, ByteBuffer cql3ColumnName)
|
||||
{
|
||||
if (prefetchedLists == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
ColumnGroupMap m = prefetchedLists.get(rowKey);
|
||||
return m == null ? Collections.<Pair<ByteBuffer, IColumn>>emptyList() : m.getCollection(cql3ColumnName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.functions;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
public abstract class AbstractFunction implements Function
|
||||
{
|
||||
public final String name;
|
||||
public final List<AbstractType<?>> argsType;
|
||||
public final AbstractType<?> returnType;
|
||||
|
||||
protected AbstractFunction(String name, AbstractType<?> returnType, AbstractType<?>... argsType)
|
||||
{
|
||||
this.name = name;
|
||||
this.argsType = Arrays.asList(argsType);
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public String name()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<AbstractType<?>> argsType()
|
||||
{
|
||||
return argsType;
|
||||
}
|
||||
|
||||
public AbstractType<?> returnType()
|
||||
{
|
||||
return returnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trivial factory that always return the provided function.
|
||||
*/
|
||||
public static Function.Factory factory(final Function fun)
|
||||
{
|
||||
return new Function.Factory()
|
||||
{
|
||||
public Function create(String ksName, String cfName)
|
||||
{
|
||||
return fun;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.functions;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.BytesType;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
|
||||
public abstract class BytesConversionFcts
|
||||
{
|
||||
// Most of the XAsBlob and blobAsX functions are basically no-op since everything is
|
||||
// bytes internally. They only "trick" the type system.
|
||||
public static Function makeToBlobFunction(AbstractType<?> fromType)
|
||||
{
|
||||
String name = fromType.asCQL3Type() + "asblob";
|
||||
return new AbstractFunction(name, BytesType.instance, fromType)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return parameters.get(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Function makeFromBlobFunction(AbstractType<?> toType)
|
||||
{
|
||||
String name = "blobas" + toType.asCQL3Type();
|
||||
return new AbstractFunction(name, toType, BytesType.instance)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return parameters.get(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final Function VarcharAsBlobFct = new AbstractFunction("varcharasblob", BytesType.instance, UTF8Type.instance)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return parameters.get(0);
|
||||
}
|
||||
};
|
||||
|
||||
public static final Function BlobAsVarcharFact = new AbstractFunction("blobasvarchar", UTF8Type.instance, BytesType.instance)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return parameters.get(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.functions;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
public interface Function
|
||||
{
|
||||
public String name();
|
||||
public List<AbstractType<?>> argsType();
|
||||
public AbstractType<?> returnType();
|
||||
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException;
|
||||
|
||||
public interface Factory
|
||||
{
|
||||
// We allow the function to be parametered by the table it is part of because the
|
||||
// "token" function needs it (the argument depends on the keyValidator). However,
|
||||
// for most function, the factory will just always the same function object (see
|
||||
// AbstractFunction).
|
||||
public Function create(String ksName, String cfName);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.functions;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.Constants;
|
||||
import org.apache.cassandra.cql3.Lists;
|
||||
import org.apache.cassandra.cql3.Maps;
|
||||
import org.apache.cassandra.cql3.Sets;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
public class FunctionCall extends Term.NonTerminal
|
||||
{
|
||||
private final Function fun;
|
||||
private final List<Term> terms;
|
||||
|
||||
private FunctionCall(Function fun, List<Term> terms)
|
||||
{
|
||||
this.fun = fun;
|
||||
this.terms = terms;
|
||||
}
|
||||
|
||||
public void collectMarkerSpecification(ColumnSpecification[] boundNames)
|
||||
{
|
||||
for (Term t : terms)
|
||||
t.collectMarkerSpecification(boundNames);
|
||||
}
|
||||
|
||||
public Term.Terminal bind(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
return makeTerminal(fun, bindAndGet(values));
|
||||
}
|
||||
|
||||
public ByteBuffer bindAndGet(List<ByteBuffer> values) throws InvalidRequestException
|
||||
{
|
||||
List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(terms.size());
|
||||
for (Term t : terms)
|
||||
buffers.add(t.bindAndGet(values));
|
||||
|
||||
return fun.execute(buffers);
|
||||
}
|
||||
|
||||
private static Term.Terminal makeTerminal(Function fun, ByteBuffer result) throws InvalidRequestException
|
||||
{
|
||||
if (!(fun.returnType() instanceof CollectionType))
|
||||
return new Constants.Value(result);
|
||||
|
||||
switch (((CollectionType)fun.returnType()).kind)
|
||||
{
|
||||
case LIST: return Lists.Value.fromSerialized(result, (ListType)fun.returnType());
|
||||
case SET: return Sets.Value.fromSerialized(result, (SetType)fun.returnType());
|
||||
case MAP: return Maps.Value.fromSerialized(result, (MapType)fun.returnType());
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static class Raw implements Term.Raw
|
||||
{
|
||||
private final String functionName;
|
||||
private final List<Term.Raw> terms;
|
||||
|
||||
public Raw(String functionName, List<Term.Raw> terms)
|
||||
{
|
||||
this.functionName = functionName;
|
||||
this.terms = terms;
|
||||
}
|
||||
|
||||
public Term prepare(ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
Function fun = Functions.get(functionName, terms, receiver);
|
||||
|
||||
List<Term> parameters = new ArrayList<Term>(terms.size());
|
||||
boolean allTerminal = true;
|
||||
for (int i = 0; i < terms.size(); i++)
|
||||
{
|
||||
Term t = terms.get(i).prepare(Functions.makeArgSpec(receiver, fun, i));
|
||||
if (t instanceof NonTerminal)
|
||||
allTerminal = false;
|
||||
parameters.add(t);
|
||||
}
|
||||
|
||||
return allTerminal
|
||||
? makeTerminal(fun, execute(fun, parameters))
|
||||
: new FunctionCall(fun, parameters);
|
||||
}
|
||||
|
||||
// All parameters must be terminal
|
||||
private static ByteBuffer execute(Function fun, List<Term> parameters) throws InvalidRequestException
|
||||
{
|
||||
List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(parameters.size());
|
||||
for (Term t : parameters)
|
||||
{
|
||||
assert t instanceof Term.Terminal;
|
||||
buffers.add(((Term.Terminal)t).get());
|
||||
}
|
||||
return fun.execute(buffers);
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
AbstractType<?> returnType = Functions.getReturnType(functionName, receiver.ksName, receiver.cfName);
|
||||
return receiver.type.equals(returnType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(functionName).append("(");
|
||||
for (int i = 0; i < terms.size(); i++)
|
||||
{
|
||||
if (i > 0) sb.append(", ");
|
||||
sb.append(terms.get(i));
|
||||
}
|
||||
return sb.append(")").toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.functions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.AssignementTestable;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
public abstract class Functions
|
||||
{
|
||||
private Functions() {}
|
||||
|
||||
// If we ever allow this to be populated at runtime, this will need to be thread safe.
|
||||
private static final ArrayListMultimap<String, Function.Factory> declared = ArrayListMultimap.create();
|
||||
static
|
||||
{
|
||||
// All method sharing the same name must have the same returnType. We could find a way to make that clear.
|
||||
declared.put("token", TokenFct.factory);
|
||||
|
||||
declared.put("now", AbstractFunction.factory(TimeuuidFcts.nowFct));
|
||||
declared.put("mintimeuuid", AbstractFunction.factory(TimeuuidFcts.minTimeuuidFct));
|
||||
declared.put("maxtimeuuid", AbstractFunction.factory(TimeuuidFcts.maxTimeuuidFct));
|
||||
declared.put("dateof", AbstractFunction.factory(TimeuuidFcts.dateOfFct));
|
||||
declared.put("unixtimestampof", AbstractFunction.factory(TimeuuidFcts.unixTimestampOfFct));
|
||||
|
||||
for (CQL3Type type : CQL3Type.Native.values())
|
||||
{
|
||||
// Note: because text and varchar ends up being synonimous, our automatic makeToBlobFunction doesn't work
|
||||
// for varchar, so we special case it below. We also skip blob for obvious reasons.
|
||||
if (type == CQL3Type.Native.VARCHAR || type == CQL3Type.Native.BLOB)
|
||||
continue;
|
||||
|
||||
Function toBlob = BytesConversionFcts.makeToBlobFunction(type.getType());
|
||||
Function fromBlob = BytesConversionFcts.makeFromBlobFunction(type.getType());
|
||||
declared.put(toBlob.name(), AbstractFunction.factory(toBlob));
|
||||
declared.put(fromBlob.name(), AbstractFunction.factory(fromBlob));
|
||||
}
|
||||
declared.put("varcharasblob", AbstractFunction.factory(BytesConversionFcts.VarcharAsBlobFct));
|
||||
declared.put("blobasvarchar", AbstractFunction.factory(BytesConversionFcts.BlobAsVarcharFact));
|
||||
}
|
||||
|
||||
public static AbstractType<?> getReturnType(String functionName, String ksName, String cfName)
|
||||
{
|
||||
List<Function.Factory> factories = declared.get(functionName.toLowerCase());
|
||||
return factories.isEmpty()
|
||||
? null // That's ok, we'll complain later
|
||||
: factories.get(0).create(ksName, cfName).returnType();
|
||||
}
|
||||
|
||||
public static ColumnSpecification makeArgSpec(ColumnSpecification receiver, Function fun, int i)
|
||||
{
|
||||
return new ColumnSpecification(receiver.ksName,
|
||||
receiver.cfName,
|
||||
new ColumnIdentifier("arg" + i + "(" + fun.name() + ")", true),
|
||||
fun.argsType().get(i));
|
||||
}
|
||||
|
||||
public static Function get(String name, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
List<Function.Factory> factories = declared.get(name.toLowerCase());
|
||||
if (factories.isEmpty())
|
||||
throw new InvalidRequestException(String.format("Unknown CQL3 function %s called", name));
|
||||
|
||||
// Fast path if there is not choice
|
||||
if (factories.size() == 1)
|
||||
{
|
||||
Function fun = factories.get(0).create(receiver.ksName, receiver.cfName);
|
||||
validateTypes(fun, providedArgs, receiver);
|
||||
return fun;
|
||||
}
|
||||
|
||||
Function candidate = null;
|
||||
for (Function.Factory factory : factories)
|
||||
{
|
||||
Function toTest = factory.create(receiver.ksName, receiver.cfName);
|
||||
if (!isValidType(toTest, providedArgs, receiver))
|
||||
continue;
|
||||
|
||||
if (candidate == null)
|
||||
candidate = toTest;
|
||||
else
|
||||
throw new InvalidRequestException(String.format("Ambiguous call to function %s (can match both type signature %s and %s): use type casts to disambiguate", name, signature(candidate), signature(toTest)));
|
||||
}
|
||||
if (candidate == null)
|
||||
throw new InvalidRequestException(String.format("Invalid call to function %s, none of its type signature matches (known type signatures: %s)", name, signatures(factories, receiver)));
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private static void validateTypes(Function fun, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver) throws InvalidRequestException
|
||||
{
|
||||
if (!receiver.type.equals(fun.returnType()))
|
||||
throw new InvalidRequestException(String.format("Type error: cannot assign result of function %s (type %s) to %s (type %s)", fun.name(), fun.returnType().asCQL3Type(), receiver, receiver.type.asCQL3Type()));
|
||||
|
||||
if (providedArgs.size() != fun.argsType().size())
|
||||
throw new InvalidRequestException(String.format("Invalid number of arguments in call to function %s: %d required but % provided", fun.name(), fun.argsType().size(), providedArgs.size()));
|
||||
|
||||
for (int i = 0; i < providedArgs.size(); i++)
|
||||
{
|
||||
AssignementTestable provided = providedArgs.get(i);
|
||||
|
||||
// If the concrete argument is a bind variables, it can have any type.
|
||||
// We'll validate the actually provided value at execution time.
|
||||
if (provided == null)
|
||||
continue;
|
||||
|
||||
ColumnSpecification expected = makeArgSpec(receiver, fun, i);
|
||||
if (!provided.isAssignableTo(expected))
|
||||
throw new InvalidRequestException(String.format("Type error: %s cannot be passed as argument %d of function %s of type %s", provided, i, fun.name(), expected.type.asCQL3Type()));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidType(Function fun, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver)
|
||||
{
|
||||
if (!receiver.type.equals(fun.returnType()))
|
||||
return false;
|
||||
|
||||
if (providedArgs.size() != fun.argsType().size())
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < providedArgs.size(); i++)
|
||||
{
|
||||
AssignementTestable provided = providedArgs.get(i);
|
||||
|
||||
// If the concrete argument is a bind variables, it can have any type.
|
||||
// We'll validate the actually provided value at execution time.
|
||||
if (provided == null)
|
||||
continue;
|
||||
|
||||
ColumnSpecification expected = makeArgSpec(receiver, fun, i);
|
||||
if (!provided.isAssignableTo(expected))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String signature(Function fun)
|
||||
{
|
||||
List<AbstractType<?>> args = fun.argsType();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(");
|
||||
for (int i = 0; i < args.size(); i++)
|
||||
{
|
||||
if (i > 0) sb.append(", ");
|
||||
sb.append(args.get(i).asCQL3Type());
|
||||
}
|
||||
sb.append(") -> ");
|
||||
sb.append(fun.returnType().asCQL3Type());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String signatures(List<Function.Factory> factories, ColumnSpecification receiver)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < factories.size(); i++)
|
||||
{
|
||||
if (i > 0) sb.append(", ");
|
||||
sb.append(signature(factories.get(i).create(receiver.ksName, receiver.cfName)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.functions;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.DateType;
|
||||
import org.apache.cassandra.db.marshal.TimeUUIDType;
|
||||
import org.apache.cassandra.db.marshal.LongType;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
public abstract class TimeuuidFcts
|
||||
{
|
||||
public static final Function nowFct = new AbstractFunction("now", TimeUUIDType.instance)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
|
||||
}
|
||||
};
|
||||
|
||||
public static final Function minTimeuuidFct = new AbstractFunction("mintimeuuid", TimeUUIDType.instance, DateType.instance)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return ByteBuffer.wrap(UUIDGen.decompose(UUIDGen.minTimeUUID(DateType.instance.compose(parameters.get(0)).getTime())));
|
||||
}
|
||||
};
|
||||
|
||||
public static final Function maxTimeuuidFct = new AbstractFunction("maxtimeuuid", TimeUUIDType.instance, DateType.instance)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return ByteBuffer.wrap(UUIDGen.decompose(UUIDGen.maxTimeUUID(DateType.instance.compose(parameters.get(0)).getTime())));
|
||||
}
|
||||
};
|
||||
|
||||
public static final Function dateOfFct = new AbstractFunction("dateof", DateType.instance, TimeUUIDType.instance)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return DateType.instance.decompose(new Date(UUIDGen.unixTimestamp(UUIDGen.getUUID(parameters.get(0)))));
|
||||
}
|
||||
};
|
||||
|
||||
public static final Function unixTimestampOfFct = new AbstractFunction("unixtimestampof", LongType.instance, TimeUUIDType.instance)
|
||||
{
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters)
|
||||
{
|
||||
return ByteBufferUtil.bytes(UUIDGen.unixTimestamp(UUIDGen.getUUID(parameters.get(0))));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.functions;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.cql3.CFDefinition;
|
||||
import org.apache.cassandra.cql3.ColumnNameBuilder;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
|
||||
public class TokenFct extends AbstractFunction
|
||||
{
|
||||
// The actual token function depends on the partitioner used
|
||||
private static final IPartitioner partitioner = StorageService.instance.getPartitioner();
|
||||
|
||||
public static final Function.Factory factory = new Function.Factory()
|
||||
{
|
||||
public Function create(String ksName, String cfName)
|
||||
{
|
||||
return new TokenFct(Schema.instance.getCFMetaData(ksName, cfName));
|
||||
}
|
||||
};
|
||||
|
||||
private final CFDefinition cfDef;
|
||||
|
||||
public TokenFct(CFMetaData cfm)
|
||||
{
|
||||
super("token", partitioner.getTokenValidator(), getKeyTypes(cfm));
|
||||
this.cfDef = cfm.getCfDef();
|
||||
}
|
||||
|
||||
private static AbstractType[] getKeyTypes(CFMetaData cfm)
|
||||
{
|
||||
AbstractType[] types = new AbstractType[cfm.getCfDef().keys.size()];
|
||||
int i = 0;
|
||||
for (CFDefinition.Name name : cfm.getCfDef().keys.values())
|
||||
types[i++] = name.type;
|
||||
return types;
|
||||
}
|
||||
|
||||
public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException
|
||||
{
|
||||
ColumnNameBuilder builder = cfDef.getKeyNameBuilder();
|
||||
for (ByteBuffer bb : parameters)
|
||||
builder.add(bb);
|
||||
return partitioner.getTokenFactory().toByteArray(partitioner.getToken(builder.build()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.operations;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.filter.QueryPath;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.db.marshal.LongType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
public class ColumnOperation implements Operation
|
||||
{
|
||||
enum Kind { SET, COUNTER_INC, COUNTER_DEC }
|
||||
|
||||
private static final Operation setToEmptyOperation = new ColumnOperation(null, Kind.SET)
|
||||
{
|
||||
@Override
|
||||
protected void doSet(ColumnFamily cf, ColumnNameBuilder builder, AbstractType<?> validator, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
ByteBuffer colName = builder.build();
|
||||
QueryProcessor.validateColumnName(colName);
|
||||
cf.addColumn(params.makeColumn(colName, ByteBufferUtil.EMPTY_BYTE_BUFFER));
|
||||
}
|
||||
};
|
||||
|
||||
private final Term value;
|
||||
private final Kind kind;
|
||||
|
||||
private ColumnOperation(Term value, Kind kind)
|
||||
{
|
||||
this.value = value;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public void execute(ColumnFamily cf,
|
||||
ColumnNameBuilder builder,
|
||||
AbstractType<?> validator,
|
||||
UpdateParameters params,
|
||||
List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case SET:
|
||||
doSet(cf, builder, validator, params);
|
||||
break;
|
||||
case COUNTER_INC:
|
||||
case COUNTER_DEC:
|
||||
doCounter(cf, builder, params);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported operation: " + kind);
|
||||
}
|
||||
}
|
||||
|
||||
protected void doSet(ColumnFamily cf, ColumnNameBuilder builder, AbstractType<?> validator, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
ByteBuffer colName = builder.build();
|
||||
QueryProcessor.validateColumnName(colName);
|
||||
|
||||
ByteBuffer valueBytes = value.getByteBuffer(validator, params.variables);
|
||||
cf.addColumn(params.makeColumn(colName, valueBytes));
|
||||
}
|
||||
|
||||
private void doCounter(ColumnFamily cf, ColumnNameBuilder builder, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
long val;
|
||||
|
||||
try
|
||||
{
|
||||
val = ByteBufferUtil.toLong(value.getByteBuffer(LongType.instance, params.variables));
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
throw new InvalidRequestException(String.format("'%s' is an invalid value, should be a long.", value.getText()));
|
||||
}
|
||||
|
||||
if (kind == Kind.COUNTER_DEC)
|
||||
{
|
||||
if (val == Long.MIN_VALUE)
|
||||
throw new InvalidRequestException("The negation of " + val + " overflows supported integer precision (signed 8 bytes integer)");
|
||||
else
|
||||
val = -val;
|
||||
}
|
||||
|
||||
cf.addCounter(new QueryPath(cf.metadata().cfName, null, builder.build()), val);
|
||||
}
|
||||
|
||||
public Operation validateAndAddBoundNames(ColumnSpecification column, ColumnSpecification[] boundNames) throws InvalidRequestException
|
||||
{
|
||||
value.validateType(column.name.toString(), column.type);
|
||||
|
||||
if (value.isBindMarker())
|
||||
boundNames[value.bindIndex] = column;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Term> getValues()
|
||||
{
|
||||
return Collections.singletonList(value);
|
||||
}
|
||||
|
||||
public boolean requiresRead(AbstractType<?> validator)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
{
|
||||
return kind == Kind.COUNTER_DEC || kind == Kind.COUNTER_INC ? Type.COUNTER : Type.COLUMN;
|
||||
}
|
||||
|
||||
/* Utility methods */
|
||||
|
||||
public static Operation Set(Term value)
|
||||
{
|
||||
return new ColumnOperation(value, Kind.SET);
|
||||
}
|
||||
|
||||
public static Operation CounterInc(Term value)
|
||||
{
|
||||
return new ColumnOperation(value, Kind.COUNTER_INC);
|
||||
}
|
||||
|
||||
public static Operation CounterDec(Term value)
|
||||
{
|
||||
return new ColumnOperation(value, Kind.COUNTER_DEC);
|
||||
}
|
||||
|
||||
public static Operation SetToEmpty()
|
||||
{
|
||||
return setToEmptyOperation;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,410 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.operations;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.ColumnNameBuilder;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.cql3.UpdateParameters;
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.MarshalException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
public class ListOperation implements Operation
|
||||
{
|
||||
// Our reference time (1 jan 2010, 00:00:00) in milliseconds.
|
||||
private static final long REFERENCE_TIME = 1262304000000L;
|
||||
|
||||
/*
|
||||
* For prepend, we need to be able to generate unique but decreasing time
|
||||
* UUID, which is a bit challenging. To do that, given a time in milliseconds,
|
||||
* we adds a number representing the 100-nanoseconds precision and make sure
|
||||
* that within the same millisecond, that number is always decreasing. We
|
||||
* do rely on the fact that the user will only provide decreasing
|
||||
* milliseconds timestamp for that purpose.
|
||||
*/
|
||||
private static class PrecisionTime
|
||||
{
|
||||
public final long millis;
|
||||
public final int nanos;
|
||||
|
||||
public PrecisionTime(long millis, int nanos)
|
||||
{
|
||||
this.millis = millis;
|
||||
this.nanos = nanos;
|
||||
}
|
||||
}
|
||||
|
||||
private static final AtomicReference<PrecisionTime> last = new AtomicReference<PrecisionTime>(new PrecisionTime(Long.MAX_VALUE, 0));
|
||||
|
||||
private static PrecisionTime getNextTime(long millis)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
PrecisionTime current = last.get();
|
||||
|
||||
assert millis <= current.millis;
|
||||
PrecisionTime next = millis < current.millis
|
||||
? new PrecisionTime(millis, 9999)
|
||||
: new PrecisionTime(millis, Math.max(0, current.nanos - 1));
|
||||
|
||||
if (last.compareAndSet(current, next))
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
enum Kind { SET, SET_IDX, APPEND, PREPEND, DISCARD, DISCARD_IDX }
|
||||
|
||||
private final List<Term> values;
|
||||
private final Kind kind;
|
||||
|
||||
private ListOperation(List<Term> values, Kind kind)
|
||||
{
|
||||
this.values = Collections.unmodifiableList(values);
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public void execute(ColumnFamily cf,
|
||||
ColumnNameBuilder builder,
|
||||
AbstractType<?> validator,
|
||||
UpdateParameters params,
|
||||
List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
if (!(validator instanceof ListType || (kind == Kind.SET_IDX && validator instanceof MapType)))
|
||||
throw new InvalidRequestException("List operations are only supported on List typed columns, but " + validator + " given.");
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case SET:
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(builder.copy().build(), builder.copy().buildAsEndOfRange()));
|
||||
doAppend(cf, builder, (CollectionType)validator, params);
|
||||
break;
|
||||
case SET_IDX:
|
||||
// Since the parser couldn't disambiguate between a 'list set by idx'
|
||||
// and a 'map put by key', we have to do it now.
|
||||
if (validator instanceof MapType)
|
||||
{
|
||||
assert values.size() == 2;
|
||||
MapOperation.Put(values.get(0), values.get(1)).execute(cf, builder, validator, params, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
doSet(cf, builder, params, (CollectionType)validator, list);
|
||||
}
|
||||
break;
|
||||
case APPEND:
|
||||
doAppend(cf, builder, (CollectionType)validator, params);
|
||||
break;
|
||||
case PREPEND:
|
||||
doPrepend(cf, builder, (CollectionType)validator, params);
|
||||
break;
|
||||
case DISCARD:
|
||||
doDiscard(cf, (CollectionType)validator, params, list);
|
||||
break;
|
||||
case DISCARD_IDX:
|
||||
doDiscardIdx(cf, params, list);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported List operation: " + kind);
|
||||
}
|
||||
}
|
||||
|
||||
public static void doSetFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, ListType validator, Term values, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(builder.copy().build(), builder.copy().buildAsEndOfRange()));
|
||||
doAppendFromPrepared(cf, builder, validator, values, params);
|
||||
}
|
||||
|
||||
public static void doAppendFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, ListType validator, Term values, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
try
|
||||
{
|
||||
List<?> l = validator.compose(params.variables.get(values.bindIndex));
|
||||
|
||||
for (int i = 0; i < l.size(); i++)
|
||||
{
|
||||
ColumnNameBuilder b = i == l.size() - 1 ? builder : builder.copy();
|
||||
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
|
||||
ByteBuffer name = b.add(uuid).build();
|
||||
cf.addColumn(params.makeColumn(name, validator.valueComparator().decompose(l.get(i))));
|
||||
}
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void doPrependFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, ListType validator, Term values, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
long time = REFERENCE_TIME - (System.currentTimeMillis() - REFERENCE_TIME);
|
||||
|
||||
try
|
||||
{
|
||||
List<?> l = validator.compose(params.variables.get(values.bindIndex));
|
||||
|
||||
for (int i = 0; i < l.size(); i++)
|
||||
{
|
||||
ColumnNameBuilder b = i == l.size() - 1 ? builder : builder.copy();
|
||||
PrecisionTime pt = getNextTime(time);
|
||||
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(pt.millis, pt.nanos));
|
||||
ByteBuffer name = b.add(uuid).build();
|
||||
cf.addColumn(params.makeColumn(name, validator.valueComparator().decompose(l.get(i))));
|
||||
}
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void doDiscardFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, ListType validator, Term values, UpdateParameters params, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
List<?> l = validator.compose(params.variables.get(values.bindIndex));
|
||||
|
||||
Set<ByteBuffer> toDiscard = new HashSet<ByteBuffer>();
|
||||
for (Object elt : l)
|
||||
toDiscard.add(validator.valueComparator().decompose(elt));
|
||||
|
||||
for (Pair<ByteBuffer, IColumn> p : list)
|
||||
{
|
||||
IColumn c = p.right;
|
||||
if (toDiscard.contains(c.value()))
|
||||
cf.addColumn(params.makeTombstone(c.name()));
|
||||
}
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void doSet(ColumnFamily cf, ColumnNameBuilder builder, UpdateParameters params, CollectionType validator, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
int idx = validateListIdx(values.get(0), list);
|
||||
Term value = values.get(1);
|
||||
|
||||
ByteBuffer name = list.get(idx).right.name();
|
||||
cf.addColumn(params.makeColumn(name, value.getByteBuffer(validator.valueComparator(), params.variables)));
|
||||
}
|
||||
|
||||
private void doAppend(ColumnFamily cf, ColumnNameBuilder builder, CollectionType validator, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
for (int i = 0; i < values.size(); i++)
|
||||
{
|
||||
ColumnNameBuilder b = i == values.size() - 1 ? builder : builder.copy();
|
||||
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
|
||||
ByteBuffer name = b.add(uuid).build();
|
||||
cf.addColumn(params.makeColumn(name, values.get(i).getByteBuffer(validator.valueComparator(), params.variables)));
|
||||
}
|
||||
}
|
||||
|
||||
private void doPrepend(ColumnFamily cf, ColumnNameBuilder builder, CollectionType validator, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
long time = REFERENCE_TIME - (System.currentTimeMillis() - REFERENCE_TIME);
|
||||
|
||||
for (int i = 0; i < values.size(); i++)
|
||||
{
|
||||
ColumnNameBuilder b = i == values.size() - 1 ? builder : builder.copy();
|
||||
PrecisionTime pt = getNextTime(time);
|
||||
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(pt.millis, pt.nanos));
|
||||
ByteBuffer name = b.add(uuid).build();
|
||||
cf.addColumn(params.makeColumn(name, values.get(i).getByteBuffer(validator.valueComparator(), params.variables)));
|
||||
}
|
||||
}
|
||||
|
||||
private void doDiscard(ColumnFamily cf, CollectionType validator, UpdateParameters params, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
Set<ByteBuffer> toDiscard = new HashSet<ByteBuffer>();
|
||||
|
||||
for (Term value : values)
|
||||
toDiscard.add(value.getByteBuffer(validator.valueComparator(), params.variables));
|
||||
|
||||
for (Pair<ByteBuffer, IColumn> p : list)
|
||||
{
|
||||
IColumn c = p.right;
|
||||
if (toDiscard.contains(c.value()))
|
||||
cf.addColumn(params.makeTombstone(c.name()));
|
||||
}
|
||||
}
|
||||
|
||||
private void doDiscardIdx(ColumnFamily cf, UpdateParameters params, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
int idx = validateListIdx(values.get(0), list);
|
||||
cf.addColumn(params.makeTombstone(list.get(idx).right.name()));
|
||||
}
|
||||
|
||||
public Operation validateAndAddBoundNames(ColumnSpecification column, ColumnSpecification[] boundNames) throws InvalidRequestException
|
||||
{
|
||||
// Since the parser couldn't disambiguate between a 'list set by idx'
|
||||
// and a 'map put by key', we have to do it now.
|
||||
if (kind == Kind.SET_IDX && (column.type instanceof MapType))
|
||||
{
|
||||
assert values.size() == 2;
|
||||
return MapOperation.Put(values.get(0), values.get(1)).validateAndAddBoundNames(column, boundNames);
|
||||
}
|
||||
|
||||
if (!(column.type instanceof ListType))
|
||||
throw new InvalidRequestException(String.format("Cannot apply list operation on column %s of type %s", column, column.type));
|
||||
|
||||
ListType lt = (ListType)column.type;
|
||||
if (kind == Kind.SET_IDX)
|
||||
{
|
||||
assert values.size() == 2;
|
||||
Term idx = values.get(0);
|
||||
idx.validateType("list index", Int32Type.instance);
|
||||
|
||||
Term value = values.get(1);
|
||||
value.validateType(column + " element", lt.elements);
|
||||
|
||||
if (idx.isBindMarker())
|
||||
boundNames[idx.bindIndex] = indexSpecOf(column);
|
||||
if (value.isBindMarker())
|
||||
boundNames[value.bindIndex] = valueSpecOf(column, lt);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Term t : values)
|
||||
{
|
||||
t.validateType(column + " element", lt.elements);
|
||||
if (t.isBindMarker())
|
||||
boundNames[t.bindIndex] = column;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public static ColumnSpecification indexSpecOf(ColumnSpecification column)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("idx(" + column.name + ")", true), Int32Type.instance);
|
||||
}
|
||||
|
||||
public static ColumnSpecification valueSpecOf(ColumnSpecification column, ListType type)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), type.elements);
|
||||
}
|
||||
|
||||
public List<Term> getValues()
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
public boolean requiresRead(AbstractType<?> validator)
|
||||
{
|
||||
return kind == Kind.DISCARD || kind == Kind.DISCARD_IDX || kind == Kind.SET_IDX;
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
{
|
||||
return Type.LIST;
|
||||
}
|
||||
|
||||
/* Utility methods */
|
||||
|
||||
public static Operation Set(List<Term> values)
|
||||
{
|
||||
return new ListOperation(values, Kind.SET);
|
||||
}
|
||||
|
||||
public static Operation SetIndex(List<Term> values)
|
||||
{
|
||||
return new ListOperation(values, Kind.SET_IDX);
|
||||
}
|
||||
|
||||
public static Operation Append(List<Term> values)
|
||||
{
|
||||
return new ListOperation(values, Kind.APPEND);
|
||||
}
|
||||
|
||||
public static Operation Prepend(List<Term> values)
|
||||
{
|
||||
return new ListOperation(values, Kind.PREPEND);
|
||||
}
|
||||
|
||||
public static Operation Discard(List<Term> values)
|
||||
{
|
||||
return new ListOperation(values, Kind.DISCARD);
|
||||
}
|
||||
|
||||
public static Operation DiscardKey(List<Term> values)
|
||||
{
|
||||
return new ListOperation(values, Kind.DISCARD_IDX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "ListOperation(" + kind + ", " + values + ")";
|
||||
}
|
||||
|
||||
private int validateListIdx(Term value, List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
if (value.getType() != Term.Type.INTEGER)
|
||||
throw new InvalidRequestException(String.format("Invalid argument %s for %s, must be an integer.", value.getText(), getType()));
|
||||
|
||||
int idx = Integer.parseInt(value.getText());
|
||||
if (list == null || list.size() <= idx)
|
||||
throw new InvalidRequestException(String.format("Invalid index %d, list has size %d", idx, list == null ? 0 : list.size()));
|
||||
|
||||
return idx;
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
// This should not happen, unless we screwed up the parser
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.operations;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.ColumnNameBuilder;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.cql3.UpdateParameters;
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.MarshalException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
public class MapOperation implements Operation
|
||||
{
|
||||
enum Kind { SET, PUT, DISCARD }
|
||||
|
||||
private final Map<Term, Term> values;
|
||||
private final Term discardKey;
|
||||
private final Kind kind;
|
||||
|
||||
private MapOperation(Map<Term, Term> values, Kind kind)
|
||||
{
|
||||
this.values = values;
|
||||
this.discardKey = null;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
private MapOperation(Term discardKey)
|
||||
{
|
||||
this.values = null;
|
||||
this.discardKey = discardKey;
|
||||
this.kind = Kind.DISCARD;
|
||||
}
|
||||
|
||||
public void execute(ColumnFamily cf,
|
||||
ColumnNameBuilder builder,
|
||||
AbstractType<?> validator,
|
||||
UpdateParameters params,
|
||||
List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
if (!(validator instanceof MapType))
|
||||
throw new InvalidRequestException("Map operations are only supported on Map typed columns, but " + validator + " given.");
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case SET: // fallthrough on purpose; remove previous Map before setting (PUT) the new one
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(builder.copy().build(), builder.copy().buildAsEndOfRange()));
|
||||
case PUT:
|
||||
doPut(cf, builder, (CollectionType)validator, params);
|
||||
break;
|
||||
case DISCARD:
|
||||
doDiscard(cf, builder, (CollectionType)validator, params);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported Map operation: " + kind);
|
||||
}
|
||||
}
|
||||
|
||||
public static void doSetFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, MapType validator, Term values, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(builder.copy().build(), builder.copy().buildAsEndOfRange()));
|
||||
doPutFromPrepared(cf, builder, validator, values, params);
|
||||
}
|
||||
|
||||
public static void doPutFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, MapType validator, Term values, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
try
|
||||
{
|
||||
Map<?, ?> m = validator.compose(params.variables.get(values.bindIndex));
|
||||
for (Map.Entry<?, ?> entry : m.entrySet())
|
||||
{
|
||||
ByteBuffer name = builder.copy().add(validator.nameComparator().decompose(entry.getKey())).build();
|
||||
ByteBuffer value = validator.valueComparator().decompose(entry.getValue());
|
||||
cf.addColumn(params.makeColumn(name, value));
|
||||
}
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void doPut(ColumnFamily cf, ColumnNameBuilder builder, CollectionType validator, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
for (Map.Entry<Term, Term> entry : values.entrySet())
|
||||
{
|
||||
ByteBuffer name = builder.copy().add(entry.getKey().getByteBuffer(validator.nameComparator(), params.variables)).build();
|
||||
ByteBuffer value = entry.getValue().getByteBuffer(validator.valueComparator(), params.variables);
|
||||
cf.addColumn(params.makeColumn(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
private void doDiscard(ColumnFamily cf, ColumnNameBuilder builder, CollectionType validator, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
ByteBuffer name = builder.add(discardKey.getByteBuffer(validator.nameComparator(), params.variables)).build();
|
||||
cf.addColumn(params.makeTombstone(name));
|
||||
}
|
||||
|
||||
public Operation validateAndAddBoundNames(ColumnSpecification column, ColumnSpecification[] boundNames) throws InvalidRequestException
|
||||
{
|
||||
if (!(column.type instanceof MapType))
|
||||
throw new InvalidRequestException(String.format("Cannot apply map operation on column %s of type %s", column, column.type));
|
||||
|
||||
MapType mt = (MapType)column.type;
|
||||
for (Map.Entry<Term, Term> entry : values.entrySet())
|
||||
{
|
||||
Term key = entry.getKey();
|
||||
key.validateType(column + " key", mt.keys);
|
||||
|
||||
Term value = entry.getValue();
|
||||
value.validateType(column + " value", mt.values);
|
||||
|
||||
if (key.isBindMarker())
|
||||
boundNames[key.bindIndex] = keySpecOf(column, mt);
|
||||
if (value.isBindMarker())
|
||||
boundNames[value.bindIndex] = valueSpecOf(column, mt);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public static ColumnSpecification keySpecOf(ColumnSpecification column, MapType type)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("key(" + column.name + ")", true), type.keys);
|
||||
}
|
||||
|
||||
public static ColumnSpecification valueSpecOf(ColumnSpecification column, MapType type)
|
||||
{
|
||||
return new ColumnSpecification(column.ksName, column.cfName, new ColumnIdentifier("value(" + column.name + ")", true), type.values);
|
||||
}
|
||||
|
||||
public List<Term> getValues()
|
||||
{
|
||||
List<Term> l = new ArrayList<Term>(2 * values.size());
|
||||
for (Map.Entry<Term, Term> entry : values.entrySet())
|
||||
{
|
||||
l.add(entry.getKey());
|
||||
l.add(entry.getValue());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
public boolean requiresRead(AbstractType<?> validator)
|
||||
{
|
||||
return kind == Kind.SET || kind == Kind.DISCARD;
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
{
|
||||
return Type.MAP;
|
||||
}
|
||||
|
||||
/* Utility methods */
|
||||
|
||||
public static Operation Set(Map<Term, Term> values)
|
||||
{
|
||||
return new MapOperation(values, Kind.SET);
|
||||
}
|
||||
|
||||
public static Operation Put(Map<Term, Term> values)
|
||||
{
|
||||
return new MapOperation(values, Kind.PUT);
|
||||
}
|
||||
|
||||
public static Operation Put(final Term key, final Term value)
|
||||
{
|
||||
return Put(new HashMap<Term, Term>(1) {{ put(key, value); }});
|
||||
}
|
||||
|
||||
public static Operation DiscardKey(Term discardKey)
|
||||
{
|
||||
return new MapOperation(discardKey);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.operations;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnNameBuilder;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.cql3.UpdateParameters;
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
public interface Operation
|
||||
{
|
||||
public static enum Type { COLUMN, COUNTER, LIST, SET, MAP, PREPARED }
|
||||
|
||||
public void execute(ColumnFamily cf,
|
||||
ColumnNameBuilder builder,
|
||||
AbstractType<?> validator,
|
||||
UpdateParameters params,
|
||||
List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException;
|
||||
|
||||
public Operation validateAndAddBoundNames(ColumnSpecification column, ColumnSpecification[] boundNames) throws InvalidRequestException;
|
||||
|
||||
public List<Term> getValues();
|
||||
|
||||
public boolean requiresRead(AbstractType<?> validator);
|
||||
|
||||
public Type getType();
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.operations;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.CounterColumnType;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
public class PreparedOperation implements Operation
|
||||
{
|
||||
public enum Kind { SET, PREPARED_PLUS, PLUS_PREPARED, MINUS_PREPARED }
|
||||
|
||||
private final Term preparedValue;
|
||||
private final Kind kind;
|
||||
|
||||
public PreparedOperation(Term value, Kind kind)
|
||||
{
|
||||
assert value.isBindMarker();
|
||||
this.preparedValue = value;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public void execute(ColumnFamily cf,
|
||||
ColumnNameBuilder builder,
|
||||
AbstractType<?> validator,
|
||||
UpdateParameters params,
|
||||
List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
if (validator instanceof CollectionType)
|
||||
{
|
||||
switch (((CollectionType)validator).kind)
|
||||
{
|
||||
case LIST:
|
||||
switch (kind)
|
||||
{
|
||||
case SET:
|
||||
ListOperation.doSetFromPrepared(cf, builder, (ListType)validator, preparedValue, params);
|
||||
break;
|
||||
case PREPARED_PLUS:
|
||||
ListOperation.doPrependFromPrepared(cf, builder, (ListType)validator, preparedValue, params);
|
||||
break;
|
||||
case PLUS_PREPARED:
|
||||
ListOperation.doAppendFromPrepared(cf, builder, (ListType)validator, preparedValue, params);
|
||||
break;
|
||||
case MINUS_PREPARED:
|
||||
ListOperation.doDiscardFromPrepared(cf, builder, (ListType)validator, preparedValue, params, list);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SET:
|
||||
switch (kind)
|
||||
{
|
||||
case SET:
|
||||
SetOperation.doSetFromPrepared(cf, builder, (SetType)validator, preparedValue, params);
|
||||
break;
|
||||
case PLUS_PREPARED:
|
||||
SetOperation.doAddFromPrepared(cf, builder, (SetType)validator, preparedValue, params);
|
||||
break;
|
||||
case MINUS_PREPARED:
|
||||
SetOperation.doDiscardFromPrepared(cf, builder, (SetType)validator, preparedValue, params);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MAP:
|
||||
switch (kind)
|
||||
{
|
||||
case SET:
|
||||
MapOperation.doSetFromPrepared(cf, builder, (MapType)validator, preparedValue, params);
|
||||
break;
|
||||
case PLUS_PREPARED:
|
||||
MapOperation.doPutFromPrepared(cf, builder, (MapType)validator, preparedValue, params);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case SET:
|
||||
ColumnOperation.Set(preparedValue).execute(cf, builder, validator, params, null);
|
||||
break;
|
||||
case PLUS_PREPARED:
|
||||
ColumnOperation.CounterInc(preparedValue).execute(cf, builder, validator, params, null);
|
||||
break;
|
||||
case MINUS_PREPARED:
|
||||
ColumnOperation.CounterDec(preparedValue).execute(cf, builder, validator, params, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Operation validateAndAddBoundNames(ColumnSpecification column, ColumnSpecification[] boundNames) throws InvalidRequestException
|
||||
{
|
||||
if (column.type instanceof CollectionType)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case PREPARED_PLUS:
|
||||
if (column.type instanceof MapType)
|
||||
throw new InvalidRequestException("Unsupported syntax, cannot put to a prepared map");
|
||||
if (column.type instanceof SetType)
|
||||
throw new InvalidRequestException("Unsupported syntax, cannot add to a prepared set");
|
||||
break;
|
||||
case MINUS_PREPARED:
|
||||
if (column.type instanceof MapType)
|
||||
throw new InvalidRequestException("Unsuppoted syntax, discard syntax for map not supported");
|
||||
break;
|
||||
}
|
||||
switch (((CollectionType)column.type).kind)
|
||||
{
|
||||
}
|
||||
}
|
||||
else if (column.type instanceof CounterColumnType)
|
||||
{
|
||||
if (kind == Kind.PREPARED_PLUS)
|
||||
throw new InvalidRequestException("Unsupported syntax for increment, must be of the form X = X + <value>");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any other operation than a set is invalid
|
||||
if (kind != Kind.SET)
|
||||
throw new InvalidRequestException(String.format("Invalid operation for %s of type %s", column, column.type));
|
||||
}
|
||||
|
||||
if (preparedValue.isBindMarker())
|
||||
boundNames[preparedValue.bindIndex] = column;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Term> getValues()
|
||||
{
|
||||
return Collections.singletonList(preparedValue);
|
||||
}
|
||||
|
||||
public boolean requiresRead(AbstractType<?> validator)
|
||||
{
|
||||
// Only prepared operation requiring a read is list discard
|
||||
return (validator instanceof ListType) && kind == Kind.MINUS_PREPARED;
|
||||
}
|
||||
|
||||
public boolean isPotentialCounterOperation()
|
||||
{
|
||||
return kind == Kind.PLUS_PREPARED || kind == Kind.MINUS_PREPARED;
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
{
|
||||
return Type.PREPARED;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.operations;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnNameBuilder;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.cql3.UpdateParameters;
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.CollectionType;
|
||||
import org.apache.cassandra.db.marshal.MarshalException;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
public class SetOperation implements Operation
|
||||
{
|
||||
enum Kind { SET, ADD, DISCARD }
|
||||
|
||||
private final List<Term> values;
|
||||
private final Kind kind;
|
||||
|
||||
private SetOperation(List<Term> values, Kind kind)
|
||||
{
|
||||
this.values = values;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public void execute(ColumnFamily cf,
|
||||
ColumnNameBuilder builder,
|
||||
AbstractType<?> validator,
|
||||
UpdateParameters params,
|
||||
List<Pair<ByteBuffer, IColumn>> list) throws InvalidRequestException
|
||||
{
|
||||
if (!(validator instanceof SetType))
|
||||
throw new InvalidRequestException("Set operations are only supported on Set typed columns, but " + validator + " given.");
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case SET: // fallthrough on purpose; remove previous Set before setting (ADD) the new one
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(builder.copy().build(), builder.copy().buildAsEndOfRange()));
|
||||
case ADD:
|
||||
doAdd(cf, builder, (CollectionType)validator, params);
|
||||
break;
|
||||
case DISCARD:
|
||||
doDiscard(cf, builder, (CollectionType)validator, params);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported Set operation: " + kind);
|
||||
}
|
||||
}
|
||||
|
||||
public static void doSetFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, SetType validator, Term values, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
cf.addAtom(params.makeTombstoneForOverwrite(builder.copy().build(), builder.copy().buildAsEndOfRange()));
|
||||
doAddFromPrepared(cf, builder, validator, values, params);
|
||||
}
|
||||
|
||||
public static void doAddFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, SetType validator, Term values, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
try
|
||||
{
|
||||
Set<?> s = validator.compose(params.variables.get(values.bindIndex));
|
||||
Iterator<?> iter = s.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
|
||||
ByteBuffer name = b.add(validator.nameComparator().decompose(iter.next())).build();
|
||||
cf.addColumn(params.makeColumn(name, ByteBufferUtil.EMPTY_BYTE_BUFFER));
|
||||
}
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void doDiscardFromPrepared(ColumnFamily cf, ColumnNameBuilder builder, SetType validator, Term values, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
if (!values.isBindMarker())
|
||||
throw new InvalidRequestException("Can't apply operation on column with " + validator + " type.");
|
||||
|
||||
try
|
||||
{
|
||||
Set<?> s = validator.compose(params.variables.get(values.bindIndex));
|
||||
Iterator<?> iter = s.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
|
||||
ByteBuffer name = b.add(validator.nameComparator().decompose(iter.next())).build();
|
||||
cf.addColumn(params.makeTombstone(name));
|
||||
}
|
||||
}
|
||||
catch (MarshalException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void doAdd(ColumnFamily cf, ColumnNameBuilder builder, CollectionType validator, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
for (int i = 0; i < values.size(); ++i)
|
||||
{
|
||||
ColumnNameBuilder b = i == values.size() - 1 ? builder : builder.copy();
|
||||
ByteBuffer name = b.add(values.get(i).getByteBuffer(validator.nameComparator(), params.variables)).build();
|
||||
cf.addColumn(params.makeColumn(name, ByteBufferUtil.EMPTY_BYTE_BUFFER));
|
||||
}
|
||||
}
|
||||
|
||||
private void doDiscard(ColumnFamily cf, ColumnNameBuilder builder, CollectionType validator, UpdateParameters params) throws InvalidRequestException
|
||||
{
|
||||
for (int i = 0; i < values.size(); ++i)
|
||||
{
|
||||
ColumnNameBuilder b = i == values.size() - 1 ? builder : builder.copy();
|
||||
ByteBuffer name = b.add(values.get(i).getByteBuffer(validator.nameComparator(), params.variables)).build();
|
||||
cf.addColumn(params.makeTombstone(name));
|
||||
}
|
||||
}
|
||||
|
||||
public Operation validateAndAddBoundNames(ColumnSpecification column, ColumnSpecification[] boundNames) throws InvalidRequestException
|
||||
{
|
||||
// On the parser side, we're unable to differentiate an empty map from an empty set for add and set operations.
|
||||
// Fix it now that we have the actual type.
|
||||
if (column.type instanceof MapType && values.isEmpty())
|
||||
return toEmptyMapOperation().validateAndAddBoundNames(column, boundNames);
|
||||
|
||||
if (!(column.type instanceof SetType))
|
||||
throw new InvalidRequestException(String.format("Cannot apply set operation on column %s of type %s", column, column.type));
|
||||
|
||||
AbstractType<?> valuesType = ((SetType)column.type).elements;
|
||||
|
||||
for (Term t : values)
|
||||
{
|
||||
t.validateType(column + " element", valuesType);
|
||||
|
||||
if (t.isBindMarker())
|
||||
boundNames[t.bindIndex] = column;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private Operation toEmptyMapOperation()
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case SET:
|
||||
return MapOperation.Set(Collections.<Term, Term>emptyMap());
|
||||
case ADD:
|
||||
return MapOperation.Put(Collections.<Term, Term>emptyMap());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Term> getValues()
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
public boolean requiresRead(AbstractType<?> validator)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
{
|
||||
return Type.SET;
|
||||
}
|
||||
|
||||
/* Utility methods */
|
||||
|
||||
public static Operation Set(List<Term> values)
|
||||
{
|
||||
return new SetOperation(values, Kind.SET);
|
||||
}
|
||||
|
||||
public static Operation Add(List<Term> values)
|
||||
{
|
||||
return new SetOperation(values, Kind.ADD);
|
||||
}
|
||||
|
||||
public static Operation Discard(List<Term> values)
|
||||
{
|
||||
return new SetOperation(values, Kind.DISCARD);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,10 +22,6 @@ import java.util.*;
|
|||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.cql3.operations.ListOperation;
|
||||
import org.apache.cassandra.cql3.operations.MapOperation;
|
||||
import org.apache.cassandra.cql3.operations.Operation;
|
||||
import org.apache.cassandra.cql3.operations.SetOperation;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.DeletionInfo;
|
||||
|
|
@ -41,20 +37,19 @@ import org.apache.cassandra.utils.Pair;
|
|||
public class DeleteStatement extends ModificationStatement
|
||||
{
|
||||
private CFDefinition cfDef;
|
||||
private final List<Selector> columns;
|
||||
private final List<Operation.RawDeletion> deletions;
|
||||
private final List<Relation> whereClause;
|
||||
|
||||
private final List<Pair<CFDefinition.Name, Term>> toRemove;
|
||||
|
||||
private final List<Operation> toRemove;
|
||||
private final Map<ColumnIdentifier, List<Term>> processedKeys = new HashMap<ColumnIdentifier, List<Term>>();
|
||||
|
||||
public DeleteStatement(CFName name, List<Selector> columns, List<Relation> whereClause, Attributes attrs)
|
||||
public DeleteStatement(CFName name, List<Operation.RawDeletion> deletions, List<Relation> whereClause, Attributes attrs)
|
||||
{
|
||||
super(name, attrs);
|
||||
|
||||
this.columns = columns;
|
||||
this.deletions = deletions;
|
||||
this.whereClause = whereClause;
|
||||
this.toRemove = new ArrayList<Pair<CFDefinition.Name, Term>>(columns.size());
|
||||
this.toRemove = new ArrayList<Operation>(deletions.size());
|
||||
}
|
||||
|
||||
protected void validateConsistency(ConsistencyLevel cl) throws InvalidRequestException
|
||||
|
|
@ -79,53 +74,49 @@ public class DeleteStatement extends ModificationStatement
|
|||
boolean isRange = cfDef.isCompact ? !fullKey : (!fullKey || toRemove.isEmpty());
|
||||
|
||||
if (!toRemove.isEmpty() && isRange)
|
||||
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s since %s specified", firstEmpty, toRemove.iterator().next().left));
|
||||
throw new InvalidRequestException(String.format("Missing mandatory PRIMARY KEY part %s since %s specified", firstEmpty, toRemove.iterator().next().columnName));
|
||||
|
||||
// Lists DISCARD operation incurs a read. Do that now.
|
||||
Set<ByteBuffer> toRead = null;
|
||||
for (Pair<CFDefinition.Name, Term> p : toRemove)
|
||||
for (Operation op : toRemove)
|
||||
{
|
||||
CFDefinition.Name name = p.left;
|
||||
Term value = p.right;
|
||||
|
||||
if ((name.type instanceof ListType) && value != null)
|
||||
if (op.requiresRead())
|
||||
{
|
||||
if (toRead == null)
|
||||
toRead = new TreeSet<ByteBuffer>(UTF8Type.instance);
|
||||
toRead.add(name.name.key);
|
||||
toRead.add(op.columnName.key);
|
||||
}
|
||||
}
|
||||
|
||||
Map<ByteBuffer, ColumnGroupMap> rows = toRead != null ? readRows(keys, builder, toRead, (CompositeType)cfDef.cfm.comparator, local, cl) : null;
|
||||
|
||||
Collection<RowMutation> rowMutations = new ArrayList<RowMutation>(keys.size());
|
||||
UpdateParameters params = new UpdateParameters(variables, getTimestamp(now), -1);
|
||||
UpdateParameters params = new UpdateParameters(variables, getTimestamp(now), -1, rows);
|
||||
|
||||
for (ByteBuffer key : keys)
|
||||
rowMutations.add(mutationForKey(cfDef, key, builder, isRange, params, rows == null ? null : rows.get(key)));
|
||||
rowMutations.add(mutationForKey(cfDef, key, builder, isRange, params));
|
||||
|
||||
return rowMutations;
|
||||
}
|
||||
|
||||
public RowMutation mutationForKey(CFDefinition cfDef, ByteBuffer key, ColumnNameBuilder builder, boolean isRange, UpdateParameters params, ColumnGroupMap group)
|
||||
public RowMutation mutationForKey(CFDefinition cfDef, ByteBuffer key, ColumnNameBuilder builder, boolean isRange, UpdateParameters params)
|
||||
throws InvalidRequestException
|
||||
{
|
||||
QueryProcessor.validateKey(key);
|
||||
RowMutation rm = new RowMutation(cfDef.cfm.ksName, key);
|
||||
ColumnFamily cf = rm.addOrGet(columnFamily());
|
||||
|
||||
if (columns.isEmpty() && builder.componentCount() == 0)
|
||||
if (toRemove.isEmpty() && builder.componentCount() == 0)
|
||||
{
|
||||
// No columns, delete the row
|
||||
// No columns specified, delete the row
|
||||
cf.delete(new DeletionInfo(params.timestamp, params.localDeletionTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isRange)
|
||||
{
|
||||
ByteBuffer start = builder.copy().build();
|
||||
assert toRemove.isEmpty();
|
||||
ByteBuffer start = builder.build();
|
||||
ByteBuffer end = builder.buildAsEndOfRange();
|
||||
QueryProcessor.validateColumnName(start); // If start is good, end is too
|
||||
cf.addAtom(params.makeRangeTombstone(start, end));
|
||||
}
|
||||
else
|
||||
|
|
@ -134,63 +125,12 @@ public class DeleteStatement extends ModificationStatement
|
|||
if (cfDef.isCompact)
|
||||
{
|
||||
ByteBuffer columnName = builder.build();
|
||||
QueryProcessor.validateColumnName(columnName);
|
||||
cf.addColumn(params.makeTombstone(columnName));
|
||||
}
|
||||
else
|
||||
{
|
||||
Iterator<Pair<CFDefinition.Name, Term>> iter = toRemove.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
Pair<CFDefinition.Name, Term> p = iter.next();
|
||||
CFDefinition.Name column = p.left;
|
||||
|
||||
if (column.type.isCollection())
|
||||
{
|
||||
CollectionType validator = (CollectionType) column.type;
|
||||
Term keySelected = p.right;
|
||||
|
||||
if (keySelected == null)
|
||||
{
|
||||
// Delete the whole collection
|
||||
ByteBuffer start = builder.copy().add(column.name.key).build();
|
||||
QueryProcessor.validateColumnName(start);
|
||||
ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
|
||||
ByteBuffer end = b.add(column.name.key).buildAsEndOfRange();
|
||||
cf.addAtom(params.makeRangeTombstone(start, end));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.add(column.name.key);
|
||||
List<Term> args = Collections.singletonList(keySelected);
|
||||
|
||||
Operation op;
|
||||
switch (validator.kind)
|
||||
{
|
||||
case LIST:
|
||||
op = ListOperation.DiscardKey(args);
|
||||
break;
|
||||
case SET:
|
||||
op = SetOperation.Discard(args);
|
||||
break;
|
||||
case MAP:
|
||||
op = MapOperation.DiscardKey(keySelected);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("Unknown collection type: " + validator.kind);
|
||||
}
|
||||
|
||||
op.execute(cf, builder, validator, params, group == null ? null : group.getCollection(column.name.key));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
|
||||
ByteBuffer columnName = b.add(column.name.key).build();
|
||||
QueryProcessor.validateColumnName(columnName);
|
||||
cf.addColumn(params.makeTombstone(columnName));
|
||||
}
|
||||
}
|
||||
for (Operation op : toRemove)
|
||||
op.execute(key, cf, builder.copy(), params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -206,40 +146,20 @@ public class DeleteStatement extends ModificationStatement
|
|||
cfDef = metadata.getCfDef();
|
||||
UpdateStatement.processKeys(cfDef, whereClause, processedKeys, boundNames);
|
||||
|
||||
for (Selector column : columns)
|
||||
for (Operation.RawDeletion deletion : deletions)
|
||||
{
|
||||
CFDefinition.Name name = cfDef.get(column.id());
|
||||
CFDefinition.Name name = cfDef.get(deletion.affectedColumn());
|
||||
if (name == null)
|
||||
throw new InvalidRequestException(String.format("Unknown identifier %s", column));
|
||||
throw new InvalidRequestException(String.format("Unknown identifier %s", name));
|
||||
|
||||
// For compact, we only have one value except the key, so the only form of DELETE that make sense is without a column
|
||||
// list. However, we support having the value name for coherence with the static/sparse case
|
||||
if (name.kind != CFDefinition.Name.Kind.COLUMN_METADATA && name.kind != CFDefinition.Name.Kind.VALUE_ALIAS)
|
||||
throw new InvalidRequestException(String.format("Invalid identifier %s for deletion (should not be a PRIMARY KEY part)", column));
|
||||
throw new InvalidRequestException(String.format("Invalid identifier %s for deletion (should not be a PRIMARY KEY part)", name));
|
||||
|
||||
if (column.hasKey())
|
||||
{
|
||||
if (name.type instanceof ListType)
|
||||
{
|
||||
column.key().validateType("list index", Int32Type.instance);
|
||||
|
||||
if (column.key().isBindMarker())
|
||||
boundNames[column.key().bindIndex] = ListOperation.indexSpecOf(name);
|
||||
}
|
||||
else if (name.type instanceof MapType)
|
||||
{
|
||||
column.key().validateType("map key", ((MapType)name.type).keys);
|
||||
|
||||
if (column.key().isBindMarker())
|
||||
boundNames[column.key().bindIndex] = MapOperation.keySpecOf(name, (MapType)name.type);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidRequestException(String.format("Invalid selection %s since %s is neither a list or a map", column, column.id()));
|
||||
}
|
||||
}
|
||||
|
||||
toRemove.add(Pair.create(name, column.key()));
|
||||
Operation op = deletion.prepare(name);
|
||||
op.collectMarkerSpecification(boundNames);
|
||||
toRemove.add(op);
|
||||
}
|
||||
|
||||
return new ParsedStatement.Prepared(this, Arrays.<ColumnSpecification>asList(boundNames));
|
||||
|
|
@ -255,7 +175,7 @@ public class DeleteStatement extends ModificationStatement
|
|||
{
|
||||
return String.format("DeleteStatement(name=%s, columns=%s, keys=%s)",
|
||||
cfName,
|
||||
columns,
|
||||
deletions,
|
||||
whereClause);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
|
||||
public interface RawSelector
|
||||
{
|
||||
public static class WritetimeOrTTL implements RawSelector
|
||||
{
|
||||
public final ColumnIdentifier id;
|
||||
public final boolean isWritetime;
|
||||
|
||||
public WritetimeOrTTL(ColumnIdentifier id, boolean isWritetime)
|
||||
{
|
||||
this.id = id;
|
||||
this.isWritetime = isWritetime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return (isWritetime ? "writetime" : "ttl") + "(" + id + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithFunction implements RawSelector
|
||||
{
|
||||
public final String functionName;
|
||||
public final List<RawSelector> args;
|
||||
|
||||
public WithFunction(String functionName, List<RawSelector> args)
|
||||
{
|
||||
this.functionName = functionName;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(functionName).append("(");
|
||||
for (int i = 0; i < args.size(); i++)
|
||||
{
|
||||
if (i > 0) sb.append(", ");
|
||||
sb.append(args.get(i));
|
||||
}
|
||||
return sb.append(")").toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ public class SelectStatement implements CQLStatement
|
|||
private final int boundTerms;
|
||||
public final CFDefinition cfDef;
|
||||
public final Parameters parameters;
|
||||
private final List<Pair<CFDefinition.Name, Selector>> selectedNames = new ArrayList<Pair<CFDefinition.Name, Selector>>(); // empty => wildcard
|
||||
private final Selection selection;
|
||||
|
||||
private final Restriction[] keyRestrictions;
|
||||
private final Restriction[] columnRestrictions;
|
||||
|
|
@ -95,10 +95,11 @@ public class SelectStatement implements CQLStatement
|
|||
}
|
||||
};
|
||||
|
||||
public SelectStatement(CFDefinition cfDef, int boundTerms, Parameters parameters)
|
||||
public SelectStatement(CFDefinition cfDef, int boundTerms, Parameters parameters, Selection selection)
|
||||
{
|
||||
this.cfDef = cfDef;
|
||||
this.boundTerms = boundTerms;
|
||||
this.selection = selection;
|
||||
this.keyRestrictions = new Restriction[cfDef.keys.size()];
|
||||
this.columnRestrictions = new Restriction[cfDef.columns.size()];
|
||||
this.parameters = parameters;
|
||||
|
|
@ -342,13 +343,13 @@ public class SelectStatement implements CQLStatement
|
|||
if (builder.remainingCount() == 1)
|
||||
{
|
||||
for (Term t : r.eqValues)
|
||||
keys.add(builder.copy().add(t, Relation.Type.EQ, variables).build());
|
||||
keys.add(builder.copy().add(t.bindAndGet(variables)).build());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r.eqValues.size() > 1)
|
||||
throw new InvalidRequestException("IN is only supported on the last column of the partition key");
|
||||
builder.add(r.eqValues.get(0), Relation.Type.EQ, variables);
|
||||
builder.add(r.eqValues.get(0).bindAndGet(variables));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
|
|
@ -371,29 +372,8 @@ public class SelectStatement implements CQLStatement
|
|||
if (t == null)
|
||||
return p.getMinimumToken();
|
||||
|
||||
if (!t.isToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
String text = t.getText();
|
||||
p.getTokenFactory().validate(text);
|
||||
return p.getTokenFactory().fromString(text);
|
||||
}
|
||||
catch (ConfigurationException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
assert t.isToken;
|
||||
ColumnNameBuilder builder = cfDef.getKeyNameBuilder();
|
||||
// We know all keyRestriction must be set
|
||||
for (CFDefinition.Name name : cfDef.keys.values())
|
||||
{
|
||||
Restriction r = keyRestrictions[name.position];
|
||||
builder.add(r.isEquality() ? r.eqValues.get(0) : r.bound(b), Relation.Type.EQ, variables);
|
||||
}
|
||||
return p.getToken(builder.build());
|
||||
ByteBuffer value = t.bindAndGet(variables);
|
||||
return p.getTokenFactory().fromByteArray(value);
|
||||
}
|
||||
|
||||
private boolean includeKeyBound(Bound b)
|
||||
|
|
@ -429,11 +409,6 @@ public class SelectStatement implements CQLStatement
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean isWildcard()
|
||||
{
|
||||
return selectedNames.isEmpty();
|
||||
}
|
||||
|
||||
private SortedSet<ByteBuffer> getRequestedColumns(List<ByteBuffer> variables) throws InvalidRequestException
|
||||
{
|
||||
assert !isColumnRange();
|
||||
|
|
@ -452,14 +427,14 @@ public class SelectStatement implements CQLStatement
|
|||
{
|
||||
Term v = iter.next();
|
||||
ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
|
||||
ByteBuffer cname = b.add(v, Relation.Type.EQ, variables).build();
|
||||
ByteBuffer cname = b.add(v.bindAndGet(variables)).build();
|
||||
columns.add(cname);
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.add(r.eqValues.get(0), Relation.Type.EQ, variables);
|
||||
builder.add(r.eqValues.get(0).bindAndGet(variables));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -485,8 +460,8 @@ public class SelectStatement implements CQLStatement
|
|||
columns.add(builder.copy().add(ByteBufferUtil.EMPTY_BYTE_BUFFER).build());
|
||||
|
||||
// selected columns
|
||||
for (Pair<CFDefinition.Name, Selector> p : getExpandedSelection())
|
||||
columns.add(builder.copy().add(p.right.id().key).build());
|
||||
for (ColumnIdentifier id : selection.regularColumnsToFetch())
|
||||
columns.add(builder.copy().add(id.key).build());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -536,13 +511,13 @@ public class SelectStatement implements CQLStatement
|
|||
if (r.isEquality())
|
||||
{
|
||||
assert r.eqValues.size() == 1;
|
||||
builder.add(r.eqValues.get(0), Relation.Type.EQ, variables);
|
||||
builder.add(r.eqValues.get(0).bindAndGet(variables));
|
||||
}
|
||||
else
|
||||
{
|
||||
Term t = r.bound(b);
|
||||
assert t != null;
|
||||
return builder.add(t, r.getRelation(eocBound, b), variables).build();
|
||||
return builder.add(t.bindAndGet(variables), r.getRelation(eocBound, b)).build();
|
||||
}
|
||||
}
|
||||
// Means no relation at all or everything was an equal
|
||||
|
|
@ -569,7 +544,7 @@ public class SelectStatement implements CQLStatement
|
|||
{
|
||||
for (Term t : restriction.eqValues)
|
||||
{
|
||||
ByteBuffer value = t.getByteBuffer(name.type, variables);
|
||||
ByteBuffer value = t.bindAndGet(variables);
|
||||
if (value.remaining() > 0xFFFF)
|
||||
throw new InvalidRequestException("Index expression values may not be larger than 64K");
|
||||
expressions.add(new IndexExpression(name.name.key, IndexOperator.EQ, value));
|
||||
|
|
@ -581,7 +556,7 @@ public class SelectStatement implements CQLStatement
|
|||
{
|
||||
if (restriction.bound(b) != null)
|
||||
{
|
||||
ByteBuffer value = restriction.bound(b).getByteBuffer(name.type, variables);
|
||||
ByteBuffer value = restriction.bound(b).bindAndGet(variables);
|
||||
if (value.remaining() > 0xFFFF)
|
||||
throw new InvalidRequestException("Index expression values may not be larger than 64K");
|
||||
expressions.add(new IndexExpression(name.name.key, restriction.getIndexOperator(b), value));
|
||||
|
|
@ -592,91 +567,6 @@ public class SelectStatement implements CQLStatement
|
|||
return expressions;
|
||||
}
|
||||
|
||||
private List<Pair<CFDefinition.Name, Selector>> getExpandedSelection()
|
||||
{
|
||||
if (selectedNames.isEmpty())
|
||||
{
|
||||
List<Pair<CFDefinition.Name, Selector>> selection = new ArrayList<Pair<CFDefinition.Name, Selector>>();
|
||||
for (CFDefinition.Name name : cfDef)
|
||||
selection.add(Pair.<CFDefinition.Name, Selector>create(name, name.name));
|
||||
return selection;
|
||||
}
|
||||
else
|
||||
{
|
||||
return selectedNames;
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBuffer value(IColumn c)
|
||||
{
|
||||
return (c instanceof CounterColumn)
|
||||
? ByteBufferUtil.bytes(CounterContext.instance().total(c.value()))
|
||||
: c.value();
|
||||
}
|
||||
|
||||
private void addReturnValue(ResultSet cqlRows, Selector s, IColumn c)
|
||||
{
|
||||
if (c == null || c.isMarkedForDelete())
|
||||
{
|
||||
cqlRows.addColumnValue(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.hasFunction())
|
||||
{
|
||||
switch (s.function())
|
||||
{
|
||||
case WRITE_TIME:
|
||||
cqlRows.addColumnValue(ByteBufferUtil.bytes(c.timestamp()));
|
||||
return;
|
||||
case TTL:
|
||||
if (c instanceof ExpiringColumn)
|
||||
{
|
||||
int ttl = ((ExpiringColumn)c).getLocalDeletionTime() - (int) (System.currentTimeMillis() / 1000);
|
||||
cqlRows.addColumnValue(ByteBufferUtil.bytes(ttl));
|
||||
}
|
||||
else
|
||||
{
|
||||
cqlRows.addColumnValue(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addReturnValue(cqlRows, s, value(c));
|
||||
}
|
||||
|
||||
private void addReturnValue(ResultSet cqlRows, Selector s, ByteBuffer value)
|
||||
{
|
||||
if (value != null && s.hasFunction())
|
||||
{
|
||||
switch (s.function())
|
||||
{
|
||||
case DATE_OF:
|
||||
value = DateType.instance.decompose(new Date(UUIDGen.unixTimestamp(UUIDGen.getUUID(value))));
|
||||
break;
|
||||
case UNIXTIMESTAMP_OF:
|
||||
value = ByteBufferUtil.bytes(UUIDGen.unixTimestamp(UUIDGen.getUUID(value)));
|
||||
break;
|
||||
case WRITE_TIME:
|
||||
case TTL:
|
||||
throw new AssertionError("Cannot return the timestamp or ttl of a value");
|
||||
}
|
||||
}
|
||||
cqlRows.addColumnValue(value);
|
||||
}
|
||||
|
||||
private ResultSet createResult(List<Pair<CFDefinition.Name, Selector>> selection)
|
||||
{
|
||||
List<ColumnSpecification> names = new ArrayList<ColumnSpecification>(selection.size());
|
||||
for (Pair<CFDefinition.Name, Selector> p : selection)
|
||||
{
|
||||
names.add(p.right.hasFunction()
|
||||
? new ColumnSpecification(p.left.ksName, p.left.cfName, new ColumnIdentifier(p.right.toString(), true), p.right.function().resultType)
|
||||
: p.left);
|
||||
}
|
||||
return new ResultSet(names);
|
||||
}
|
||||
|
||||
private Iterable<IColumn> columnsInOrder(final ColumnFamily cf, final List<ByteBuffer> variables) throws InvalidRequestException
|
||||
{
|
||||
|
|
@ -688,7 +578,7 @@ public class SelectStatement implements CQLStatement
|
|||
|
||||
ColumnNameBuilder builder = cfDef.getColumnNameBuilder();
|
||||
for (int i = 0; i < columnRestrictions.length - 1; i++)
|
||||
builder.add(columnRestrictions[i].eqValues.get(0), Relation.Type.EQ, variables);
|
||||
builder.add(columnRestrictions[i].eqValues.get(0).bindAndGet(variables));
|
||||
|
||||
final List<ByteBuffer> requested = new ArrayList<ByteBuffer>(last.eqValues.size());
|
||||
Iterator<Term> iter = last.eqValues.iterator();
|
||||
|
|
@ -696,7 +586,7 @@ public class SelectStatement implements CQLStatement
|
|||
{
|
||||
Term t = iter.next();
|
||||
ColumnNameBuilder b = iter.hasNext() ? builder.copy() : builder;
|
||||
requested.add(b.add(t, Relation.Type.EQ, variables).build());
|
||||
requested.add(b.add(t.bindAndGet(variables)).build());
|
||||
}
|
||||
|
||||
return new Iterable<IColumn>()
|
||||
|
|
@ -720,9 +610,7 @@ public class SelectStatement implements CQLStatement
|
|||
|
||||
private ResultSet process(List<Row> rows, List<ByteBuffer> variables) throws InvalidRequestException
|
||||
{
|
||||
List<Pair<CFDefinition.Name, Selector>> selection = getExpandedSelection();
|
||||
ResultSet cqlRows = createResult(selection);
|
||||
|
||||
Selection.ResultSetBuilder result = selection.resultSetBuilder();
|
||||
for (org.apache.cassandra.db.Row row : rows)
|
||||
{
|
||||
// Not columns match the query, skip
|
||||
|
|
@ -755,30 +643,29 @@ public class SelectStatement implements CQLStatement
|
|||
else if (sliceRestriction != null)
|
||||
{
|
||||
// For dynamic CF, the column could be out of the requested bounds, filter here
|
||||
if (!sliceRestriction.isInclusive(Bound.START) && c.name().equals(sliceRestriction.bound(Bound.START).getByteBuffer(cfDef.cfm.comparator, variables)))
|
||||
if (!sliceRestriction.isInclusive(Bound.START) && c.name().equals(sliceRestriction.bound(Bound.START).bindAndGet(variables)))
|
||||
continue;
|
||||
if (!sliceRestriction.isInclusive(Bound.END) && c.name().equals(sliceRestriction.bound(Bound.END).getByteBuffer(cfDef.cfm.comparator, variables)))
|
||||
if (!sliceRestriction.isInclusive(Bound.END) && c.name().equals(sliceRestriction.bound(Bound.END).bindAndGet(variables)))
|
||||
continue;
|
||||
}
|
||||
|
||||
result.newRow();
|
||||
// Respect selection order
|
||||
for (Pair<CFDefinition.Name, Selector> p : selection)
|
||||
for (CFDefinition.Name name : selection.getColumnsList())
|
||||
{
|
||||
CFDefinition.Name name = p.left;
|
||||
Selector selector = p.right;
|
||||
switch (name.kind)
|
||||
{
|
||||
case KEY_ALIAS:
|
||||
addReturnValue(cqlRows, selector, keyComponents[name.position]);
|
||||
result.add(keyComponents[name.position]);
|
||||
break;
|
||||
case COLUMN_ALIAS:
|
||||
ByteBuffer val = cfDef.isComposite
|
||||
? (name.position < components.length ? components[name.position] : null)
|
||||
: c.name();
|
||||
addReturnValue(cqlRows, selector, val);
|
||||
result.add(val);
|
||||
break;
|
||||
case VALUE_ALIAS:
|
||||
addReturnValue(cqlRows, selector, c);
|
||||
result.add(c);
|
||||
break;
|
||||
case COLUMN_METADATA:
|
||||
// This should not happen for compact CF
|
||||
|
|
@ -805,7 +692,7 @@ public class SelectStatement implements CQLStatement
|
|||
}
|
||||
|
||||
for (ColumnGroupMap group : builder.groups())
|
||||
handleGroup(selection, row.key.key, keyComponents, group, cqlRows);
|
||||
handleGroup(selection, result, row.key.key, keyComponents, group);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -813,24 +700,20 @@ public class SelectStatement implements CQLStatement
|
|||
continue;
|
||||
|
||||
// Static case: One cqlRow for all columns
|
||||
// Respect selection order
|
||||
for (Pair<CFDefinition.Name, Selector> p : selection)
|
||||
result.newRow();
|
||||
for (CFDefinition.Name name : selection.getColumnsList())
|
||||
{
|
||||
CFDefinition.Name name = p.left;
|
||||
Selector selector = p.right;
|
||||
if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS)
|
||||
{
|
||||
addReturnValue(cqlRows, selector, keyComponents[name.position]);
|
||||
continue;
|
||||
}
|
||||
|
||||
IColumn c = row.cf.getColumn(name.name.key);
|
||||
addReturnValue(cqlRows, selector, c);
|
||||
result.add(keyComponents[name.position]);
|
||||
else
|
||||
result.add(row.cf.getColumn(name.name.key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orderResults(selection, cqlRows);
|
||||
ResultSet cqlRows = result.build();
|
||||
|
||||
orderResults(cqlRows);
|
||||
|
||||
// Internal calls always return columns in the comparator order, even when reverse was set
|
||||
if (isReversed)
|
||||
|
|
@ -844,7 +727,7 @@ public class SelectStatement implements CQLStatement
|
|||
/**
|
||||
* Orders results when multiple keys are selected (using IN)
|
||||
*/
|
||||
private void orderResults(List<Pair<CFDefinition.Name, Selector>> selection, ResultSet cqlRows)
|
||||
private void orderResults(ResultSet cqlRows)
|
||||
{
|
||||
// There is nothing to do if
|
||||
// a. there are no results,
|
||||
|
|
@ -859,7 +742,7 @@ public class SelectStatement implements CQLStatement
|
|||
if (parameters.orderings.size() == 1)
|
||||
{
|
||||
CFDefinition.Name ordering = cfDef.get(parameters.orderings.keySet().iterator().next());
|
||||
Collections.sort(cqlRows.rows, new SingleColumnComparator(getColumnPositionInSelect(selection, ordering), ordering.type));
|
||||
Collections.sort(cqlRows.rows, new SingleColumnComparator(getColumnPositionInResultSet(cqlRows, ordering), ordering.type));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -874,18 +757,18 @@ public class SelectStatement implements CQLStatement
|
|||
{
|
||||
CFDefinition.Name orderingColumn = cfDef.get(identifier);
|
||||
types.add(orderingColumn.type);
|
||||
positions[idx++] = getColumnPositionInSelect(selection, orderingColumn);
|
||||
positions[idx++] = getColumnPositionInResultSet(cqlRows, orderingColumn);
|
||||
}
|
||||
|
||||
Collections.sort(cqlRows.rows, new CompositeComparator(types, positions));
|
||||
}
|
||||
|
||||
// determine position of column in the select clause
|
||||
private int getColumnPositionInSelect(List<Pair<CFDefinition.Name, Selector>> selection, CFDefinition.Name columnName)
|
||||
private int getColumnPositionInResultSet(ResultSet rs, CFDefinition.Name columnName)
|
||||
{
|
||||
for (int i = 0; i < selection.size(); i++)
|
||||
for (int i = 0; i < rs.metadata.names.size(); i++)
|
||||
{
|
||||
if (selection.get(i).left.equals(columnName))
|
||||
if (rs.metadata.names.get(i).name.equals(columnName.name))
|
||||
return i;
|
||||
}
|
||||
|
||||
|
|
@ -914,20 +797,19 @@ public class SelectStatement implements CQLStatement
|
|||
return true;
|
||||
}
|
||||
|
||||
private void handleGroup(List<Pair<CFDefinition.Name, Selector>> selection, ByteBuffer key, ByteBuffer[] keyComponents, ColumnGroupMap columns, ResultSet cqlRows)
|
||||
private void handleGroup(Selection selection, Selection.ResultSetBuilder result, ByteBuffer key, ByteBuffer[] keyComponents, ColumnGroupMap columns) throws InvalidRequestException
|
||||
{
|
||||
// Respect requested order
|
||||
for (Pair<CFDefinition.Name, Selector> p : selection)
|
||||
result.newRow();
|
||||
for (CFDefinition.Name name : selection.getColumnsList())
|
||||
{
|
||||
CFDefinition.Name name = p.left;
|
||||
Selector selector = p.right;
|
||||
switch (name.kind)
|
||||
{
|
||||
case KEY_ALIAS:
|
||||
addReturnValue(cqlRows, selector, keyComponents[name.position]);
|
||||
result.add(keyComponents[name.position]);
|
||||
break;
|
||||
case COLUMN_ALIAS:
|
||||
addReturnValue(cqlRows, selector, columns.getKeyComponent(name.position));
|
||||
result.add(columns.getKeyComponent(name.position));
|
||||
break;
|
||||
case VALUE_ALIAS:
|
||||
// This should not happen for SPARSE
|
||||
|
|
@ -939,16 +821,13 @@ public class SelectStatement implements CQLStatement
|
|||
ByteBuffer value = collection == null
|
||||
? null
|
||||
: ((CollectionType)name.type).serialize(collection);
|
||||
addReturnValue(cqlRows, selector, value);
|
||||
result.add(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
IColumn c = columns.getSimple(name.name.key);
|
||||
addReturnValue(cqlRows, selector, c);
|
||||
result.add(columns.getSimple(name.name.key));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -971,10 +850,10 @@ public class SelectStatement implements CQLStatement
|
|||
public static class RawStatement extends CFStatement
|
||||
{
|
||||
private final Parameters parameters;
|
||||
private final List<Selector> selectClause;
|
||||
private final List<RawSelector> selectClause;
|
||||
private final List<Relation> whereClause;
|
||||
|
||||
public RawStatement(CFName cfName, Parameters parameters, List<Selector> selectClause, List<Relation> whereClause)
|
||||
public RawStatement(CFName cfName, Parameters parameters, List<RawSelector> selectClause, List<Relation> whereClause)
|
||||
{
|
||||
super(cfName);
|
||||
this.parameters = parameters;
|
||||
|
|
@ -990,48 +869,19 @@ public class SelectStatement implements CQLStatement
|
|||
throw new InvalidRequestException("LIMIT must be strictly positive");
|
||||
|
||||
CFDefinition cfDef = cfm.getCfDef();
|
||||
SelectStatement stmt = new SelectStatement(cfDef, getBoundsTerms(), parameters);
|
||||
CFDefinition.Name[] names = new CFDefinition.Name[getBoundsTerms()];
|
||||
|
||||
ColumnSpecification[] names = new ColumnSpecification[getBoundsTerms()];
|
||||
IPartitioner partitioner = StorageService.getPartitioner();
|
||||
|
||||
// Select clause
|
||||
if (parameters.isCount)
|
||||
{
|
||||
if (!selectClause.isEmpty())
|
||||
throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported.");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Selector t : selectClause)
|
||||
{
|
||||
CFDefinition.Name name = cfDef.get(t.id());
|
||||
if (name == null)
|
||||
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", t.id()));
|
||||
if (t.hasFunction())
|
||||
{
|
||||
if (name.type.isCollection())
|
||||
throw new InvalidRequestException(String.format("Function %s is not supported on collections", t.function()));
|
||||
switch (t.function())
|
||||
{
|
||||
case WRITE_TIME:
|
||||
case TTL:
|
||||
if (name.kind != CFDefinition.Name.Kind.COLUMN_METADATA && name.kind != CFDefinition.Name.Kind.VALUE_ALIAS)
|
||||
throw new InvalidRequestException(String.format("Cannot use function %s on PRIMARY KEY part %s", t.function(), name));
|
||||
break;
|
||||
case DATE_OF:
|
||||
case UNIXTIMESTAMP_OF:
|
||||
if (!(name.type instanceof TimeUUIDType))
|
||||
throw new InvalidRequestException(String.format("Function %s is only allowed on timeuuid columns", t.function()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parameters.isCount && !selectClause.isEmpty())
|
||||
throw new InvalidRequestException("Only COUNT(*) and COUNT(1) operations are currently supported.");
|
||||
|
||||
if (t.hasKey())
|
||||
throw new InvalidRequestException("Selecting a list/map element by index/key is not yet supported");
|
||||
Selection selection = selectClause.isEmpty()
|
||||
? Selection.wildcard(cfDef)
|
||||
: Selection.fromSelectors(cfDef, selectClause);
|
||||
|
||||
stmt.selectedNames.add(Pair.create(name, t));
|
||||
}
|
||||
}
|
||||
SelectStatement stmt = new SelectStatement(cfDef, getBoundsTerms(), parameters, selection);
|
||||
|
||||
/*
|
||||
* WHERE clause. For a given entity, rules are:
|
||||
|
|
@ -1047,41 +897,18 @@ public class SelectStatement implements CQLStatement
|
|||
if (name == null)
|
||||
throw new InvalidRequestException(String.format("Undefined name %s in where clause ('%s')", rel.getEntity(), rel));
|
||||
|
||||
if (rel.operator() == Relation.Type.IN)
|
||||
{
|
||||
for (Term value : rel.getInValues())
|
||||
{
|
||||
if (!rel.onToken || value.isToken)
|
||||
value.validateType(name.toString(), name.type);
|
||||
else
|
||||
value.validateType("token of " + name.toString(), StorageService.getPartitioner().getTokenValidator());
|
||||
if (value.isBindMarker())
|
||||
names[value.bindIndex] = name;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Term value = rel.getValue();
|
||||
if (!rel.onToken || value.isToken)
|
||||
value.validateType(name.toString(), name.type);
|
||||
else
|
||||
value.validateType("token of " + name.toString(), StorageService.getPartitioner().getTokenValidator());
|
||||
if (value.isBindMarker())
|
||||
names[value.bindIndex] = name;
|
||||
}
|
||||
|
||||
switch (name.kind)
|
||||
{
|
||||
case KEY_ALIAS:
|
||||
stmt.keyRestrictions[name.position] = updateRestriction(name, stmt.keyRestrictions[name.position], rel);
|
||||
stmt.keyRestrictions[name.position] = updateRestriction(name, stmt.keyRestrictions[name.position], rel, names);
|
||||
break;
|
||||
case COLUMN_ALIAS:
|
||||
stmt.columnRestrictions[name.position] = updateRestriction(name, stmt.columnRestrictions[name.position], rel);
|
||||
stmt.columnRestrictions[name.position] = updateRestriction(name, stmt.columnRestrictions[name.position], rel, names);
|
||||
break;
|
||||
case VALUE_ALIAS:
|
||||
throw new InvalidRequestException(String.format("Restricting the value of a compact CF (%s) is not supported", name.name));
|
||||
case COLUMN_METADATA:
|
||||
stmt.metadataRestrictions.put(name, updateRestriction(name, stmt.metadataRestrictions.get(name), rel));
|
||||
stmt.metadataRestrictions.put(name, updateRestriction(name, stmt.metadataRestrictions.get(name), rel, names));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1135,6 +962,7 @@ public class SelectStatement implements CQLStatement
|
|||
{
|
||||
CFDefinition.Name cname = iter.next();
|
||||
Restriction restriction = stmt.keyRestrictions[i];
|
||||
|
||||
if (restriction == null)
|
||||
{
|
||||
if (stmt.onToken)
|
||||
|
|
@ -1235,16 +1063,16 @@ public class SelectStatement implements CQLStatement
|
|||
// If we order an IN query, we'll have to do a manual sort post-query. Currently, this sorting requires that we
|
||||
// have queried the column on which we sort (TODO: we should update it to add the column on which we sort to the one
|
||||
// queried automatically, and then removing it from the resultSet afterwards if needed)
|
||||
if (stmt.keyIsInRelation && !stmt.selectedNames.isEmpty()) // empty means wildcard was used
|
||||
if (stmt.keyIsInRelation && !selectClause.isEmpty()) // empty means wildcard was used
|
||||
{
|
||||
for (ColumnIdentifier column : stmt.parameters.orderings.keySet())
|
||||
{
|
||||
CFDefinition.Name name = cfDef.get(column);
|
||||
|
||||
boolean hasColumn = false;
|
||||
for (Pair<CFDefinition.Name, Selector> selectPair : stmt.selectedNames)
|
||||
for (RawSelector selector : selectClause)
|
||||
{
|
||||
if (selectPair.left.equals(name))
|
||||
if (name.name.equals(selector))
|
||||
{
|
||||
hasColumn = true;
|
||||
break;
|
||||
|
|
@ -1310,30 +1138,54 @@ public class SelectStatement implements CQLStatement
|
|||
return new ParsedStatement.Prepared(stmt, Arrays.<ColumnSpecification>asList(names));
|
||||
}
|
||||
|
||||
Restriction updateRestriction(CFDefinition.Name name, Restriction restriction, Relation newRel) throws InvalidRequestException
|
||||
Restriction updateRestriction(CFDefinition.Name name, Restriction restriction, Relation newRel, ColumnSpecification[] boundNames) throws InvalidRequestException
|
||||
{
|
||||
if (newRel.onToken && name.kind != CFDefinition.Name.Kind.KEY_ALIAS)
|
||||
throw new InvalidRequestException(String.format("The token() function is only supported on the partition key, found on %s", name));
|
||||
ColumnSpecification receiver = name;
|
||||
if (newRel.onToken)
|
||||
{
|
||||
if (name.kind != CFDefinition.Name.Kind.KEY_ALIAS)
|
||||
throw new InvalidRequestException(String.format("The token() function is only supported on the partition key, found on %s", name));
|
||||
|
||||
receiver = new ColumnSpecification(name.ksName,
|
||||
name.cfName,
|
||||
new ColumnIdentifier("partition key token", true),
|
||||
StorageService.instance.getPartitioner().getTokenValidator());
|
||||
}
|
||||
|
||||
switch (newRel.operator())
|
||||
{
|
||||
case EQ:
|
||||
if (restriction != null)
|
||||
throw new InvalidRequestException(String.format("%s cannot be restricted by more than one relation if it includes an Equal", name));
|
||||
restriction = new Restriction(newRel.getValue(), newRel.onToken);
|
||||
{
|
||||
if (restriction != null)
|
||||
throw new InvalidRequestException(String.format("%s cannot be restricted by more than one relation if it includes an Equal", name));
|
||||
Term t = newRel.getValue().prepare(receiver);
|
||||
t.collectMarkerSpecification(boundNames);
|
||||
restriction = new Restriction(t, newRel.onToken);
|
||||
}
|
||||
break;
|
||||
case IN:
|
||||
if (restriction != null)
|
||||
throw new InvalidRequestException(String.format("%s cannot be restricted by more than one reation if it includes a IN", name));
|
||||
restriction = new Restriction(newRel.getInValues());
|
||||
List<Term> inValues = new ArrayList<Term>(newRel.getInValues().size());
|
||||
for (Term.Raw raw : newRel.getInValues())
|
||||
{
|
||||
Term t = raw.prepare(receiver);
|
||||
t.collectMarkerSpecification(boundNames);
|
||||
inValues.add(t);
|
||||
}
|
||||
restriction = new Restriction(inValues);
|
||||
break;
|
||||
case GT:
|
||||
case GTE:
|
||||
case LT:
|
||||
case LTE:
|
||||
if (restriction == null)
|
||||
restriction = new Restriction(newRel.onToken);
|
||||
restriction.setBound(name.name, newRel.operator(), newRel.getValue());
|
||||
{
|
||||
if (restriction == null)
|
||||
restriction = new Restriction(newRel.onToken);
|
||||
Term t = newRel.getValue().prepare(receiver);
|
||||
t.collectMarkerSpecification(boundNames);
|
||||
restriction.setBound(name.name, newRel.operator(), t);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return restriction;
|
||||
|
|
@ -1364,17 +1216,23 @@ public class SelectStatement implements CQLStatement
|
|||
|
||||
final boolean onToken;
|
||||
|
||||
Restriction(List<Term> values)
|
||||
|
||||
Restriction(List<Term> values, boolean onToken)
|
||||
{
|
||||
this.eqValues = values;
|
||||
this.bounds = null;
|
||||
this.boundInclusive = null;
|
||||
this.onToken = false;
|
||||
this.onToken = onToken;
|
||||
}
|
||||
|
||||
Restriction(List<Term> values)
|
||||
{
|
||||
this(values, false);
|
||||
}
|
||||
|
||||
Restriction(Term value, boolean onToken)
|
||||
{
|
||||
this(Collections.singletonList(value));
|
||||
this(Collections.singletonList(value), onToken);
|
||||
}
|
||||
|
||||
Restriction(boolean onToken)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,401 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.functions.Functions;
|
||||
import org.apache.cassandra.db.CounterColumn;
|
||||
import org.apache.cassandra.db.ExpiringColumn;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.context.CounterContext;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
import org.apache.cassandra.db.marshal.LongType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class Selection
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Selection.class);
|
||||
|
||||
private final List<CFDefinition.Name> columnsList;
|
||||
private final List<ColumnSpecification> metadata;
|
||||
private final boolean collectTimestamps;
|
||||
private final boolean collectTTLs;
|
||||
|
||||
protected Selection(List<CFDefinition.Name> columnsList, List<ColumnSpecification> metadata, boolean collectTimestamps, boolean collectTTLs)
|
||||
{
|
||||
this.columnsList = columnsList;
|
||||
this.metadata = metadata;
|
||||
this.collectTimestamps = collectTimestamps;
|
||||
this.collectTTLs = collectTTLs;
|
||||
}
|
||||
|
||||
public static Selection wildcard(CFDefinition cfDef)
|
||||
{
|
||||
List<CFDefinition.Name> all = new ArrayList<CFDefinition.Name>();
|
||||
for (CFDefinition.Name name : cfDef)
|
||||
all.add(name);
|
||||
return new SimpleSelection(all);
|
||||
}
|
||||
|
||||
private static boolean isUsingFunction(List<RawSelector> rawSelectors)
|
||||
{
|
||||
for (RawSelector rawSelector : rawSelectors)
|
||||
{
|
||||
if (!(rawSelector instanceof ColumnIdentifier))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int addAndGetIndex(CFDefinition.Name name, List<CFDefinition.Name> l)
|
||||
{
|
||||
int idx = l.indexOf(name);
|
||||
if (idx < 0)
|
||||
{
|
||||
idx = l.size();
|
||||
l.add(name);
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
private static Selector makeSelector(CFDefinition cfDef, RawSelector raw, List<CFDefinition.Name> names, List<ColumnSpecification> metadata) throws InvalidRequestException
|
||||
{
|
||||
if (raw instanceof ColumnIdentifier)
|
||||
{
|
||||
CFDefinition.Name name = cfDef.get((ColumnIdentifier)raw);
|
||||
if (name == null)
|
||||
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", raw));
|
||||
if (metadata != null)
|
||||
metadata.add(name);
|
||||
return new SimpleSelector(addAndGetIndex(name, names), name.type);
|
||||
}
|
||||
else if (raw instanceof RawSelector.WritetimeOrTTL)
|
||||
{
|
||||
RawSelector.WritetimeOrTTL tot = (RawSelector.WritetimeOrTTL)raw;
|
||||
CFDefinition.Name name = cfDef.get(tot.id);
|
||||
if (name == null)
|
||||
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", tot.id));
|
||||
if (name.kind != CFDefinition.Name.Kind.COLUMN_METADATA && name.kind != CFDefinition.Name.Kind.VALUE_ALIAS)
|
||||
throw new InvalidRequestException(String.format("Cannot use selection function %s on PRIMARY KEY part %s", tot.isWritetime ? "writeTime" : "ttl", name));
|
||||
if (name.type.isCollection())
|
||||
throw new InvalidRequestException(String.format("Cannot use selection function %s on collections", tot.isWritetime ? "writeTime" : "ttl"));
|
||||
|
||||
if (metadata != null)
|
||||
metadata.add(makeWritetimeOrTTLSpec(cfDef, tot));
|
||||
return new WritetimeOrTTLSelector(addAndGetIndex(name, names), tot.isWritetime);
|
||||
}
|
||||
else
|
||||
{
|
||||
RawSelector.WithFunction withFun = (RawSelector.WithFunction)raw;
|
||||
List<Selector> args = new ArrayList<Selector>(withFun.args.size());
|
||||
for (RawSelector rawArg : withFun.args)
|
||||
args.add(makeSelector(cfDef, rawArg, names, null));
|
||||
|
||||
AbstractType<?> returnType = Functions.getReturnType(withFun.functionName, cfDef.cfm.ksName, cfDef.cfm.cfName);
|
||||
ColumnSpecification spec = makeFunctionSpec(cfDef, withFun, returnType);
|
||||
Function fun = Functions.get(withFun.functionName, args, spec);
|
||||
if (metadata != null)
|
||||
metadata.add(spec);
|
||||
return new FunctionSelector(fun, args);
|
||||
}
|
||||
}
|
||||
|
||||
private static ColumnSpecification makeWritetimeOrTTLSpec(CFDefinition cfDef, RawSelector.WritetimeOrTTL tot)
|
||||
{
|
||||
return new ColumnSpecification(cfDef.cfm.ksName,
|
||||
cfDef.cfm.cfName,
|
||||
new ColumnIdentifier(tot.toString(), true),
|
||||
tot.isWritetime ? LongType.instance : Int32Type.instance);
|
||||
}
|
||||
|
||||
private static ColumnSpecification makeFunctionSpec(CFDefinition cfDef, RawSelector.WithFunction fun, AbstractType<?> returnType) throws InvalidRequestException
|
||||
{
|
||||
if (returnType == null)
|
||||
throw new InvalidRequestException(String.format("Unknown function %s called in selection clause", fun.functionName));
|
||||
|
||||
return new ColumnSpecification(cfDef.cfm.ksName,
|
||||
cfDef.cfm.cfName,
|
||||
new ColumnIdentifier(fun.toString(), true),
|
||||
returnType);
|
||||
}
|
||||
|
||||
public static Selection fromSelectors(CFDefinition cfDef, List<RawSelector> rawSelectors) throws InvalidRequestException
|
||||
{
|
||||
boolean usesFunction = isUsingFunction(rawSelectors);
|
||||
|
||||
if (usesFunction)
|
||||
{
|
||||
List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>();
|
||||
List<ColumnSpecification> metadata = new ArrayList<ColumnSpecification>(rawSelectors.size());
|
||||
List<Selector> selectors = new ArrayList<Selector>(rawSelectors.size());
|
||||
boolean collectTimestamps = false;
|
||||
boolean collectTTLs = false;
|
||||
for (RawSelector rawSelector : rawSelectors)
|
||||
{
|
||||
Selector selector = makeSelector(cfDef, rawSelector, names, metadata);
|
||||
selectors.add(selector);
|
||||
if (selector instanceof WritetimeOrTTLSelector)
|
||||
{
|
||||
collectTimestamps |= ((WritetimeOrTTLSelector)selector).isWritetime;
|
||||
collectTTLs |= !((WritetimeOrTTLSelector)selector).isWritetime;
|
||||
}
|
||||
}
|
||||
return new SelectionWithFunctions(names, metadata, selectors, collectTimestamps, collectTTLs);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<CFDefinition.Name> names = new ArrayList<CFDefinition.Name>(rawSelectors.size());
|
||||
for (RawSelector rawSelector : rawSelectors)
|
||||
{
|
||||
assert rawSelector instanceof ColumnIdentifier;
|
||||
CFDefinition.Name name = cfDef.get((ColumnIdentifier)rawSelector);
|
||||
if (name == null)
|
||||
throw new InvalidRequestException(String.format("Undefined name %s in selection clause", rawSelector));
|
||||
names.add(name);
|
||||
}
|
||||
return new SimpleSelection(names);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract List<ByteBuffer> handleRow(ResultSetBuilder rs) throws InvalidRequestException;
|
||||
|
||||
/**
|
||||
* @return the list of CQL3 "regular" (the "COLUMN_METADATA" ones) column names to fetch.
|
||||
*/
|
||||
public List<ColumnIdentifier> regularColumnsToFetch()
|
||||
{
|
||||
List<ColumnIdentifier> toFetch = new ArrayList<ColumnIdentifier>();
|
||||
for (CFDefinition.Name name : columnsList)
|
||||
{
|
||||
if (name.kind == CFDefinition.Name.Kind.COLUMN_METADATA)
|
||||
toFetch.add(name.name);
|
||||
}
|
||||
return toFetch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of CQL3 columns value this SelectionClause needs.
|
||||
*/
|
||||
public List<CFDefinition.Name> getColumnsList()
|
||||
{
|
||||
return columnsList;
|
||||
}
|
||||
|
||||
public ResultSetBuilder resultSetBuilder()
|
||||
{
|
||||
return new ResultSetBuilder();
|
||||
}
|
||||
|
||||
private static ByteBuffer value(IColumn c)
|
||||
{
|
||||
return (c instanceof CounterColumn)
|
||||
? ByteBufferUtil.bytes(CounterContext.instance().total(c.value()))
|
||||
: c.value();
|
||||
}
|
||||
|
||||
public class ResultSetBuilder
|
||||
{
|
||||
private final ResultSet resultSet;
|
||||
|
||||
/*
|
||||
* We'll build CQL3 row one by one.
|
||||
* The currentRow is the values for the (CQL3) columns we've fetched.
|
||||
* We also collect timestamps and ttls for the case where the writetime and
|
||||
* ttl functions are used. Note that we might collect timestamp and/or ttls
|
||||
* we don't care about, but since the array below are allocated just once,
|
||||
* it doesn't matter performance wise.
|
||||
*/
|
||||
List<ByteBuffer> current;
|
||||
final long[] timestamps;
|
||||
final int[] ttls;
|
||||
|
||||
private ResultSetBuilder()
|
||||
{
|
||||
this.resultSet = new ResultSet(metadata);
|
||||
this.timestamps = collectTimestamps ? new long[columnsList.size()] : null;
|
||||
this.ttls = collectTTLs ? new int[columnsList.size()] : null;
|
||||
}
|
||||
|
||||
public void add(ByteBuffer v)
|
||||
{
|
||||
current.add(v);
|
||||
}
|
||||
|
||||
public void add(IColumn c)
|
||||
{
|
||||
current.add(c == null || c.isMarkedForDelete() ? null : value(c));
|
||||
if (timestamps != null)
|
||||
{
|
||||
timestamps[current.size() - 1] = c.timestamp();
|
||||
}
|
||||
if (ttls != null)
|
||||
{
|
||||
int ttl = -1;
|
||||
if (c instanceof ExpiringColumn)
|
||||
ttl = ((ExpiringColumn)c).getLocalDeletionTime() - (int) (System.currentTimeMillis() / 1000);
|
||||
ttls[current.size() - 1] = ttl;
|
||||
}
|
||||
}
|
||||
|
||||
public void newRow() throws InvalidRequestException
|
||||
{
|
||||
if (current != null)
|
||||
resultSet.addRow(handleRow(this));
|
||||
current = new ArrayList<ByteBuffer>(columnsList.size());
|
||||
}
|
||||
|
||||
public ResultSet build() throws InvalidRequestException
|
||||
{
|
||||
if (current != null)
|
||||
{
|
||||
resultSet.addRow(handleRow(this));
|
||||
current = null;
|
||||
}
|
||||
return resultSet;
|
||||
}
|
||||
}
|
||||
|
||||
// Special cased selection for when no function is used (this save some allocations).
|
||||
private static class SimpleSelection extends Selection
|
||||
{
|
||||
public SimpleSelection(List<CFDefinition.Name> columnsList)
|
||||
{
|
||||
/*
|
||||
* In theory, even a simple selection could have multiple time the same column, so we
|
||||
* could filter those duplicate out of columnsList. But since we're very unlikely to
|
||||
* get much duplicate in practice, it's more efficient not to bother.
|
||||
*/
|
||||
super(columnsList, new ArrayList<ColumnSpecification>(columnsList), false, false);
|
||||
}
|
||||
|
||||
protected List<ByteBuffer> handleRow(ResultSetBuilder rs)
|
||||
{
|
||||
return rs.current;
|
||||
}
|
||||
}
|
||||
|
||||
private interface Selector extends AssignementTestable
|
||||
{
|
||||
public ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException;
|
||||
}
|
||||
|
||||
private static class SimpleSelector implements Selector
|
||||
{
|
||||
private final int idx;
|
||||
private final AbstractType<?> type;
|
||||
|
||||
public SimpleSelector(int idx, AbstractType<?> type)
|
||||
{
|
||||
this.idx = idx;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public ByteBuffer compute(ResultSetBuilder rs)
|
||||
{
|
||||
return rs.current.get(idx);
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
return type.equals(receiver.type);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FunctionSelector implements Selector
|
||||
{
|
||||
private final Function fun;
|
||||
private final List<Selector> argSelectors;
|
||||
|
||||
public FunctionSelector(Function fun, List<Selector> argSelectors)
|
||||
{
|
||||
this.fun = fun;
|
||||
this.argSelectors = argSelectors;
|
||||
}
|
||||
|
||||
public ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException
|
||||
{
|
||||
List<ByteBuffer> args = new ArrayList<ByteBuffer>(argSelectors.size());
|
||||
for (Selector s : argSelectors)
|
||||
args.add(s.compute(rs));
|
||||
|
||||
return fun.execute(args);
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
return fun.returnType().equals(receiver.type);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WritetimeOrTTLSelector implements Selector
|
||||
{
|
||||
private final int idx;
|
||||
private final boolean isWritetime;
|
||||
|
||||
public WritetimeOrTTLSelector(int idx, boolean isWritetime)
|
||||
{
|
||||
this.idx = idx;
|
||||
this.isWritetime = isWritetime;
|
||||
}
|
||||
|
||||
public ByteBuffer compute(ResultSetBuilder rs)
|
||||
{
|
||||
if (isWritetime)
|
||||
return ByteBufferUtil.bytes(rs.timestamps[idx]);
|
||||
|
||||
int ttl = rs.ttls[idx];
|
||||
return ttl > 0 ? ByteBufferUtil.bytes(ttl) : null;
|
||||
}
|
||||
|
||||
public boolean isAssignableTo(ColumnSpecification receiver)
|
||||
{
|
||||
return receiver.type.equals(isWritetime ? LongType.instance : Int32Type.instance);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SelectionWithFunctions extends Selection
|
||||
{
|
||||
private final List<Selector> selectors;
|
||||
|
||||
public SelectionWithFunctions(List<CFDefinition.Name> columnsList, List<ColumnSpecification> metadata, List<Selector> selectors, boolean collectTimestamps, boolean collectTTLs)
|
||||
{
|
||||
super(columnsList, metadata, collectTimestamps, collectTTLs);
|
||||
this.selectors = selectors;
|
||||
}
|
||||
|
||||
protected List<ByteBuffer> handleRow(ResultSetBuilder rs) throws InvalidRequestException
|
||||
{
|
||||
List<ByteBuffer> result = new ArrayList<ByteBuffer>();
|
||||
for (Selector selector : selectors)
|
||||
{
|
||||
result.add(selector.compute(rs));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
|
||||
public abstract class Selector
|
||||
{
|
||||
public enum Function
|
||||
{
|
||||
WRITE_TIME (LongType.instance),
|
||||
TTL (Int32Type.instance),
|
||||
DATE_OF (DateType.instance),
|
||||
UNIXTIMESTAMP_OF(LongType.instance);
|
||||
|
||||
public final AbstractType<?> resultType;
|
||||
|
||||
private Function(AbstractType<?> resultType)
|
||||
{
|
||||
this.resultType = resultType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
switch (this)
|
||||
{
|
||||
case WRITE_TIME:
|
||||
return "writetime";
|
||||
case TTL:
|
||||
return "ttl";
|
||||
case DATE_OF:
|
||||
return "dateof";
|
||||
case UNIXTIMESTAMP_OF:
|
||||
return "unixtimestampof";
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract ColumnIdentifier id();
|
||||
|
||||
public boolean hasFunction()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Function function()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasKey()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Term key()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class WithFunction extends Selector
|
||||
{
|
||||
private final Function function;
|
||||
private final ColumnIdentifier id;
|
||||
|
||||
public WithFunction(ColumnIdentifier id, Function function)
|
||||
{
|
||||
this.id = id;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public ColumnIdentifier id()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFunction()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function function()
|
||||
{
|
||||
return function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode()
|
||||
{
|
||||
return Objects.hashCode(function, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o)
|
||||
{
|
||||
if(!(o instanceof WithFunction))
|
||||
return false;
|
||||
Selector that = (WithFunction)o;
|
||||
return id().equals(that.id()) && function() == that.function();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return function + "(" + id + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static class WithKey extends Selector
|
||||
{
|
||||
private final ColumnIdentifier id;
|
||||
private final Term key;
|
||||
|
||||
public WithKey(ColumnIdentifier id, Term key)
|
||||
{
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public ColumnIdentifier id()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public Term key()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode()
|
||||
{
|
||||
return Objects.hashCode(id, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o)
|
||||
{
|
||||
if(!(o instanceof WithKey))
|
||||
return false;
|
||||
WithKey that = (WithKey)o;
|
||||
return id().equals(that.id()) && key.equals(that.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return id + "[" + key + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,14 +20,8 @@ package org.apache.cassandra.cql3.statements;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.cql3.operations.ColumnOperation;
|
||||
import org.apache.cassandra.cql3.operations.Operation;
|
||||
import org.apache.cassandra.cql3.operations.SetOperation;
|
||||
import org.apache.cassandra.cql3.operations.PreparedOperation;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.exceptions.*;
|
||||
|
|
@ -44,34 +38,39 @@ import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily;
|
|||
public class UpdateStatement extends ModificationStatement
|
||||
{
|
||||
private CFDefinition cfDef;
|
||||
private final List<Pair<ColumnIdentifier, Operation>> columns;
|
||||
private final List<ColumnIdentifier> columnNames;
|
||||
private final List<Operation> columnOperations;
|
||||
|
||||
// Provided for an UPDATE
|
||||
private final List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations;
|
||||
private final List<Relation> whereClause;
|
||||
|
||||
private final ArrayListMultimap<CFDefinition.Name, Operation> processedColumns = ArrayListMultimap.create();
|
||||
// Provided for an INSERT
|
||||
private final List<ColumnIdentifier> columnNames;
|
||||
private final List<Term.Raw> columnValues;
|
||||
|
||||
private final List<Operation> processedColumns = new ArrayList<Operation>();
|
||||
private final Map<ColumnIdentifier, List<Term>> processedKeys = new HashMap<ColumnIdentifier, List<Term>>();
|
||||
|
||||
private static final Operation setToEmptyOperation = new Constants.Setter(null, new Constants.Value(ByteBufferUtil.EMPTY_BYTE_BUFFER));
|
||||
|
||||
/**
|
||||
* Creates a new UpdateStatement from a column family name, columns map, consistency
|
||||
* level, and key term.
|
||||
*
|
||||
* @param name column family being operated on
|
||||
* @param columns a map of column name/values pairs
|
||||
* @param operations a map of column operations to perform
|
||||
* @param whereClause the where clause
|
||||
* @param attrs additional attributes for statement (CL, timestamp, timeToLive)
|
||||
*/
|
||||
public UpdateStatement(CFName name,
|
||||
List<Pair<ColumnIdentifier, Operation>> columns,
|
||||
List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations,
|
||||
List<Relation> whereClause,
|
||||
Attributes attrs)
|
||||
{
|
||||
super(name, attrs);
|
||||
|
||||
this.columns = columns;
|
||||
this.operations = operations;
|
||||
this.whereClause = whereClause;
|
||||
this.columnNames = null;
|
||||
this.columnOperations = null;
|
||||
this.columnValues = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -81,20 +80,19 @@ public class UpdateStatement extends ModificationStatement
|
|||
*
|
||||
* @param name column family being operated on
|
||||
* @param columnNames list of column names
|
||||
* @param columnOperations list of column 'set' operations (corresponds to names)
|
||||
* @param columnValues list of column values (corresponds to names)
|
||||
* @param attrs additional attributes for statement (CL, timestamp, timeToLive)
|
||||
*/
|
||||
public UpdateStatement(CFName name,
|
||||
Attributes attrs,
|
||||
List<ColumnIdentifier> columnNames,
|
||||
List<Operation> columnOperations)
|
||||
List<Term.Raw> columnValues)
|
||||
{
|
||||
super(name, attrs);
|
||||
|
||||
this.columnNames = columnNames;
|
||||
this.columnOperations = columnOperations;
|
||||
this.columnValues = columnValues;
|
||||
this.operations = null;
|
||||
this.whereClause = null;
|
||||
this.columns = null;
|
||||
}
|
||||
|
||||
protected void validateConsistency(ConsistencyLevel cl) throws InvalidRequestException
|
||||
|
|
@ -116,29 +114,23 @@ public class UpdateStatement extends ModificationStatement
|
|||
|
||||
// Lists SET operation incurs a read.
|
||||
Set<ByteBuffer> toRead = null;
|
||||
for (Map.Entry<CFDefinition.Name, Operation> entry : processedColumns.entries())
|
||||
for (Operation op : processedColumns)
|
||||
{
|
||||
CFDefinition.Name name = entry.getKey();
|
||||
Operation value = entry.getValue();
|
||||
|
||||
if (!(name.type instanceof ListType))
|
||||
continue;
|
||||
|
||||
if (value.requiresRead(name.type))
|
||||
if (op.requiresRead())
|
||||
{
|
||||
if (toRead == null)
|
||||
toRead = new TreeSet<ByteBuffer>(UTF8Type.instance);
|
||||
toRead.add(name.name.key);
|
||||
toRead.add(op.columnName.key);
|
||||
}
|
||||
}
|
||||
|
||||
Map<ByteBuffer, ColumnGroupMap> rows = toRead != null ? readRows(keys, builder, toRead, (CompositeType)cfDef.cfm.comparator, local, cl) : null;
|
||||
|
||||
Collection<IMutation> mutations = new LinkedList<IMutation>();
|
||||
UpdateParameters params = new UpdateParameters(variables, getTimestamp(now), getTimeToLive());
|
||||
UpdateParameters params = new UpdateParameters(variables, getTimestamp(now), getTimeToLive(), rows);
|
||||
|
||||
for (ByteBuffer key: keys)
|
||||
mutations.add(mutationForKey(cfDef, key, builder, params, rows == null ? null : rows.get(key), cl));
|
||||
mutations.add(mutationForKey(cfDef, key, builder, params, cl));
|
||||
|
||||
return mutations;
|
||||
}
|
||||
|
|
@ -164,7 +156,7 @@ public class UpdateStatement extends ModificationStatement
|
|||
else
|
||||
{
|
||||
assert values.size() == 1; // We only allow IN for row keys so far
|
||||
builder.add(values.get(0), Relation.Type.EQ, variables);
|
||||
builder.add(values.get(0).bindAndGet(variables));
|
||||
}
|
||||
}
|
||||
return firstEmpty;
|
||||
|
|
@ -184,13 +176,15 @@ public class UpdateStatement extends ModificationStatement
|
|||
if (keyBuilder.remainingCount() == 1)
|
||||
{
|
||||
for (Term t : values)
|
||||
keys.add(keyBuilder.copy().add(t, Relation.Type.EQ, variables).build());
|
||||
{
|
||||
keys.add(keyBuilder.copy().add(t.bindAndGet(variables)).build());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (values.size() > 1)
|
||||
throw new InvalidRequestException("IN is only supported on the last column of the partition key");
|
||||
keyBuilder.add(values.get(0), Relation.Type.EQ, variables);
|
||||
keyBuilder.add(values.get(0).bindAndGet(variables));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
|
|
@ -203,7 +197,7 @@ public class UpdateStatement extends ModificationStatement
|
|||
*
|
||||
* @throws InvalidRequestException on the wrong request
|
||||
*/
|
||||
private IMutation mutationForKey(CFDefinition cfDef, ByteBuffer key, ColumnNameBuilder builder, UpdateParameters params, ColumnGroupMap group, ConsistencyLevel cl)
|
||||
private IMutation mutationForKey(CFDefinition cfDef, ByteBuffer key, ColumnNameBuilder builder, UpdateParameters params, ConsistencyLevel cl)
|
||||
throws InvalidRequestException
|
||||
{
|
||||
validateKey(key);
|
||||
|
|
@ -232,30 +226,28 @@ public class UpdateStatement extends ModificationStatement
|
|||
if (builder.componentCount() == 0)
|
||||
throw new InvalidRequestException(String.format("Missing PRIMARY KEY part %s", cfDef.columns.values().iterator().next()));
|
||||
|
||||
Operation operation;
|
||||
if (cfDef.value == null)
|
||||
{
|
||||
// No value was defined, we set to the empty value
|
||||
operation = ColumnOperation.SetToEmpty();
|
||||
// compact + no compact value implies there is no column outside the PK. So no operation could
|
||||
// have passed through validation
|
||||
assert processedColumns.isEmpty();
|
||||
setToEmptyOperation.execute(key, cf, builder.copy(), params);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Operation> operations = processedColumns.get(cfDef.value);
|
||||
if (operations.isEmpty())
|
||||
// compact means we don't have a row marker, so don't accept to set only the PK (Note: we
|
||||
// could accept it and use an empty value!?)
|
||||
if (processedColumns.isEmpty())
|
||||
throw new InvalidRequestException(String.format("Missing mandatory column %s", cfDef.value));
|
||||
assert operations.size() == 1;
|
||||
operation = operations.get(0);
|
||||
|
||||
for (Operation op : processedColumns)
|
||||
op.execute(key, cf, builder.copy(), params);
|
||||
}
|
||||
operation.execute(cf, builder.copy(), cfDef.value == null ? null : cfDef.value.type, params, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Map.Entry<CFDefinition.Name, Operation> entry : processedColumns.entries())
|
||||
{
|
||||
CFDefinition.Name name = entry.getKey();
|
||||
Operation op = entry.getValue();
|
||||
op.execute(cf, builder.copy().add(name.name.key), name.type, params, group == null || !op.requiresRead(name.type) ? null : group.getCollection(name.name.key));
|
||||
}
|
||||
for (Operation op : processedColumns)
|
||||
op.execute(key, cf, builder.copy(), params);
|
||||
}
|
||||
|
||||
return type == Type.COUNTER ? new CounterMutation(rm, cl) : rm;
|
||||
|
|
@ -269,15 +261,15 @@ public class UpdateStatement extends ModificationStatement
|
|||
|
||||
type = metadata.getDefaultValidator().isCommutative() ? Type.COUNTER : Type.LOGGED;
|
||||
|
||||
if (columns == null)
|
||||
if (operations == null)
|
||||
{
|
||||
// Created from an INSERT
|
||||
if (type == Type.COUNTER)
|
||||
throw new InvalidRequestException("INSERT statement are not allowed on counter tables, use UPDATE instead");
|
||||
if (columnNames.size() != columnOperations.size())
|
||||
throw new InvalidRequestException("unmatched column names/values");
|
||||
if (columnNames.size() < 1)
|
||||
throw new InvalidRequestException("no columns specified for INSERT");
|
||||
if (columnNames.size() != columnValues.size())
|
||||
throw new InvalidRequestException("Unmatched column names/values");
|
||||
if (columnNames.isEmpty())
|
||||
throw new InvalidRequestException("No columns provided to INSERT");
|
||||
|
||||
for (int i = 0; i < columnNames.size(); i++)
|
||||
{
|
||||
|
|
@ -285,24 +277,27 @@ public class UpdateStatement extends ModificationStatement
|
|||
if (name == null)
|
||||
throw new InvalidRequestException(String.format("Unknown identifier %s", columnNames.get(i)));
|
||||
|
||||
Operation operation = columnOperations.get(i).validateAndAddBoundNames(name, boundNames);
|
||||
// For UPDATES, the parser validates we don't set the same value twice but we must check it here for INSERT
|
||||
for (int j = 0; j < i; j++)
|
||||
if (name.name.equals(columnNames.get(j)))
|
||||
throw new InvalidRequestException(String.format("Multiple definitions found for column %s", name));
|
||||
|
||||
Term.Raw value = columnValues.get(i);
|
||||
|
||||
switch (name.kind)
|
||||
{
|
||||
case KEY_ALIAS:
|
||||
case COLUMN_ALIAS:
|
||||
if (processedKeys.containsKey(name.name))
|
||||
Term t = value.prepare(name);
|
||||
t.collectMarkerSpecification(boundNames);
|
||||
if (processedKeys.put(name.name, Collections.singletonList(t)) != null)
|
||||
throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name));
|
||||
// We know collection are not accepted for key and column aliases
|
||||
if (operation.getType() != Operation.Type.COLUMN && operation.getType() != Operation.Type.PREPARED)
|
||||
throw new InvalidRequestException(String.format("Invalid definition for %s, not a collection type", name));
|
||||
processedKeys.put(name.name, operation.getValues());
|
||||
break;
|
||||
case VALUE_ALIAS:
|
||||
case COLUMN_METADATA:
|
||||
if (processedColumns.containsKey(name))
|
||||
throw new InvalidRequestException(String.format("Multiple definitions found for column %s", name));
|
||||
processedColumns.put(name, operation);
|
||||
Operation operation = new Operation.SetValue(value).prepare(name);
|
||||
operation.collectMarkerSpecification(boundNames);
|
||||
processedColumns.add(operation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -310,32 +305,14 @@ public class UpdateStatement extends ModificationStatement
|
|||
else
|
||||
{
|
||||
// Created from an UPDATE
|
||||
for (Pair<ColumnIdentifier, Operation> entry : columns)
|
||||
for (Pair<ColumnIdentifier, Operation.RawUpdate> entry : operations)
|
||||
{
|
||||
CFDefinition.Name name = cfDef.get(entry.left);
|
||||
if (name == null)
|
||||
throw new InvalidRequestException(String.format("Unknown identifier %s", entry.left));
|
||||
|
||||
Operation operation = entry.right.validateAndAddBoundNames(name, boundNames);
|
||||
|
||||
switch (operation.getType())
|
||||
{
|
||||
case COUNTER:
|
||||
if (type != Type.COUNTER)
|
||||
throw new InvalidRequestException("Invalid counter operation on non-counter table.");
|
||||
break;
|
||||
case LIST:
|
||||
case SET:
|
||||
case MAP:
|
||||
case COLUMN:
|
||||
if (type == Type.COUNTER)
|
||||
throw new InvalidRequestException("Invalid non-counter operation on counter table.");
|
||||
break;
|
||||
case PREPARED:
|
||||
if (type == Type.COUNTER && !((PreparedOperation)operation).isPotentialCounterOperation())
|
||||
throw new InvalidRequestException("Invalid non-counter operation on counter table.");
|
||||
break;
|
||||
}
|
||||
Operation operation = entry.right.prepare(name);
|
||||
operation.collectMarkerSpecification(boundNames);
|
||||
|
||||
switch (name.kind)
|
||||
{
|
||||
|
|
@ -344,10 +321,7 @@ public class UpdateStatement extends ModificationStatement
|
|||
throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", entry.left));
|
||||
case VALUE_ALIAS:
|
||||
case COLUMN_METADATA:
|
||||
for (Operation otherOp : processedColumns.get(name))
|
||||
if (otherOp.getType() == Operation.Type.COLUMN)
|
||||
throw new InvalidRequestException(String.format("Multiple definitions found for column %s", name));
|
||||
processedColumns.put(name, operation);
|
||||
processedColumns.add(operation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -376,27 +350,28 @@ public class UpdateStatement extends ModificationStatement
|
|||
{
|
||||
case KEY_ALIAS:
|
||||
case COLUMN_ALIAS:
|
||||
List<Term> values;
|
||||
List<Term.Raw> rawValues;
|
||||
if (rel.operator() == Relation.Type.EQ)
|
||||
values = Collections.singletonList(rel.getValue());
|
||||
rawValues = Collections.singletonList(rel.getValue());
|
||||
else if (name.kind == CFDefinition.Name.Kind.KEY_ALIAS && rel.operator() == Relation.Type.IN)
|
||||
values = rel.getInValues();
|
||||
rawValues = rel.getInValues();
|
||||
else
|
||||
throw new InvalidRequestException(String.format("Invalid operator %s for key %s", rel.operator(), rel.getEntity()));
|
||||
throw new InvalidRequestException(String.format("Invalid operator %s for PRIMARY KEY part %s", rel.operator(), name));
|
||||
|
||||
if (processed.containsKey(name.name))
|
||||
throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name));
|
||||
for (Term value : values)
|
||||
List<Term> values = new ArrayList<Term>(rawValues.size());
|
||||
for (Term.Raw raw : rawValues)
|
||||
{
|
||||
value.validateType(name.toString(), name.type);
|
||||
if (value.isBindMarker())
|
||||
names[value.bindIndex] = name;
|
||||
Term t = raw.prepare(name);
|
||||
t.collectMarkerSpecification(names);
|
||||
values.add(t);
|
||||
}
|
||||
processed.put(name.name, values);
|
||||
|
||||
if (processed.put(name.name, values) != null)
|
||||
throw new InvalidRequestException(String.format("Multiple definitions found for PRIMARY KEY part %s", name));
|
||||
break;
|
||||
case VALUE_ALIAS:
|
||||
case COLUMN_METADATA:
|
||||
throw new InvalidRequestException(String.format("PRIMARY KEY part %s found in SET part", rel.getEntity()));
|
||||
throw new InvalidRequestException(String.format("Non PRIMARY KEY %s found in where clause", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -406,7 +381,7 @@ public class UpdateStatement extends ModificationStatement
|
|||
return String.format("UpdateStatement(name=%s, keys=%s, columns=%s, timestamp=%s, timeToLive=%s)",
|
||||
cfName,
|
||||
whereClause,
|
||||
columns,
|
||||
operations,
|
||||
isSetTimestamp() ? getTimestamp(-1) : "<now>",
|
||||
getTimeToLive());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.google.common.base.Function;
|
|||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -204,7 +205,7 @@ public class SystemTable
|
|||
return sstable.descriptor.generation;
|
||||
}
|
||||
});
|
||||
processInternal(String.format(req, COMPACTION_LOG, compactionId, cfs.table.name, cfs.columnFamily, StringUtils.join(generations.iterator(), ',')));
|
||||
processInternal(String.format(req, COMPACTION_LOG, compactionId, cfs.table.name, cfs.columnFamily, StringUtils.join(Sets.newHashSet(generations), ',')));
|
||||
forceBlockingFlush(COMPACTION_LOG);
|
||||
return compactionId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.exceptions.SyntaxException;
|
||||
import org.apache.cassandra.db.IColumn;
|
||||
import org.apache.cassandra.db.OnDiskAtom;
|
||||
|
|
@ -150,12 +149,6 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>
|
|||
/* validate that the byte array is a valid sequence for the type we are supposed to be comparing */
|
||||
public abstract void validate(ByteBuffer bytes) throws MarshalException;
|
||||
|
||||
/* CQL3 types will actually override this, but we use a default for compatibility sake */
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/* Most of our internal type should override that. */
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,19 +18,14 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcAscii;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
|
||||
public class AsciiType extends AbstractType<String>
|
||||
{
|
||||
public static final AsciiType instance = new AsciiType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.STRING);
|
||||
|
||||
AsciiType() {} // singleton
|
||||
|
||||
public String getString(ByteBuffer bytes)
|
||||
|
|
@ -76,11 +71,6 @@ public class AsciiType extends AbstractType<String>
|
|||
}
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.ASCII;
|
||||
|
|
|
|||
|
|
@ -18,19 +18,15 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcBoolean;
|
||||
import org.apache.cassandra.cql3.Constants;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
|
||||
public class BooleanType extends AbstractType<Boolean>
|
||||
{
|
||||
public static final BooleanType instance = new BooleanType();
|
||||
|
||||
public final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.BOOLEAN);
|
||||
|
||||
BooleanType() {} // singleton
|
||||
|
||||
public Boolean compose(ByteBuffer bytes)
|
||||
|
|
@ -84,11 +80,6 @@ public class BooleanType extends AbstractType<Boolean>
|
|||
throw new MarshalException(String.format("Expected 1 or 0 byte value (%d)", bytes.remaining()));
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.BOOLEAN;
|
||||
|
|
|
|||
|
|
@ -18,12 +18,9 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcBytes;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.Hex;
|
||||
|
||||
|
|
@ -31,8 +28,6 @@ public class BytesType extends AbstractType<ByteBuffer>
|
|||
{
|
||||
public static final BytesType instance = new BytesType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.HEX);
|
||||
|
||||
BytesType() {} // singleton
|
||||
|
||||
public ByteBuffer compose(ByteBuffer bytes)
|
||||
|
|
@ -88,11 +83,6 @@ public class BytesType extends AbstractType<ByteBuffer>
|
|||
return this == previous || previous == AsciiType.instance || previous == UTF8Type.instance;
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.BLOB;
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public abstract class CollectionType<T> extends AbstractType<T>
|
|||
}
|
||||
|
||||
// Utilitary method
|
||||
protected ByteBuffer pack(List<ByteBuffer> buffers, int elements, int size)
|
||||
protected static ByteBuffer pack(List<ByteBuffer> buffers, int elements, int size)
|
||||
{
|
||||
ByteBuffer result = ByteBuffer.allocate(2 + size);
|
||||
result.putShort((short)elements);
|
||||
|
|
@ -105,6 +105,13 @@ public abstract class CollectionType<T> extends AbstractType<T>
|
|||
return (ByteBuffer)result.flip();
|
||||
}
|
||||
|
||||
public static ByteBuffer pack(List<ByteBuffer> buffers, int elements)
|
||||
{
|
||||
int size = 0;
|
||||
for (ByteBuffer bb : buffers)
|
||||
size += 2 + bb.remaining();
|
||||
return pack(buffers, elements, size);
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -235,14 +235,12 @@ public class CompositeType extends AbstractCompositeType
|
|||
this.serializedSize = b.serializedSize;
|
||||
}
|
||||
|
||||
public Builder add(Term t, Relation.Type op, List<ByteBuffer> variables) throws InvalidRequestException
|
||||
public Builder add(ByteBuffer buffer, Relation.Type op)
|
||||
{
|
||||
if (components.size() >= composite.types.size())
|
||||
throw new IllegalStateException("Composite column is already fully constructed");
|
||||
|
||||
int current = components.size();
|
||||
AbstractType currentType = composite.types.get(current);
|
||||
ByteBuffer buffer = t.getByteBuffer(currentType, variables);
|
||||
components.add(buffer);
|
||||
|
||||
/*
|
||||
|
|
@ -272,13 +270,7 @@ public class CompositeType extends AbstractCompositeType
|
|||
|
||||
public Builder add(ByteBuffer bb)
|
||||
{
|
||||
int current = components.size();
|
||||
if (current >= composite.types.size())
|
||||
throw new IllegalStateException("Composite column is already fully constructed");
|
||||
|
||||
components.add(bb);
|
||||
endOfComponents[current] = (byte) 0;
|
||||
return this;
|
||||
return add(bb, Relation.Type.EQ);
|
||||
}
|
||||
|
||||
public int componentCount()
|
||||
|
|
|
|||
|
|
@ -18,11 +18,9 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql3.Constants;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
|
|
@ -30,8 +28,6 @@ public class CounterColumnType extends AbstractCommutativeType
|
|||
{
|
||||
public static final CounterColumnType instance = new CounterColumnType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER);
|
||||
|
||||
CounterColumnType() {} // singleton
|
||||
|
||||
public int compare(ByteBuffer o1, ByteBuffer o2)
|
||||
|
|
@ -66,11 +62,6 @@ public class CounterColumnType extends AbstractCommutativeType
|
|||
throw new MarshalException(String.format("Expected 8 or 0 byte long (%d)", bytes.remaining()));
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.COUNTER;
|
||||
|
|
|
|||
|
|
@ -23,12 +23,10 @@ import java.nio.ByteBuffer;
|
|||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcDate;
|
||||
import org.apache.cassandra.cql3.Constants;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.commons.lang.time.DateUtils;
|
||||
|
||||
|
|
@ -39,8 +37,6 @@ public class DateType extends AbstractType<Date>
|
|||
static final String DEFAULT_FORMAT = iso8601Patterns[3];
|
||||
static final SimpleDateFormat FORMATTER = new SimpleDateFormat(DEFAULT_FORMAT);
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.STRING, Term.Type.INTEGER);
|
||||
|
||||
DateType() {} // singleton
|
||||
|
||||
public Date compose(ByteBuffer bytes)
|
||||
|
|
@ -130,11 +126,6 @@ public class DateType extends AbstractType<Date>
|
|||
throw new MarshalException(String.format("Expected 8 or 0 byte long for date (%d)", bytes.remaining()));
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.TIMESTAMP;
|
||||
|
|
|
|||
|
|
@ -19,20 +19,15 @@ package org.apache.cassandra.db.marshal;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcDecimal;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public class DecimalType extends AbstractType<BigDecimal>
|
||||
{
|
||||
public static final DecimalType instance = new DecimalType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER, Term.Type.FLOAT);
|
||||
|
||||
DecimalType() {} // singleton
|
||||
|
||||
public int compare(ByteBuffer bb0, ByteBuffer bb1)
|
||||
|
|
@ -92,11 +87,6 @@ public class DecimalType extends AbstractType<BigDecimal>
|
|||
// no useful check for invalid decimals.
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.DECIMAL;
|
||||
|
|
|
|||
|
|
@ -18,20 +18,15 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcDouble;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public class DoubleType extends AbstractType<Double>
|
||||
{
|
||||
public static final DoubleType instance = new DoubleType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER, Term.Type.FLOAT);
|
||||
|
||||
DoubleType() {} // singleton
|
||||
|
||||
public Double compose(ByteBuffer bytes)
|
||||
|
|
@ -95,11 +90,6 @@ public class DoubleType extends AbstractType<Double>
|
|||
throw new MarshalException(String.format("Expected 8 or 0 byte value for a double (%d)", bytes.remaining()));
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.DOUBLE;
|
||||
|
|
|
|||
|
|
@ -18,12 +18,9 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcFloat;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
|
||||
|
|
@ -31,8 +28,6 @@ public class FloatType extends AbstractType<Float>
|
|||
{
|
||||
public static final FloatType instance = new FloatType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER, Term.Type.FLOAT);
|
||||
|
||||
FloatType() {} // singleton
|
||||
|
||||
public Float compose(ByteBuffer bytes)
|
||||
|
|
@ -94,11 +89,6 @@ public class FloatType extends AbstractType<Float>
|
|||
throw new MarshalException(String.format("Expected 4 or 0 byte value for a float (%d)", bytes.remaining()));
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.FLOAT;
|
||||
|
|
|
|||
|
|
@ -20,20 +20,15 @@ package org.apache.cassandra.db.marshal;
|
|||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcInetAddress;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public class InetAddressType extends AbstractType<InetAddress>
|
||||
{
|
||||
public static final InetAddressType instance = new InetAddressType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.STRING);
|
||||
|
||||
InetAddressType() {} // singleton
|
||||
|
||||
public InetAddress compose(ByteBuffer bytes)
|
||||
|
|
@ -88,11 +83,6 @@ public class InetAddressType extends AbstractType<InetAddress>
|
|||
}
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.INET;
|
||||
|
|
|
|||
|
|
@ -18,20 +18,15 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcInt32;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public class Int32Type extends AbstractType<Integer>
|
||||
{
|
||||
public static final Int32Type instance = new Int32Type();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER);
|
||||
|
||||
Int32Type() {} // singleton
|
||||
|
||||
public Integer compose(ByteBuffer bytes)
|
||||
|
|
@ -101,11 +96,6 @@ public class Int32Type extends AbstractType<Integer>
|
|||
throw new MarshalException(String.format("Expected 4 or 0 byte int (%d)", bytes.remaining()));
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.INT;
|
||||
|
|
|
|||
|
|
@ -19,20 +19,15 @@ package org.apache.cassandra.db.marshal;
|
|||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcInteger;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public final class IntegerType extends AbstractType<BigInteger>
|
||||
{
|
||||
public static final IntegerType instance = new IntegerType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER);
|
||||
|
||||
private static int findMostSignificantByte(ByteBuffer bytes)
|
||||
{
|
||||
int len = bytes.remaining() - 1;
|
||||
|
|
@ -159,11 +154,6 @@ public final class IntegerType extends AbstractType<BigInteger>
|
|||
// no invalid integers.
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.VARINT;
|
||||
|
|
|
|||
|
|
@ -18,12 +18,9 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcLexicalUUID;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
|
|
@ -31,8 +28,6 @@ public class LexicalUUIDType extends AbstractType<UUID>
|
|||
{
|
||||
public static final LexicalUUIDType instance = new LexicalUUIDType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.UUID);
|
||||
|
||||
LexicalUUIDType() {} // singleton
|
||||
|
||||
public UUID compose(ByteBuffer bytes)
|
||||
|
|
@ -93,9 +88,4 @@ public class LexicalUUIDType extends AbstractType<UUID>
|
|||
throw new MarshalException(String.format("LexicalUUID should be 16 or 0 bytes (%d)", bytes.remaining()));
|
||||
// not sure what the version should be for this.
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,20 +18,15 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcLong;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public class LongType extends AbstractType<Long>
|
||||
{
|
||||
public static final LongType instance = new LongType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.INTEGER);
|
||||
|
||||
LongType() {} // singleton
|
||||
|
||||
public Long compose(ByteBuffer bytes)
|
||||
|
|
@ -101,11 +96,6 @@ public class LongType extends AbstractType<Long>
|
|||
throw new MarshalException(String.format("Expected 8 or 0 byte long (%d)", bytes.remaining()));
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.BIGINT;
|
||||
|
|
|
|||
|
|
@ -18,15 +18,12 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcTimeUUID;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
|
|
@ -35,9 +32,6 @@ public class TimeUUIDType extends AbstractType<UUID>
|
|||
public static final TimeUUIDType instance = new TimeUUIDType();
|
||||
|
||||
static final Pattern regexPattern = Pattern.compile("[A-Fa-f0-9]{8}\\-[A-Fa-f0-9]{4}\\-[A-Fa-f0-9]{4}\\-[A-Fa-f0-9]{4}\\-[A-Fa-f0-9]{12}");
|
||||
static final Pattern functionPattern = Pattern.compile("(\\w+)\\((.*)\\)");
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.UUID);
|
||||
|
||||
TimeUUIDType() {} // singleton
|
||||
|
||||
|
|
@ -171,34 +165,8 @@ public class TimeUUIDType extends AbstractType<UUID>
|
|||
}
|
||||
else
|
||||
{
|
||||
Matcher m = functionPattern.matcher(source);
|
||||
if (!m.matches())
|
||||
throw new MarshalException(String.format("Unable to make a time-based UUID from '%s'", source));
|
||||
|
||||
String fct = m.group(1);
|
||||
String arg = m.group(2);
|
||||
|
||||
if (fct.equalsIgnoreCase("minTimeUUID"))
|
||||
{
|
||||
idBytes = decompose(UUIDGen.minTimeUUID(DateType.dateStringToTimestamp(arg)));
|
||||
}
|
||||
else if (fct.equalsIgnoreCase("maxTimeUUID"))
|
||||
{
|
||||
idBytes = decompose(UUIDGen.maxTimeUUID(DateType.dateStringToTimestamp(arg)));
|
||||
}
|
||||
else if (fct.equalsIgnoreCase("now"))
|
||||
{
|
||||
if (!arg.trim().isEmpty())
|
||||
throw new MarshalException(String.format("The 'now' timeuuid method takes no argument ('%s' provided)", arg));
|
||||
|
||||
idBytes = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MarshalException(String.format("Unknown timeuuid method '%s'", fct));
|
||||
}
|
||||
throw new MarshalException(String.format("Unknown timeuuid representation: %s", source));
|
||||
}
|
||||
|
||||
return idBytes;
|
||||
}
|
||||
|
||||
|
|
@ -216,11 +184,6 @@ public class TimeUUIDType extends AbstractType<UUID>
|
|||
}
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.TIMEUUID;
|
||||
|
|
|
|||
|
|
@ -18,19 +18,15 @@
|
|||
package org.apache.cassandra.db.marshal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcUTF8;
|
||||
import org.apache.cassandra.cql3.Constants;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
|
||||
public class UTF8Type extends AbstractType<String>
|
||||
{
|
||||
public static final UTF8Type instance = new UTF8Type();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.STRING);
|
||||
|
||||
UTF8Type() {} // singleton
|
||||
|
||||
public String compose(ByteBuffer bytes)
|
||||
|
|
@ -197,11 +193,6 @@ public class UTF8Type extends AbstractType<String>
|
|||
return this == previous || previous == AsciiType.instance;
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.TEXT;
|
||||
|
|
|
|||
|
|
@ -20,13 +20,10 @@ package org.apache.cassandra.db.marshal;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.ParseException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.cassandra.cql.jdbc.JdbcUUID;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.Term;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
|
@ -48,8 +45,6 @@ public class UUIDType extends AbstractType<UUID>
|
|||
{
|
||||
public static final UUIDType instance = new UUIDType();
|
||||
|
||||
private final Set<Term.Type> supportedCQL3Constants = EnumSet.of(Term.Type.UUID);
|
||||
|
||||
UUIDType()
|
||||
{
|
||||
}
|
||||
|
|
@ -249,11 +244,6 @@ public class UUIDType extends AbstractType<UUID>
|
|||
return idBytes;
|
||||
}
|
||||
|
||||
public Set<Term.Type> supportedCQL3Constants()
|
||||
{
|
||||
return supportedCQL3Constants;
|
||||
}
|
||||
|
||||
public CQL3Type asCQL3Type()
|
||||
{
|
||||
return CQL3Type.Native.UUID;
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ public class Murmur3Partitioner extends AbstractPartitioner<LongToken>
|
|||
|
||||
public Token<Long> fromByteArray(ByteBuffer bytes)
|
||||
{
|
||||
return new LongToken(bytes.getLong());
|
||||
return new LongToken(ByteBufferUtil.toLong(bytes));
|
||||
}
|
||||
|
||||
public String toString(Token<Long> longToken)
|
||||
|
|
|
|||
Loading…
Reference in New Issue