Allow dropping COMPACT STORAGE flag

Patch by Alex Petrov; reviewed by Sylvain Lebresne for CASSANDRA-10857.
This commit is contained in:
Alex Petrov 2017-09-30 08:56:22 +02:00
parent b8697441d7
commit 6c29ee84a2
36 changed files with 848 additions and 92 deletions

View File

@ -37,6 +37,23 @@ Upgrading
- Nothing specific to this release, but please see previous upgrading sections,
especially if you are upgrading from 2.2.
Compact Storage
---------------
- Starting version 4.0, Thrift and COMPACT STORAGE is no longer supported.
'ALTER ... DROP COMPACT STORAGE' statement makes Compact Tables CQL-compatible,
exposing internal structure of Thrift/Compact Tables. You can find more details
on exposed internal structure under:
http://cassandra.apache.org/doc/latest/cql/appendices.html#appendix-c-dropping-compact-storage
For uninterrupted cluster upgrades, drivers now support 'NO_COMPACT' startup option.
Supplying this flag will have same effect as 'DROP COMPACT STORAGE', but only for the
current connection.
In order to upgrade, clients supporting a non-compact schema view can be rolled out
gradually. When all the clients are updated 'ALTER ... DROP COMPACT STORAGE' can be
executed. After dropping compact storage, NO_COMPACT' option will have no effect
after that.
Materialized Views
-------------------
- Cassandra will no longer allow dropping columns on tables with Materialized Views.

View File

@ -205,6 +205,7 @@ parser.add_option("--browser", dest='browser', help="""The browser to use to dis
- one of the supported browsers in https://docs.python.org/2/library/webbrowser.html.
- browser path followed by %s, example: /usr/bin/google-chrome-stable %s""")
parser.add_option('--ssl', action='store_true', help='Use SSL', default=False)
parser.add_option('--no_compact', action='store_true', help='No Compact', default=False)
parser.add_option("-u", "--username", help="Authenticate as user.")
parser.add_option("-p", "--password", help="Authenticate using password.")
parser.add_option('-k', '--keyspace', help='Authenticate to the given keyspace.')
@ -702,6 +703,7 @@ class Shell(cmd.Cmd):
completekey=DEFAULT_COMPLETEKEY, browser=None, use_conn=None,
cqlver=DEFAULT_CQLVER, keyspace=None,
tracing_enabled=False, expand_enabled=False,
no_compact=False,
display_nanotime_format=DEFAULT_NANOTIME_FORMAT,
display_timestamp_format=DEFAULT_TIMESTAMP_FORMAT,
display_date_format=DEFAULT_DATE_FORMAT,
@ -732,7 +734,7 @@ class Shell(cmd.Cmd):
else:
self.conn = Cluster(contact_points=(self.hostname,), port=self.port, cql_version=cqlver,
protocol_version=protocol_version,
auth_provider=self.auth_provider,
auth_provider=self.auth_provider, no_compact=no_compact,
ssl_options=sslhandling.ssl_settings(hostname, CONFIG_FILE) if ssl else None,
load_balancing_policy=WhiteListRoundRobinPolicy([self.hostname]),
control_connection_timeout=connect_timeout,
@ -2486,6 +2488,7 @@ def read_options(cmdlineargs, environment):
optvalues.debug = False
optvalues.file = None
optvalues.ssl = False
optvalues.no_compact = False
optvalues.encoding = option_with_default(configs.get, 'ui', 'encoding', UTF8)
optvalues.tty = option_with_default(configs.getboolean, 'ui', 'tty', sys.stdin.isatty())
@ -2643,6 +2646,7 @@ def main(options, hostname, port):
browser=options.browser,
cqlver=options.cqlversion,
keyspace=options.keyspace,
no_compact=options.no_compact,
display_timestamp_format=options.time_format,
display_nanotime_format=options.nanotime_format,
display_date_format=options.date_format,

View File

@ -271,6 +271,10 @@ Table of Contents
different from the protocol version.
- "COMPRESSION": the compression algorithm to use for frames (See section 5).
This is optional; if not specified no compression will be used.
- "NO_COMPACT": whether or not connection has to be established in compatibility
mode. This mode will make all Thrift and Compact Tables to be exposed as if
they were CQL Tables. This is optional; if not specified, the option will
not be used.
4.1.2. AUTH_RESPONSE

Binary file not shown.

View File

@ -39,6 +39,7 @@ import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.transport.messages.ResultMessage;
@ -465,7 +466,7 @@ public class CassandraRoleManager implements IRoleManager
{
try
{
return QueryProcessor.parseStatement(String.format(template, keyspace, table)).prepare().statement;
return QueryProcessor.parseStatement(String.format(template, keyspace, table)).prepare(ClientState.forInternalCalls()).statement;
}
catch (RequestValidationException e)
{

View File

@ -79,10 +79,6 @@ public final class CFMetaData
public final Pair<String, String> ksAndCFName;
public final byte[] ksAndCFBytes;
private final ImmutableSet<Flag> flags;
private final boolean isDense;
private final boolean isCompound;
private final boolean isSuper;
private final boolean isCounter;
private final boolean isView;
private final boolean isIndex;
@ -94,6 +90,11 @@ public final class CFMetaData
private final Serializers serializers;
// non-final, for now
private volatile ImmutableSet<Flag> flags;
private volatile boolean isDense;
private volatile boolean isCompound;
private volatile boolean isSuper;
public volatile TableParams params = TableParams.DEFAULT;
private volatile Map<ByteBuffer, DroppedColumn> droppedColumns = new HashMap<>();
@ -127,6 +128,9 @@ public final class CFMetaData
private volatile ColumnDefinition superCfKeyColumn;
private volatile ColumnDefinition superCfValueColumn;
/** Caches a non-compact version of the metadata for compact tables to be used with the NO_COMPACT protocol option. */
private volatile CFMetaData nonCompactCopy = null;
public boolean isSuperColumnKeyColumn(ColumnDefinition cd)
{
return cd.name.equals(superCfKeyColumn.name);
@ -330,6 +334,9 @@ public final class CFMetaData
// are kept because they are often useful in a different format.
private void rebuild()
{
// A non-compact copy will be created lazily
this.nonCompactCopy = null;
if (isCompactTable())
{
this.compactValueColumn = isSuper() ?
@ -505,6 +512,38 @@ public final class CFMetaData
return params(indexParams.build());
}
/**
* Returns a cached non-compact version of this table. Cached version has to be invalidated
* every time the table is rebuilt.
*/
public CFMetaData asNonCompact()
{
assert isCompactTable() : "Can't get non-compact version of a CQL table";
// Note that this is racy, but re-computing the non-compact copy a few times on first uses isn't a big deal so
// we don't bother.
if (nonCompactCopy == null)
{
nonCompactCopy = copyOpts(new CFMetaData(ksName,
cfName,
cfId,
false,
isCounter,
false,
true,
isView,
copy(partitionKeyColumns),
copy(clusteringColumns),
copy(partitionColumns),
partitioner,
superCfKeyColumn,
superCfValueColumn),
this);
}
return nonCompactCopy;
}
public CFMetaData copy()
{
return copy(cfId);
@ -842,6 +881,12 @@ public final class CFMetaData
superCfKeyColumn = cfm.superCfKeyColumn;
superCfValueColumn = cfm.superCfValueColumn;
isDense = cfm.isDense;
isCompound = cfm.isCompound;
isSuper = cfm.isSuper;
flags = cfm.flags;
rebuild();
// compaction thresholds are checked by ThriftValidation. We shouldn't be doing
@ -874,12 +919,6 @@ public final class CFMetaData
if (!cfm.cfId.equals(cfId))
throw new ConfigurationException(String.format("Column family ID mismatch (found %s; expected %s)",
cfm.cfId, cfId));
// Dense flag can get set, see CASSANDRA-12373 for details. We have to remove flag from both parts because
// there's no guaranteed call order in the call.
if (!cfm.flags.equals(flags) && (!isSuper() || !Sets.difference(cfm.flags, Sets.immutableEnumSet(Flag.DENSE)).equals(Sets.difference(flags, Sets.immutableEnumSet(Flag.DENSE)))))
throw new ConfigurationException("Types do not match: " + cfm.flags + " != " + flags);
}

View File

@ -781,7 +781,7 @@ createTriggerStatement returns [CreateTriggerStatement expr]
@init {
boolean ifNotExists = false;
}
: K_CREATE K_TRIGGER (K_IF K_NOT K_EXISTS { ifNotExists = true; } )? (name=cident)
: K_CREATE K_TRIGGER (K_IF K_NOT K_EXISTS { ifNotExists = true; } )? (name=noncol_ident)
K_ON cf=columnFamilyName K_USING cls=STRING_LITERAL
{ $expr = new CreateTriggerStatement(cf, name.toString(), $cls.text, ifNotExists); }
;
@ -791,7 +791,7 @@ createTriggerStatement returns [CreateTriggerStatement expr]
*/
dropTriggerStatement returns [DropTriggerStatement expr]
@init { boolean ifExists = false; }
: K_DROP K_TRIGGER (K_IF K_EXISTS { ifExists = true; } )? (name=cident) K_ON cf=columnFamilyName
: K_DROP K_TRIGGER (K_IF K_EXISTS { ifExists = true; } )? (name=noncol_ident) K_ON cf=columnFamilyName
{ $expr = new DropTriggerStatement(cf, name.toString(), ifExists); }
;
@ -816,20 +816,21 @@ alterTableStatement returns [AlterTableStatement expr]
@init {
AlterTableStatement.Type type = null;
TableAttributes attrs = new TableAttributes();
Map<ColumnIdentifier.Raw, ColumnIdentifier.Raw> renames = new HashMap<ColumnIdentifier.Raw, ColumnIdentifier.Raw>();
Map<ColumnIdentifier.Raw, ColumnIdentifier> renames = new HashMap<ColumnIdentifier.Raw, ColumnIdentifier>();
boolean isStatic = false;
Long dropTimestamp = null;
}
: K_ALTER K_COLUMNFAMILY cf=columnFamilyName
( K_ALTER id=cident K_TYPE v=comparatorType { type = AlterTableStatement.Type.ALTER; }
| K_ADD id=cident v=comparatorType ({ isStatic=true; } K_STATIC)? { type = AlterTableStatement.Type.ADD; }
( K_ALTER id=cident K_TYPE v=comparatorType { type = AlterTableStatement.Type.ALTER; }
| K_ADD aid=ident {id=new ColumnIdentifier.ColumnIdentifierValue(aid);} v=comparatorType ({ isStatic=true; } K_STATIC)? { type = AlterTableStatement.Type.ADD; }
| K_DROP id=cident { type = AlterTableStatement.Type.DROP; }
| K_DROP id=cident K_USING K_TIMESTAMP t=INTEGER { type = AlterTableStatement.Type.DROP;
dropTimestamp = Long.parseLong(Constants.Literal.integer($t.text).getText()); }
| K_DROP K_COMPACT K_STORAGE { type = AlterTableStatement.Type.DROP_COMPACT_STORAGE; }
| K_WITH properties[attrs] { type = AlterTableStatement.Type.OPTS; }
| K_RENAME { type = AlterTableStatement.Type.RENAME; }
id1=cident K_TO toId1=cident { renames.put(id1, toId1); }
( K_AND idn=cident K_TO toIdn=cident { renames.put(idn, toIdn); } )*
id1=cident K_TO toId1=ident { renames.put(id1, toId1); }
( K_AND idn=cident K_TO toIdn=ident { renames.put(idn, toIdn); } )*
)
{
$expr = new AlterTableStatement(cf, type, id, v, attrs, renames, isStatic, dropTimestamp);
@ -1169,10 +1170,14 @@ userPassword[RoleOptions opts]
// Column Identifiers. These need to be treated differently from other
// identifiers because the underlying comparator is not necessarily text. See
// CASSANDRA-8178 for details.
// Also, we need to support the internal of the super column map (for backward
// compatibility) which is empty (we only want to allow this is queries, not for
// creating table or other).
cident returns [ColumnIdentifier.Raw id]
: t=IDENT { $id = new ColumnIdentifier.Literal($t.text, false); }
| t=QUOTED_NAME { $id = new ColumnIdentifier.Literal($t.text, true); }
| k=unreserved_keyword { $id = new ColumnIdentifier.Literal(k, false); }
| EMPTY_QUOTED_NAME { $id = new ColumnIdentifier.Literal("", false); }
;
// Column identifiers where the comparator is known to be text
@ -1309,7 +1314,9 @@ intValue returns [Term.Raw value]
;
functionName returns [FunctionName s]
: (ks=keyspaceName '.')? f=allowedFunctionName { $s = new FunctionName(ks, f); }
// antlr might try to recover and give a null for f. It will still error out in the end, but FunctionName
// wouldn't be happy with that so we should bypass this for now or we'll have a weird user-facing error
: (ks=keyspaceName '.')? f=allowedFunctionName { $s = f == null ? null : new FunctionName(ks, f); }
;
allowedFunctionName returns [String s]
@ -1822,6 +1829,10 @@ STRING_LITERAL
)
;
EMPTY_QUOTED_NAME
: '\"' '\"'
;
QUOTED_NAME
@init{ StringBuilder b = new StringBuilder(); }
@after{ setText(b.toString()); }

View File

@ -517,7 +517,7 @@ public class QueryProcessor implements QueryHandler
((CFStatement)statement).prepareKeyspace(clientState);
Tracing.trace("Preparing statement");
return statement.prepare();
return statement.prepare(clientState);
}
public static ParsedStatement parseStatement(String queryStr) throws SyntaxException

View File

@ -31,8 +31,6 @@ import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
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.ReversedType;
import org.apache.cassandra.db.view.View;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.schema.IndexMetadata;
@ -49,14 +47,14 @@ public class AlterTableStatement extends SchemaAlteringStatement
{
public enum Type
{
ADD, ALTER, DROP, OPTS, RENAME
ADD, ALTER, DROP, DROP_COMPACT_STORAGE, OPTS, RENAME
}
public final Type oType;
public final CQL3Type.Raw validator;
public final ColumnIdentifier.Raw rawColumnName;
private final TableAttributes attrs;
private final Map<ColumnIdentifier.Raw, ColumnIdentifier.Raw> renames;
private final Map<ColumnIdentifier.Raw, ColumnIdentifier> renames;
private final boolean isStatic; // Only for ALTER ADD
private final Long deleteTimestamp;
@ -65,7 +63,7 @@ public class AlterTableStatement extends SchemaAlteringStatement
ColumnIdentifier.Raw columnName,
CQL3Type.Raw validator,
TableAttributes attrs,
Map<ColumnIdentifier.Raw, ColumnIdentifier.Raw> renames,
Map<ColumnIdentifier.Raw, ColumnIdentifier> renames,
boolean isStatic,
Long deleteTimestamp)
{
@ -95,15 +93,15 @@ public class AlterTableStatement extends SchemaAlteringStatement
if (meta.isView())
throw new InvalidRequestException("Cannot use ALTER TABLE on Materialized View");
CFMetaData cfm = meta.copy();
CFMetaData cfm;
CQL3Type validator = this.validator == null ? null : this.validator.prepare(keyspace());
ColumnIdentifier columnName = null;
ColumnDefinition def = null;
if (rawColumnName != null)
{
columnName = rawColumnName.prepare(cfm);
def = cfm.getColumnDefinition(columnName);
columnName = rawColumnName.prepare(meta);
def = meta.getColumnDefinition(columnName);
}
List<ViewDefinition> viewUpdates = null;
@ -115,9 +113,11 @@ public class AlterTableStatement extends SchemaAlteringStatement
throw new InvalidRequestException("Altering of types is not allowed");
case ADD:
assert columnName != null;
if (cfm.isDense())
if (meta.isDense())
throw new InvalidRequestException("Cannot add new column to a COMPACT STORAGE table");
cfm = meta.copy();
if (isStatic)
{
if (!cfm.isCompound())
@ -190,11 +190,14 @@ public class AlterTableStatement extends SchemaAlteringStatement
case DROP:
assert columnName != null;
if (!cfm.isCQLTable())
if (!meta.isCQLTable())
throw new InvalidRequestException("Cannot drop columns from a non-CQL3 table");
if (def == null)
throw new InvalidRequestException(String.format("Column %s was not found in table %s", columnName, columnFamily()));
cfm = meta.copy();
switch (def.kind)
{
case PARTITION_KEY:
@ -238,11 +241,19 @@ public class AlterTableStatement extends SchemaAlteringStatement
columnName.toString(),
keyspace()));
break;
case DROP_COMPACT_STORAGE:
if (!meta.isCompactTable())
throw new InvalidRequestException("Cannot DROP COMPACT STORAGE on table without COMPACT STORAGE");
cfm = meta.asNonCompact();
break;
case OPTS:
if (attrs == null)
throw new InvalidRequestException("ALTER TABLE WITH invoked, but no parameters found");
attrs.validate();
cfm = meta.copy();
TableParams params = attrs.asAlteredTableParams(cfm.params);
if (!Iterables.isEmpty(views) && params.gcGraceSeconds == 0)
@ -261,10 +272,13 @@ public class AlterTableStatement extends SchemaAlteringStatement
break;
case RENAME:
for (Map.Entry<ColumnIdentifier.Raw, ColumnIdentifier.Raw> entry : renames.entrySet())
cfm = meta.copy();
for (Map.Entry<ColumnIdentifier.Raw, ColumnIdentifier> entry : renames.entrySet())
{
ColumnIdentifier from = entry.getKey().prepare(cfm);
ColumnIdentifier to = entry.getValue().prepare(cfm);
ColumnIdentifier to = entry.getValue();
cfm.renameColumn(from, to);
// If the view includes a renamed column, it must be renamed in the view table and the definition.
@ -274,7 +288,7 @@ public class AlterTableStatement extends SchemaAlteringStatement
ViewDefinition viewCopy = view.copy();
ColumnIdentifier viewFrom = entry.getKey().prepare(viewCopy.metadata);
ColumnIdentifier viewTo = entry.getValue().prepare(viewCopy.metadata);
ColumnIdentifier viewTo = entry.getValue();
viewCopy.renameColumn(viewFrom, viewTo);
if (viewUpdates == null)
@ -283,6 +297,8 @@ public class AlterTableStatement extends SchemaAlteringStatement
}
}
break;
default:
throw new InvalidRequestException("Can not alter table: unknown option type " + oType);
}
MigrationManager.announceColumnFamilyUpdate(cfm, viewUpdates, isLocalOnly);

View File

@ -31,7 +31,7 @@ import org.apache.cassandra.transport.messages.ResultMessage;
public abstract class AuthenticationStatement extends ParsedStatement implements CQLStatement
{
@Override
public Prepared prepare()
public Prepared prepare(ClientState clientState)
{
return new Prepared(this);
}

View File

@ -32,7 +32,7 @@ import org.apache.cassandra.transport.messages.ResultMessage;
public abstract class AuthorizationStatement extends ParsedStatement implements CQLStatement
{
@Override
public Prepared prepare()
public Prepared prepare(ClientState clientState)
{
return new Prepared(this);
}

View File

@ -516,7 +516,7 @@ public class BatchStatement implements CQLStatement
statement.prepareKeyspace(state);
}
public ParsedStatement.Prepared prepare() throws InvalidRequestException
public ParsedStatement.Prepared prepare(ClientState clientState) throws InvalidRequestException
{
VariableSpecifications boundNames = getBoundVariables();
@ -537,7 +537,7 @@ public class BatchStatement implements CQLStatement
haveMultipleCFs = !firstKS.equals(parsed.keyspace()) || !firstCF.equals(parsed.columnFamily());
}
statements.add(parsed.prepare(boundNames));
statements.add(parsed.prepare(boundNames, clientState));
}
Attributes prepAttrs = attrs.prepare("[batch]", "[batch]");

View File

@ -78,7 +78,7 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement
this.ifNotExists = ifNotExists;
}
public Prepared prepare()
public Prepared prepare(ClientState clientState)
{
argTypes = new ArrayList<>(argRawTypes.size());
for (CQL3Type.Raw rawType : argRawTypes)
@ -136,7 +136,7 @@ public final class CreateAggregateStatement extends SchemaAlteringStatement
throw new InvalidRequestException("INITCOND must not be empty for all types except TEXT, ASCII, BLOB");
}
return super.prepare();
return super.prepare(clientState);
}
private AbstractType<?> prepareType(String typeName, CQL3Type.Raw rawType)

View File

@ -76,7 +76,7 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement
this.ifNotExists = ifNotExists;
}
public Prepared prepare() throws InvalidRequestException
public Prepared prepare(ClientState clientState) throws InvalidRequestException
{
if (new HashSet<>(argNames).size() != argNames.size())
throw new InvalidRequestException(String.format("duplicate argument names for given function %s with argument names %s",
@ -87,7 +87,7 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement
argTypes.add(prepareType("arguments", rawType));
returnType = prepareType("return type", rawReturnType);
return super.prepare();
return super.prepare(clientState);
}
public void prepareKeyspace(ClientState state) throws InvalidRequestException

View File

@ -103,8 +103,13 @@ public class CreateIndexStatement extends SchemaAlteringStatement
throw new InvalidRequestException("No column definition found for column " + target.column);
// TODO: we could lift that limitation
if (cfm.isCompactTable() && cd.isPrimaryKeyColumn())
throw new InvalidRequestException("Secondary indexes are not supported on PRIMARY KEY columns in COMPACT STORAGE tables");
if (cfm.isCompactTable())
{
if (cd.isPrimaryKeyColumn())
throw new InvalidRequestException("Secondary indexes are not supported on PRIMARY KEY columns in COMPACT STORAGE tables");
if (cfm.compactValueColumn().equals(cd))
throw new InvalidRequestException("Secondary indexes are not supported on compact value column of COMPACT STORAGE tables");
}
// It would be possible to support 2ndary index on static columns (but not without modifications of at least ExtendedFilter and
// CompositesIndex) and maybe we should, but that means a query like:

View File

@ -191,7 +191,7 @@ public class CreateTableStatement extends SchemaAlteringStatement
/**
* Transform this raw statement into a CreateTableStatement.
*/
public ParsedStatement.Prepared prepare() throws RequestValidationException
public ParsedStatement.Prepared prepare(ClientState clientState) throws RequestValidationException
{
KeyspaceMetadata ksm = Schema.instance.getKSMetaData(keyspace());
if (ksm == null)

View File

@ -214,7 +214,7 @@ public class CreateViewStatement extends SchemaAlteringStatement
rawSelect.prepareKeyspace(state);
rawSelect.setBoundVariables(getBoundVariables());
ParsedStatement.Prepared prepared = rawSelect.prepare(true);
ParsedStatement.Prepared prepared = rawSelect.prepare(true, queryState.getClientState());
SelectStatement select = (SelectStatement) prepared.statement;
StatementRestrictions restrictions = select.getRestrictions();

View File

@ -63,7 +63,7 @@ public final class DropFunctionStatement extends SchemaAlteringStatement
}
@Override
public Prepared prepare() throws InvalidRequestException
public Prepared prepare(ClientState clientState) throws InvalidRequestException
{
if (Schema.instance.getKSMetaData(functionName.keyspace) != null)
{
@ -82,7 +82,7 @@ public final class DropFunctionStatement extends SchemaAlteringStatement
}
}
return super.prepare();
return super.prepare(clientState);
}
@Override

View File

@ -793,17 +793,16 @@ public abstract class ModificationStatement implements CQLStatement
this.ifExists = ifExists;
}
public ParsedStatement.Prepared prepare()
public ParsedStatement.Prepared prepare(ClientState clientState)
{
VariableSpecifications boundNames = getBoundVariables();
ModificationStatement statement = prepare(boundNames);
CFMetaData cfm = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
return new ParsedStatement.Prepared(statement, boundNames, boundNames.getPartitionKeyBindIndexes(cfm));
ModificationStatement statement = prepare(boundNames, clientState);
return new ParsedStatement.Prepared(statement, boundNames, boundNames.getPartitionKeyBindIndexes(statement.cfm));
}
public ModificationStatement prepare(VariableSpecifications boundNames)
public ModificationStatement prepare(VariableSpecifications boundNames, ClientState clientState)
{
CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
CFMetaData metadata = ThriftValidation.validateColumnFamilyWithCompactMode(keyspace(), columnFamily(), clientState.isNoCompactMode());
Attributes preparedAttributes = attrs.prepare(keyspace(), columnFamily());
preparedAttributes.collectMarkerSpecification(boundNames);

View File

@ -23,6 +23,7 @@ import java.util.List;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.exceptions.RequestValidationException;
import org.apache.cassandra.service.ClientState;
public abstract class ParsedStatement
{
@ -44,7 +45,7 @@ public abstract class ParsedStatement
this.variables = variables;
}
public abstract Prepared prepare() throws RequestValidationException;
public abstract Prepared prepare(ClientState clientState) throws RequestValidationException;
public static class Prepared
{

View File

@ -18,6 +18,8 @@
package org.apache.cassandra.cql3.statements;
import org.apache.cassandra.auth.AuthenticatedUser;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.cql3.CFName;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.QueryOptions;
@ -25,9 +27,12 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.RequestValidationException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.thrift.ThriftValidation;
import org.apache.cassandra.transport.Event;
import org.apache.cassandra.transport.messages.ResultMessage;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
/**
* Abstract class for statements that alter the schema.
*/
@ -60,8 +65,33 @@ public abstract class SchemaAlteringStatement extends CFStatement implements CQL
}
@Override
public Prepared prepare()
public Prepared prepare(ClientState clientState)
{
// We don't allow schema changes in no-compact mode on compact tables because it feels like unnecessary
// complication: applying the change on the non compact version of the table might be unsafe (the table is
// still compact in general), and applying it to the compact version in a no-compact connection feels
// confusing/unintuitive. If user want to alter the compact version, they can simply do so in a normal
// connection; if they want to alter the non-compact version, they should finish their transition and properly
// DROP COMPACT STORAGE on the table before doing so.
if (isColumnFamilyLevel && clientState.isNoCompactMode())
{
CFMetaData table = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
if (table.isCompactTable())
{
throw invalidRequest("Cannot alter schema of compact table %s.%s from a connection in NO-COMPACT mode",
table.ksName, table.cfName);
}
else if (table.isView())
{
CFMetaData baseTable = Schema.instance.getView(table.ksName, table.cfName).baseTableMetadata();
if (baseTable.isCompactTable())
throw new InvalidRequestException(String.format("Cannot ALTER schema of view %s.%s on compact table %s from "
+ "a connection in NO-COMPACT mode",
table.ksName, table.cfName,
baseTable.ksName, baseTable.cfName));
}
}
return new Prepared(this);
}

View File

@ -887,14 +887,14 @@ public class SelectStatement implements CQLStatement
this.limit = limit;
}
public ParsedStatement.Prepared prepare() throws InvalidRequestException
public ParsedStatement.Prepared prepare(ClientState clientState) throws InvalidRequestException
{
return prepare(false);
return prepare(false, clientState);
}
public ParsedStatement.Prepared prepare(boolean forView) throws InvalidRequestException
public ParsedStatement.Prepared prepare(boolean forView, ClientState clientState) throws InvalidRequestException
{
CFMetaData cfm = ThriftValidation.validateColumnFamily(keyspace(), columnFamily());
CFMetaData cfm = ThriftValidation.validateColumnFamilyWithCompactMode(keyspace(), columnFamily(), clientState.isNoCompactMode());
VariableSpecifications boundNames = getBoundVariables();
Selection selection = selectClause.isEmpty()

View File

@ -45,7 +45,7 @@ public class TruncateStatement extends CFStatement implements CQLStatement
return 0;
}
public Prepared prepare() throws InvalidRequestException
public Prepared prepare(ClientState clientState) throws InvalidRequestException
{
return new Prepared(this);
}

View File

@ -39,7 +39,7 @@ public class UseStatement extends ParsedStatement implements CQLStatement
return 0;
}
public Prepared prepare() throws InvalidRequestException
public Prepared prepare(ClientState clientState) throws InvalidRequestException
{
return new Prepared(this);
}

View File

@ -17,33 +17,39 @@
*/
package org.apache.cassandra.db.view;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.collect.Iterables;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.statements.ParsedStatement;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.db.*;
import org.apache.cassandra.config.*;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.pager.QueryPager;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.btree.BTreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.config.ViewDefinition;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.MultiColumnRelation;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.Relation;
import org.apache.cassandra.cql3.SingleColumnRelation;
import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.cql3.statements.ParsedStatement;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.ReadQuery;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.utils.FBUtilities;
/**
* A View copies data from a base table into a view table which can be queried independently from the
* base. Every update which targets the base table must be fed through the {@link ViewManager} to ensure
@ -176,7 +182,7 @@ public class View
ClientState state = ClientState.forInternalCalls();
state.setKeyspace(baseCfs.keyspace.getName());
rawSelect.prepareKeyspace(state);
ParsedStatement.Prepared prepared = rawSelect.prepare(true);
ParsedStatement.Prepared prepared = rawSelect.prepare(true, ClientState.forInternalCalls());
select = (SelectStatement) prepared.statement;
}

View File

@ -197,7 +197,13 @@ public class KeysSearcher extends CassandraIndexSearcher
}
else
{
assert iterator.metadata().isCompactTable();
if (!iterator.metadata().isCompactTable())
{
logger.warn("Non-composite index was used on the table '{}' during the query. Starting from Cassandra 4.0, only " +
"composite indexes will be supported. If compact flags were dropped for this table, drop and re-create " +
"the index.", iterator.metadata().cfName);
}
Row data = iterator.staticRow();
if (index.isStale(data, indexedValue, nowInSec))
{

View File

@ -45,6 +45,7 @@ import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.repair.messages.RepairOption;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.tracing.TraceKeyspace;
@ -385,7 +386,7 @@ public class RepairRunnable extends WrappedRunnable implements ProgressEventNoti
String format = "select event_id, source, activity from %s.%s where session_id = ? and event_id > ? and event_id < ?;";
String query = String.format(format, TraceKeyspace.NAME, TraceKeyspace.EVENTS);
SelectStatement statement = (SelectStatement) QueryProcessor.parseStatement(query).prepare().statement;
SelectStatement statement = (SelectStatement) QueryProcessor.parseStatement(query).prepare(ClientState.forInternalCalls()).statement;
ByteBuffer sessionIdBytes = ByteBufferUtil.bytes(sessionId);
InetAddress source = FBUtilities.getBroadcastAddress();

View File

@ -81,6 +81,12 @@ public class ClientState
private volatile AuthenticatedUser user;
private volatile String keyspace;
/**
* Force Compact Tables to be represented as CQL ones for the current client session (simulates
* ALTER .. DROP COMPACT STORAGE but only for this session)
*/
private volatile boolean noCompactMode;
private static final QueryHandler cqlQueryHandler;
static
{
@ -253,6 +259,16 @@ public class ClientState
keyspace = ks;
}
public void setNoCompactMode()
{
this.noCompactMode = true;
}
public boolean isNoCompactMode()
{
return noCompactMode;
}
/**
* Attempts to login the given user.
*/

View File

@ -104,6 +104,11 @@ public class ThriftValidation
// To be used when the operation should be authorized whether this is a counter CF or not
public static CFMetaData validateColumnFamily(String keyspaceName, String cfName) throws org.apache.cassandra.exceptions.InvalidRequestException
{
return validateColumnFamilyWithCompactMode(keyspaceName, cfName, false);
}
public static CFMetaData validateColumnFamilyWithCompactMode(String keyspaceName, String cfName, boolean noCompactMode) throws org.apache.cassandra.exceptions.InvalidRequestException
{
validateKeyspace(keyspaceName);
if (cfName.isEmpty())
@ -113,7 +118,10 @@ public class ThriftValidation
if (metadata == null)
throw new org.apache.cassandra.exceptions.InvalidRequestException("unconfigured table " + cfName);
return metadata;
if (metadata.isCompactTable() && noCompactMode)
return metadata.asNonCompact();
else
return metadata;
}
/**

View File

@ -35,6 +35,7 @@ public class StartupMessage extends Message.Request
{
public static final String CQL_VERSION = "CQL_VERSION";
public static final String COMPRESSION = "COMPRESSION";
public static final String NO_COMPACT = "NO_COMPACT";
public static final Message.Codec<StartupMessage> codec = new Message.Codec<StartupMessage>()
{
@ -97,6 +98,9 @@ public class StartupMessage extends Message.Request
}
}
if (options.containsKey(NO_COMPACT) && Boolean.parseBoolean(options.get(NO_COMPACT)))
state.getClientState().setNoCompactMode();
if (DatabaseDescriptor.getAuthenticator().requireAuthentication())
return new AuthenticateMessage(DatabaseDescriptor.getAuthenticator().getClass().getName());
else

View File

@ -48,6 +48,8 @@ import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.utils.FBUtilities;
@ -1237,7 +1239,6 @@ public class ViewTest extends CQLTester
catch (Exception e)
{
}
}
@Test
@ -1376,4 +1377,16 @@ public class ViewTest extends CQLTester
assertRows(execute("SELECT k, toJson(listval) from mv"),
row(0, "[[\"a\", \"1\"], [\"b\", \"2\"], [\"c\", \"3\"]]"));
}
@Test(expected = SyntaxException.class)
public void emptyViewNameTest() throws Throwable
{
execute("CREATE MATERIALIZED VIEW \"\" AS SELECT a, b FROM tbl WHERE b IS NOT NULL PRIMARY KEY (b, a)");
}
@Test(expected = SyntaxException.class)
public void emptyBaseTableNameTest() throws Throwable
{
execute("CREATE MATERIALIZED VIEW myview AS SELECT a, b FROM \"\" WHERE b IS NOT NULL PRIMARY KEY (b, a)");
}
}

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.cql3.functions.UDFunction;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.transport.Event;
@ -968,4 +969,28 @@ public class UFTest extends CQLTester
assertRows(execute("SELECT " + fNameICC + "(empty_int) FROM %s"), row(0));
assertRows(execute("SELECT " + fNameICN + "(empty_int) FROM %s"), row(new Object[]{ null }));
}
@Test(expected = SyntaxException.class)
public void testEmptyFunctionName() throws Throwable
{
execute("CREATE FUNCTION IF NOT EXISTS " + KEYSPACE + ".\"\" (arg int)\n" +
" RETURNS NULL ON NULL INPUT\n" +
" RETURNS int\n" +
" LANGUAGE java\n" +
" AS $$\n" +
" return a;\n" +
" $$");
}
@Test(expected = SyntaxException.class)
public void testEmptyArgName() throws Throwable
{
execute("CREATE FUNCTION IF NOT EXISTS " + KEYSPACE + ".myfn (\"\" int)\n" +
" RETURNS NULL ON NULL INPUT\n" +
" RETURNS int\n" +
" LANGUAGE java\n" +
" AS $$\n" +
" return a;\n" +
" $$");
}
}

View File

@ -24,6 +24,7 @@ import org.junit.Test;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.dht.ByteOrderedPartitioner;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.service.StorageService;
public class UserTypesTest extends CQLTester
@ -713,6 +714,25 @@ public class UserTypesTest extends CQLTester
row(1, 1,set(userType(1), userType(1, 1), userType(1, 2), userType(2), userType(2, 1)), 2));
}
@Test(expected = SyntaxException.class)
public void emptyTypeNameTest() throws Throwable
{
execute("CREATE TYPE \"\" (a int, b int)");
}
@Test(expected = SyntaxException.class)
public void emptyFieldNameTest() throws Throwable
{
execute("CREATE TYPE mytype (\"\" int, b int)");
}
@Test(expected = SyntaxException.class)
public void renameColumnToEmpty() throws Throwable
{
String typeName = createType("CREATE TYPE %s (a int, b int)");
execute(String.format("ALTER TYPE %s.%s RENAME b TO \"\"", keyspace(), typeName));
}
private String typeWithKs(String type1)
{
return keyspace() + '.' + type1;

View File

@ -17,6 +17,9 @@
*/
package org.apache.cassandra.cql3.validation.operations;
import org.junit.Assert;
import org.junit.Test;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
@ -24,11 +27,6 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.schema.SchemaKeyspace;
import org.apache.cassandra.transport.Server;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.junit.Assert;
import org.junit.Test;
import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
@ -266,6 +264,13 @@ public class AlterTest extends CQLTester
execute("alter table %s add v int");
}
@Test(expected = SyntaxException.class)
public void renameToEmptyTest() throws Throwable
{
createTable("CREATE TABLE %s (k int, c1 int, v int, PRIMARY KEY (k, c1))");
execute("ALTER TABLE %s RENAME c1 TO \"\"");
}
@Test
// tests CASSANDRA-9565
public void testDoubleWith() throws Throwable

View File

@ -0,0 +1,525 @@
/*
* 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.validation.operations;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.EmptyType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.locator.SimpleStrategy;
import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.CfDef;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnDef;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.IndexType;
import org.apache.cassandra.thrift.KsDef;
import org.apache.cassandra.thrift.Mutation;
import org.apache.cassandra.thrift.SuperColumn;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.thrift.ConsistencyLevel.ONE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class DropCompactStorageThriftTest extends ThriftCQLTester
{
@Test
public void thriftCreatedTableTest() throws Throwable
{
final String KEYSPACE = "thrift_created_table_test_ks";
final String TABLE = "test_table_1";
CfDef cfDef = new CfDef().setDefault_validation_class(Int32Type.instance.toString())
.setKey_validation_class(AsciiType.instance.toString())
.setComparator_type(AsciiType.instance.toString())
.setColumn_metadata(Arrays.asList(new ColumnDef(ByteBufferUtil.bytes("col1"),
AsciiType.instance.toString())
.setIndex_name("col1Index")
.setIndex_type(IndexType.KEYS),
new ColumnDef(ByteBufferUtil.bytes("col2"),
AsciiType.instance.toString())
.setIndex_name("col2Index")
.setIndex_type(IndexType.KEYS)))
.setKeyspace(KEYSPACE)
.setName(TABLE);
KsDef ksDef = new KsDef(KEYSPACE,
SimpleStrategy.class.getName(),
Arrays.asList(cfDef));
ksDef.setStrategy_options(Collections.singletonMap("replication_factor", "1"));
Cassandra.Client client = getClient();
client.system_add_keyspace(ksDef);
client.set_keyspace(KEYSPACE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("col1"), ByteBufferUtil.bytes("val1")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("col2"), ByteBufferUtil.bytes("val2")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("dynamicKey1"), ByteBufferUtil.bytes(100)),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("dynamicKey2"), ByteBufferUtil.bytes(200)),
ONE);
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertColumnType(AsciiType.instance, resultSet, "key");
assertColumnType(AsciiType.instance, resultSet, "column1");
assertColumnType(Int32Type.instance, resultSet, "value");
assertColumnType(AsciiType.instance, resultSet, "col1");
assertColumnType(AsciiType.instance, resultSet, "col2");
assertRows(resultSet,
row("key1", "dynamicKey1", "val1", "val2", 100),
row("key1", "dynamicKey2", "val1", "val2", 200));
}
@Test
public void thriftStaticCompatTableTest() throws Throwable
{
String KEYSPACE = keyspace();
String TABLE = createTable("CREATE TABLE %s (key ascii PRIMARY KEY, val ascii) WITH COMPACT STORAGE");
Cassandra.Client client = getClient();
client.set_keyspace(KEYSPACE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("val"), ByteBufferUtil.bytes("val1")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("dynamicKey1"), ByteBufferUtil.bytes("dynamicValue1")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("dynamicKey2"), ByteBufferUtil.bytes("dynamicValue2")),
ONE);
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertColumnType(AsciiType.instance, resultSet, "key");
assertColumnType(UTF8Type.instance, resultSet, "column1");
assertColumnType(AsciiType.instance, resultSet, "val");
assertColumnType(BytesType.instance, resultSet, "value");
// Values are interpreted as bytes by default:
assertRows(resultSet,
row("key1", "dynamicKey1", "val1", ByteBufferUtil.bytes("dynamicValue1")),
row("key1", "dynamicKey2", "val1", ByteBufferUtil.bytes("dynamicValue2")));
}
@Test
public void testSparseCompactTableIndex() throws Throwable
{
createTable("CREATE TABLE %s (key ascii PRIMARY KEY, val ascii) WITH COMPACT STORAGE");
// Indexes are allowed only on the sparse compact tables
createIndex("CREATE INDEX ON %s(val)");
for (int i = 0; i < 10; i++)
execute("INSERT INTO %s (key, val) VALUES (?, ?)", Integer.toString(i), Integer.toString(i * 10));
alterTable("ALTER TABLE %s DROP COMPACT STORAGE");
assertRows(execute("SELECT * FROM %s WHERE val = '50'"),
row("5", null, "50", null));
assertRows(execute("SELECT * FROM %s WHERE key = '5'"),
row("5", null, "50", null));
}
@Test
public void thriftCompatTableTest() throws Throwable
{
String KEYSPACE = keyspace();
String TABLE = createTable("CREATE TABLE %s (pkey ascii, ckey ascii, PRIMARY KEY (pkey, ckey)) WITH COMPACT STORAGE");
Cassandra.Client client = getClient();
client.set_keyspace(KEYSPACE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("ckeyValue1"), ByteBufferUtil.EMPTY_BYTE_BUFFER),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("ckeyValue2"), ByteBufferUtil.EMPTY_BYTE_BUFFER),
ONE);
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertColumnType(AsciiType.instance, resultSet, "pkey");
assertColumnType(AsciiType.instance, resultSet, "ckey");
assertColumnType(EmptyType.instance, resultSet, "value");
// Value is always empty
assertRows(resultSet,
row("key1", "ckeyValue1", ByteBufferUtil.EMPTY_BYTE_BUFFER),
row("key1", "ckeyValue2", ByteBufferUtil.EMPTY_BYTE_BUFFER));
}
@Test
public void thriftDenseTableTest() throws Throwable
{
String KEYSPACE = keyspace();
String TABLE = createTable("CREATE TABLE %s (pkey text, ckey text, v text, PRIMARY KEY (pkey, ckey)) WITH COMPACT STORAGE");
Cassandra.Client client = getClient();
client.set_keyspace(KEYSPACE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("ckey1"), ByteBufferUtil.bytes("cvalue1")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("ckey2"), ByteBufferUtil.bytes("cvalue2")),
ONE);
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertColumnType(UTF8Type.instance, resultSet, "pkey");
assertColumnType(UTF8Type.instance, resultSet, "ckey");
assertColumnType(UTF8Type.instance, resultSet, "v");
assertRows(resultSet,
row("key1", "ckey1", "cvalue1"),
row("key1", "ckey2", "cvalue2"));
}
@Test
public void thriftTableWithIntKey() throws Throwable
{
final String KEYSPACE = "thrift_table_with_int_key_ks";
final String TABLE = "test_table_1";
ByteBuffer columnName = ByteBufferUtil.bytes("columnname");
CfDef cfDef = new CfDef().setDefault_validation_class(UTF8Type.instance.toString())
.setKey_validation_class(BytesType.instance.toString())
.setComparator_type(BytesType.instance.toString())
.setColumn_metadata(Arrays.asList(new ColumnDef(columnName,
Int32Type.instance.toString())
.setIndex_name("col1Index")
.setIndex_type(IndexType.KEYS)))
.setKeyspace(KEYSPACE)
.setName(TABLE);
KsDef ksDef = new KsDef(KEYSPACE,
SimpleStrategy.class.getName(),
Arrays.asList(cfDef));
ksDef.setStrategy_options(Collections.singletonMap("replication_factor", "1"));
Cassandra.Client client = getClient();
client.system_add_keyspace(ksDef);
client.set_keyspace(KEYSPACE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(columnName, ByteBufferUtil.bytes(100)),
ONE);
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertEquals(resultSet.metadata()
.stream()
.filter((cs) -> cs.name.toString().equals(BytesType.instance.getString(columnName)))
.findFirst()
.get().type,
Int32Type.instance);
assertRows(resultSet,
row(UTF8Type.instance.decompose("key1"), null, 100, null));
}
@Test
public void thriftCompatTableWithSupercolumnsTest() throws Throwable
{
final String KEYSPACE = "thrift_compact_table_with_supercolumns_test";
final String TABLE = "test_table_1";
CfDef cfDef = new CfDef().setColumn_type("Super")
.setSubcomparator_type(Int32Type.instance.toString())
.setComparator_type(AsciiType.instance.toString())
.setDefault_validation_class(AsciiType.instance.toString())
.setKey_validation_class(AsciiType.instance.toString())
.setKeyspace(KEYSPACE)
.setName(TABLE);
KsDef ksDef = new KsDef(KEYSPACE,
SimpleStrategy.class.getName(),
Arrays.asList(cfDef));
ksDef.setStrategy_options(Collections.singletonMap("replication_factor", "1"));
Cassandra.Client client = getClient();
client.system_add_keyspace(ksDef);
client.set_keyspace(KEYSPACE);
Mutation mutation = new Mutation();
ColumnOrSuperColumn csoc = new ColumnOrSuperColumn();
csoc.setSuper_column(getSuperColumnForInsert(ByteBufferUtil.bytes("val1"),
Arrays.asList(getColumnForInsert(ByteBufferUtil.bytes(1), ByteBufferUtil.bytes("value1")),
getColumnForInsert(ByteBufferUtil.bytes(2), ByteBufferUtil.bytes("value2")),
getColumnForInsert(ByteBufferUtil.bytes(3), ByteBufferUtil.bytes("value3")))));
mutation.setColumn_or_supercolumn(csoc);
Mutation mutation2 = new Mutation();
ColumnOrSuperColumn csoc2 = new ColumnOrSuperColumn();
csoc2.setSuper_column(getSuperColumnForInsert(ByteBufferUtil.bytes("val2"),
Arrays.asList(getColumnForInsert(ByteBufferUtil.bytes(4), ByteBufferUtil.bytes("value7")),
getColumnForInsert(ByteBufferUtil.bytes(5), ByteBufferUtil.bytes("value8")),
getColumnForInsert(ByteBufferUtil.bytes(6), ByteBufferUtil.bytes("value9")))));
mutation2.setColumn_or_supercolumn(csoc2);
client.batch_mutate(Collections.singletonMap(ByteBufferUtil.bytes("key1"),
Collections.singletonMap(TABLE, Arrays.asList(mutation, mutation2))),
ONE);
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertColumnType(AsciiType.instance, resultSet, "key");
assertColumnType(AsciiType.instance, resultSet, "column1");
assertColumnType(MapType.getInstance(Int32Type.instance, AsciiType.instance, true), resultSet, "");
assertRows(resultSet,
row("key1", "val1", map(1, "value1", 2, "value2", 3, "value3")),
row("key1", "val2", map(4, "value7", 5, "value8", 6, "value9")));
assertRows(execute(String.format("SELECT \"\" FROM %s.%s;", KEYSPACE, TABLE)),
row(map(1, "value1", 2, "value2", 3, "value3")),
row(map(4, "value7", 5, "value8", 6, "value9")));
assertInvalidMessage("Range deletions are not supported for specific columns",
String.format("DELETE \"\" FROM %s.%s WHERE key=?;", KEYSPACE, TABLE),
"key1");
execute(String.format("TRUNCATE %s.%s;", KEYSPACE, TABLE));
execute(String.format("INSERT INTO %s.%s (key, column1, \"\") VALUES (?, ?, ?);", KEYSPACE, TABLE),
"key3", "val1", map(7, "value7", 8, "value8"));
assertRows(execute(String.format("SELECT \"\" FROM %s.%s;", KEYSPACE, TABLE)),
row(map(7, "value7", 8, "value8")));
}
@Test
public void thriftCreatedTableWithCompositeColumnsTest() throws Throwable
{
final String KEYSPACE = "thrift_created_table_with_composites_test_ks";
final String TABLE = "test_table_1";
CompositeType type = CompositeType.getInstance(AsciiType.instance, AsciiType.instance, AsciiType.instance);
CfDef cfDef = new CfDef().setDefault_validation_class(AsciiType.instance.toString())
.setComparator_type(type.toString())
.setKey_validation_class(AsciiType.instance.toString())
.setKeyspace(KEYSPACE)
.setName(TABLE);
KsDef ksDef = new KsDef(KEYSPACE,
SimpleStrategy.class.getName(),
Arrays.asList(cfDef));
ksDef.setStrategy_options(Collections.singletonMap("replication_factor", "1"));
Cassandra.Client client = getClient();
client.system_add_keyspace(ksDef);
client.set_keyspace(KEYSPACE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(type.decompose("a", "b", "c"), ByteBufferUtil.bytes("val1")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(type.decompose("d", "e", "f"), ByteBufferUtil.bytes("val2")),
ONE);
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertColumnType(AsciiType.instance, resultSet, "key");
assertColumnType(AsciiType.instance, resultSet, "column1");
assertColumnType(AsciiType.instance, resultSet, "column2");
assertColumnType(AsciiType.instance, resultSet, "column3");
assertColumnType(AsciiType.instance, resultSet, "value");
assertRows(resultSet,
row("key1", "a", "b", "c", "val1"),
row("key1", "d", "e", "f", "val2"));
}
@Test
public void compactTableWithoutClusteringKeyTest() throws Throwable
{
String KEYSPACE = keyspace();
String TABLE = createTable("CREATE TABLE %s (pkey text PRIMARY KEY, s1 text, s2 text) WITH COMPACT STORAGE");
Cassandra.Client client = getClient();
client.set_keyspace(KEYSPACE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("ckey1"), ByteBufferUtil.bytes("val1")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("ckey2"), ByteBufferUtil.bytes("val2")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("s1"), ByteBufferUtil.bytes("s1Val")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("s2"), ByteBufferUtil.bytes("s2Val")),
ONE);
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertColumnType(UTF8Type.instance, resultSet, "pkey");
assertColumnType(UTF8Type.instance, resultSet, "s1");
assertColumnType(UTF8Type.instance, resultSet, "s2");
assertColumnType(UTF8Type.instance, resultSet, "column1");
assertColumnType(BytesType.instance, resultSet, "value");
assertRows(resultSet,
row("key1", "ckey1", "s1Val", "s2Val", ByteBufferUtil.bytes("val1")),
row("key1", "ckey2", "s1Val", "s2Val", ByteBufferUtil.bytes("val2")));
}
@Test
public void denseTableTestTest() throws Throwable
{
String KEYSPACE = keyspace();
String TABLE = createTable("CREATE TABLE %s (pkey text PRIMARY KEY, s text) WITH COMPACT STORAGE");
Cassandra.Client client = getClient();
client.set_keyspace(KEYSPACE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("ckey1"), ByteBufferUtil.bytes("val1")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("ckey2"), ByteBufferUtil.bytes("val2")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("s"), ByteBufferUtil.bytes("sval1")),
ONE);
client.insert(UTF8Type.instance.decompose("key1"),
new ColumnParent(TABLE),
getColumnForInsert(ByteBufferUtil.bytes("s"), ByteBufferUtil.bytes("sval2")),
ONE);
// `s` becomes static, `column1` becomes a clustering key, `value` becomes visible
execute(String.format("ALTER TABLE %s.%s DROP COMPACT STORAGE;", KEYSPACE, TABLE));
UntypedResultSet resultSet = execute(String.format("select * from %s.%s",
KEYSPACE, TABLE));
assertColumnType(UTF8Type.instance, resultSet, "pkey");
assertColumnType(UTF8Type.instance, resultSet, "s");
assertColumnType(UTF8Type.instance, resultSet, "column1");
assertColumnType(BytesType.instance, resultSet, "value");
assertRows(resultSet,
row("key1", "ckey1", "sval2", ByteBufferUtil.bytes("val1")),
row("key1", "ckey2", "sval2", ByteBufferUtil.bytes("val2")));
}
private Column getColumnForInsert(ByteBuffer columnName, ByteBuffer value)
{
Column column = new Column();
column.setName(columnName);
column.setValue(value);
column.setTimestamp(System.currentTimeMillis());
return column;
}
private SuperColumn getSuperColumnForInsert(ByteBuffer columnName, List<Column> columns)
{
SuperColumn column = new SuperColumn();
column.setName(columnName);
for (Column c : columns)
column.addToColumns(c);
return column;
}
private static void assertColumnType(AbstractType t, UntypedResultSet resultSet, String columnName)
{
for (ColumnSpecification columnSpecification : resultSet.metadata())
{
if (columnSpecification.name.toString().equals(columnName))
{
assertEquals(t, columnSpecification.type);
return;
}
}
fail(String.format("Could not find a column with name '%s'", columnName));
}
}