Avoid exposing intermediate state while replaying log during startup

Patch by marcuse; reviewed by Sam Tunnicliffe for CASSANDRA-19384
This commit is contained in:
Marcus Eriksson 2024-02-01 10:16:41 +01:00
parent f31d72e875
commit 5d4bcc797a
19 changed files with 103 additions and 76 deletions

View File

@ -32,6 +32,7 @@ import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.cql3.terms.Terms; import org.apache.cassandra.cql3.terms.Terms;
import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
@ -150,7 +151,7 @@ public class FunctionCall extends Term.NonTerminal
public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{ {
Function fun = FunctionResolver.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type); Function fun = FunctionResolver.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type, UserFunctions.getCurrentUserFunctions(name, keyspace));
if (fun == null) if (fun == null)
throw invalidRequest("Unknown function %s called", name); throw invalidRequest("Unknown function %s called", name);
if (fun.isAggregate()) if (fun.isAggregate())
@ -194,7 +195,7 @@ public class FunctionCall extends Term.NonTerminal
// later with a more helpful error message that if we were to return false here. // later with a more helpful error message that if we were to return false here.
try try
{ {
Function fun = FunctionResolver.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type); Function fun = FunctionResolver.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type, UserFunctions.getCurrentUserFunctions(name, keyspace));
// Because the return type of functions built by factories is not fixed but depending on the types of // Because the return type of functions built by factories is not fixed but depending on the types of
// their arguments, we'll always get EXACT_MATCH. To handle potentially ambiguous function calls with // their arguments, we'll always get EXACT_MATCH. To handle potentially ambiguous function calls with
@ -221,7 +222,7 @@ public class FunctionCall extends Term.NonTerminal
{ {
try try
{ {
Function fun = FunctionResolver.get(keyspace, name, terms, null, null, null); Function fun = FunctionResolver.get(keyspace, name, terms, null, null, null, UserFunctions.getCurrentUserFunctions(name, keyspace));
return fun == null ? null : fun.returnType(); return fun == null ? null : fun.returnType();
} }
catch (InvalidRequestException e) catch (InvalidRequestException e)

View File

@ -29,7 +29,6 @@ import org.apache.cassandra.cql3.terms.Marker;
import org.apache.cassandra.cql3.AssignmentTestable; import org.apache.cassandra.cql3.AssignmentTestable;
import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.UserFunctions; import org.apache.cassandra.schema.UserFunctions;
@ -51,27 +50,6 @@ public final class FunctionResolver
fun.argTypes().get(i)); fun.argTypes().get(i));
} }
/**
* @param keyspace the current keyspace
* @param name the name of the function
* @param providedArgs the arguments provided for the function call
* @param receiverKeyspace the receiver's keyspace
* @param receiverTable the receiver's table
* @param receiverType if the receiver type is known (during inserts, for example), this should be the type of
* the receiver
*/
@Nullable
public static Function get(String keyspace,
FunctionName name,
List<? extends AssignmentTestable> providedArgs,
String receiverKeyspace,
String receiverTable,
AbstractType<?> receiverType)
throws InvalidRequestException
{
return get(keyspace, name, providedArgs, receiverKeyspace, receiverTable, receiverType, UserFunctions.none());
}
/** /**
* @param keyspace the current keyspace * @param keyspace the current keyspace
* @param name the name of the function * @param name the name of the function
@ -123,7 +101,6 @@ public final class FunctionResolver
{ {
// function name is fully qualified (keyspace + name) // function name is fully qualified (keyspace + name)
candidates.addAll(functions.get(name)); candidates.addAll(functions.get(name));
candidates.addAll(Schema.instance.getUserFunctions(name));
candidates.addAll(NativeFunctions.instance.getFunctions(name)); candidates.addAll(NativeFunctions.instance.getFunctions(name));
candidates.addAll(NativeFunctions.instance.getFactories(name).stream() candidates.addAll(NativeFunctions.instance.getFactories(name).stream()
.map(f -> f.getOrCreateFunction(providedArgs, receiverType, receiverKeyspace, receiverTable)) .map(f -> f.getOrCreateFunction(providedArgs, receiverType, receiverKeyspace, receiverTable))
@ -136,7 +113,6 @@ public final class FunctionResolver
// add 'current keyspace' candidates // add 'current keyspace' candidates
FunctionName userName = new FunctionName(keyspace, name.name); FunctionName userName = new FunctionName(keyspace, name.name);
candidates.addAll(functions.get(userName)); candidates.addAll(functions.get(userName));
candidates.addAll(Schema.instance.getUserFunctions(userName));
// add 'SYSTEM' (native) candidates // add 'SYSTEM' (native) candidates
FunctionName nativeName = name.asNativeFunction(); FunctionName nativeName = name.asNativeFunction();
candidates.addAll(NativeFunctions.instance.getFunctions(nativeName)); candidates.addAll(NativeFunctions.instance.getFunctions(nativeName));

View File

@ -124,7 +124,8 @@ public class ColumnMask
.add(reversed) .add(reversed)
.addAll(partialArgumentTypes()) .addAll(partialArgumentTypes())
.build(); .build();
Function newFunction = FunctionResolver.get(function.name().keyspace, function.name(), args, null, null, null);
Function newFunction = FunctionResolver.get(function.name().keyspace, function.name(), args, null, null, null, UserFunctions.getCurrentUserFunctions(function.name()));
assert newFunction != null; assert newFunction != null;
return new ColumnMask((ScalarFunction) newFunction, partialArgumentValues); return new ColumnMask((ScalarFunction) newFunction, partialArgumentValues);
} }
@ -215,20 +216,20 @@ public class ColumnMask
this.rawPartialArguments = rawPartialArguments; this.rawPartialArguments = rawPartialArguments;
} }
public ColumnMask prepare(String keyspace, String table, ColumnIdentifier column, AbstractType<?> type) public ColumnMask prepare(String keyspace, String table, ColumnIdentifier column, AbstractType<?> type, UserFunctions functions)
{ {
ScalarFunction function = findMaskingFunction(keyspace, table, column, type); ScalarFunction function = findMaskingFunction(keyspace, table, column, type, functions);
ByteBuffer[] partialArguments = preparePartialArguments(keyspace, function); ByteBuffer[] partialArguments = preparePartialArguments(keyspace, function);
return new ColumnMask(function, partialArguments); return new ColumnMask(function, partialArguments);
} }
private ScalarFunction findMaskingFunction(String keyspace, String table, ColumnIdentifier column, AbstractType<?> type) private ScalarFunction findMaskingFunction(String keyspace, String table, ColumnIdentifier column, AbstractType<?> type, UserFunctions functions)
{ {
List<AssignmentTestable> args = new ArrayList<>(rawPartialArguments.size() + 1); List<AssignmentTestable> args = new ArrayList<>(rawPartialArguments.size() + 1);
args.add(type); args.add(type);
args.addAll(rawPartialArguments); args.addAll(rawPartialArguments);
Function function = FunctionResolver.get(keyspace, name, args, keyspace, table, type); Function function = FunctionResolver.get(keyspace, name, args, keyspace, table, type, functions);
if (function == null) if (function == null)
throw invalidRequest("Unable to find masking function for %s, " + throw invalidRequest("Unable to find masking function for %s, " +

View File

@ -31,6 +31,7 @@ import org.apache.cassandra.cql3.functions.Arguments;
import org.apache.cassandra.cql3.functions.FunctionResolver; import org.apache.cassandra.cql3.functions.FunctionResolver;
import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.ColumnSpecification;
@ -68,7 +69,7 @@ abstract class AbstractFunctionSelector<T extends Function> extends Selector
argTypes.add(readType(metadata, in)); argTypes.add(readType(metadata, in));
} }
Function function = FunctionResolver.get(metadata.keyspace, name, argTypes, metadata.keyspace, metadata.name, null); Function function = FunctionResolver.get(metadata.keyspace, name, argTypes, metadata.keyspace, metadata.name, null, UserFunctions.getCurrentUserFunctions(name, metadata.keyspace));
if (function == null) if (function == null)
throw new IOException(String.format("Unknown serialized function %s(%s)", throw new IOException(String.format("Unknown serialized function %s(%s)",

View File

@ -37,6 +37,7 @@ import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.cql3.selection.SelectorFactories.createFactoriesAndCollectColumnDefinitions; import static org.apache.cassandra.cql3.selection.SelectorFactories.createFactoriesAndCollectColumnDefinitions;
@ -419,8 +420,7 @@ public interface Selectable extends AssignmentTestable
name = AggregateFcts.countRowsFunction.name(); name = AggregateFcts.countRowsFunction.name();
preparedArgs = Collections.emptyList(); preparedArgs = Collections.emptyList();
} }
Function fun = FunctionResolver.get(table.keyspace, name, preparedArgs, table.keyspace, table.name, null, UserFunctions.getCurrentUserFunctions(name, table.keyspace));
Function fun = FunctionResolver.get(table.keyspace, name, preparedArgs, table.keyspace, table.name, null);
if (fun == null) if (fun == null)
throw new InvalidRequestException(String.format("Unknown function '%s'", functionName)); throw new InvalidRequestException(String.format("Unknown function '%s'", functionName));
@ -462,7 +462,7 @@ public interface Selectable extends AssignmentTestable
return factory; return factory;
FunctionName name = FunctionName.nativeFunction(CastFcts.getFunctionName(type)); FunctionName name = FunctionName.nativeFunction(CastFcts.getFunctionName(type));
Function fun = FunctionResolver.get(table.keyspace, name, args, table.keyspace, table.name, null); Function fun = FunctionResolver.get(table.keyspace, name, args, table.keyspace, table.name, null, UserFunctions.getCurrentUserFunctions(name, table.keyspace));
if (fun == null) if (fun == null)
{ {

View File

@ -60,6 +60,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.MemtableParams; import org.apache.cassandra.schema.MemtableParams;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableParams; import org.apache.cassandra.schema.TableParams;
import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.schema.ViewMetadata; import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.schema.Views; import org.apache.cassandra.schema.Views;
import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientState;
@ -120,7 +121,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
if (table.isView()) if (table.isView())
throw ire("Cannot use ALTER TABLE on a materialized view; use ALTER MATERIALIZED VIEW instead"); throw ire("Cannot use ALTER TABLE on a materialized view; use ALTER MATERIALIZED VIEW instead");
return schema.withAddedOrUpdated(apply(metadata.nextEpoch(), keyspace, table)); return schema.withAddedOrUpdated(apply(metadata.nextEpoch(), keyspace, table, metadata));
} }
SchemaChange schemaChangeEvent(KeyspacesDiff diff) SchemaChange schemaChangeEvent(KeyspacesDiff diff)
@ -144,7 +145,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
return format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName); return format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName);
} }
abstract KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table); abstract KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata);
/** /**
* {@code ALTER TABLE [IF EXISTS] <table> ALTER <column> TYPE <newtype>;} * {@code ALTER TABLE [IF EXISTS] <table> ALTER <column> TYPE <newtype>;}
@ -158,7 +159,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
super(keyspaceName, tableName, ifTableExists); super(keyspaceName, tableName, ifTableExists);
} }
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{ {
throw ire("Altering column types is no longer supported"); throw ire("Altering column types is no longer supported");
} }
@ -198,7 +199,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
} }
@Override @Override
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{ {
ColumnMetadata column = table.getColumn(columnName); ColumnMetadata column = table.getColumn(columnName);
@ -210,8 +211,14 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
return keyspace; return keyspace;
} }
// add all user functions to be able to give a good error message to the user if the alter references
// a function from another keyspace
UserFunctions.Builder ufBuilder = UserFunctions.builder();
for (KeyspaceMetadata ksm : metadata.schema.getKeyspaces())
ufBuilder.add(ksm.userFunctions);
ColumnMask oldMask = table.getColumn(columnName).getMask(); ColumnMask oldMask = table.getColumn(columnName).getMask();
ColumnMask newMask = rawMask == null ? null : rawMask.prepare(keyspace.name, table.name, columnName, column.type); ColumnMask newMask = rawMask == null ? null : rawMask.prepare(keyspace.name, table.name, columnName, column.type, ufBuilder.build());
if (Objects.equals(oldMask, newMask)) if (Objects.equals(oldMask, newMask))
return keyspace; return keyspace;
@ -276,7 +283,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
newColumns.forEach(c -> c.type.validate(state, "Column " + c.name)); newColumns.forEach(c -> c.type.validate(state, "Column " + c.name));
} }
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{ {
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state); Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch); TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
@ -302,7 +309,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
ColumnIdentifier name = column.name; ColumnIdentifier name = column.name;
AbstractType<?> type = column.type.prepare(keyspaceName, keyspace.types).getType(); AbstractType<?> type = column.type.prepare(keyspaceName, keyspace.types).getType();
boolean isStatic = column.isStatic; boolean isStatic = column.isStatic;
ColumnMask mask = column.mask == null ? null : column.mask.prepare(keyspaceName, tableName, name, type); ColumnMask mask = column.mask == null ? null : column.mask.prepare(keyspaceName, tableName, name, type, keyspace.userFunctions);
if (null != tableBuilder.getColumn(name)) { if (null != tableBuilder.getColumn(name)) {
if (!ifColumnNotExists) if (!ifColumnNotExists)
@ -413,7 +420,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
this.timestamp = timestamp; this.timestamp = timestamp;
} }
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{ {
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state); Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
TableMetadata.Builder builder = table.unbuild(); TableMetadata.Builder builder = table.unbuild();
@ -475,7 +482,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
this.ifColumnsExists = ifColumnsExists; this.ifColumnsExists = ifColumnsExists;
} }
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{ {
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state); Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch); TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
@ -553,7 +560,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
validateDefaultTimeToLive(attrs.asNewTableParams()); validateDefaultTimeToLive(attrs.asNewTableParams());
} }
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{ {
attrs.validate(); attrs.validate();
@ -597,7 +604,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
super(keyspaceName, tableName, ifTableExists); super(keyspaceName, tableName, ifTableExists);
} }
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table) public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
{ {
if (!DatabaseDescriptor.enableDropCompactStorage()) if (!DatabaseDescriptor.enableDropCompactStorage())
throw new InvalidRequestException("DROP COMPACT STORAGE is disabled. Enable in cassandra.yaml to use."); throw new InvalidRequestException("DROP COMPACT STORAGE is disabled. Enable in cassandra.yaml to use.");

View File

@ -26,9 +26,6 @@ import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.audit.AuditLogContext; import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType; import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.auth.DataResource; import org.apache.cassandra.auth.DataResource;
@ -55,7 +52,6 @@ import static com.google.common.collect.Iterables.concat;
public final class CreateTableStatement extends AlterSchemaStatement public final class CreateTableStatement extends AlterSchemaStatement
{ {
private static final Logger logger = LoggerFactory.getLogger(CreateTableStatement.class);
private final String tableName; private final String tableName;
private final Map<ColumnIdentifier, ColumnProperties.Raw> rawColumns; private final Map<ColumnIdentifier, ColumnProperties.Raw> rawColumns;
@ -110,7 +106,13 @@ public final class CreateTableStatement extends AlterSchemaStatement
throw new AlreadyExistsException(keyspaceName, tableName); throw new AlreadyExistsException(keyspaceName, tableName);
} }
TableMetadata.Builder builder = builder(keyspace.types).epoch(metadata.nextEpoch()); // add all user functions to be able to give a good error message to the user if the alter references
// a function from another keyspace
UserFunctions.Builder ufBuilder = UserFunctions.builder().add();
for (KeyspaceMetadata ksm : schema)
ufBuilder.add(ksm.userFunctions);
TableMetadata.Builder builder = builder(keyspace.types, ufBuilder.build()).epoch(metadata.nextEpoch());
if (!builder.hasId() && !DatabaseDescriptor.useDeterministicTableID()) if (!builder.hasId() && !DatabaseDescriptor.useDeterministicTableID())
builder.id(TableId.get(metadata)); builder.id(TableId.get(metadata));
TableMetadata table = builder.build(); TableMetadata table = builder.build();
@ -189,14 +191,14 @@ public final class CreateTableStatement extends AlterSchemaStatement
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName); return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName);
} }
public TableMetadata.Builder builder(Types types) public TableMetadata.Builder builder(Types types, UserFunctions functions)
{ {
attrs.validate(); attrs.validate();
TableParams params = attrs.asNewTableParams(); TableParams params = attrs.asNewTableParams();
// use a TreeMap to preserve ordering across JDK versions (see CASSANDRA-9492) - important for stable unit tests // use a TreeMap to preserve ordering across JDK versions (see CASSANDRA-9492) - important for stable unit tests
Map<ColumnIdentifier, ColumnProperties> columns = new TreeMap<>(comparing(o -> o.bytes)); Map<ColumnIdentifier, ColumnProperties> columns = new TreeMap<>(comparing(o -> o.bytes));
rawColumns.forEach((column, properties) -> columns.put(column, properties.prepare(keyspaceName, tableName, column, types))); rawColumns.forEach((column, properties) -> columns.put(column, properties.prepare(keyspaceName, tableName, column, types, functions)));
// check for nested non-frozen UDTs or collections in a non-frozen UDT // check for nested non-frozen UDTs or collections in a non-frozen UDT
columns.forEach((column, properties) -> columns.forEach((column, properties) ->
@ -469,7 +471,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
return CQLFragmentParser.parseAny(CqlParser::createTableStatement, cql, "CREATE TABLE") return CQLFragmentParser.parseAny(CqlParser::createTableStatement, cql, "CREATE TABLE")
.keyspace(keyspace) .keyspace(keyspace)
.prepare(null) // works around a messy ClientState/QueryProcessor class init deadlock .prepare(null) // works around a messy ClientState/QueryProcessor class init deadlock
.builder(Types.none()); .builder(Types.none(), UserFunctions.none());
} }
public final static class Raw extends CQLStatement.Raw public final static class Raw extends CQLStatement.Raw
@ -614,11 +616,11 @@ public final class CreateTableStatement extends AlterSchemaStatement
ColumnMask.ensureEnabled(); ColumnMask.ensureEnabled();
} }
public ColumnProperties prepare(String keyspace, String table, ColumnIdentifier column, Types udts) public ColumnProperties prepare(String keyspace, String table, ColumnIdentifier column, Types udts, UserFunctions functions)
{ {
CQL3Type cqlType = rawType.prepare(keyspace, udts); CQL3Type cqlType = rawType.prepare(keyspace, udts);
AbstractType<?> type = cqlType.getType(); AbstractType<?> type = cqlType.getType();
ColumnMask mask = rawMask == null ? null : rawMask.prepare(keyspace, table, column, type); ColumnMask mask = rawMask == null ? null : rawMask.prepare(keyspace, table, column, type, functions);
return new ColumnProperties(type, cqlType, mask); return new ColumnProperties(type, cqlType, mask);
} }
} }

View File

@ -663,7 +663,7 @@ public class CQLSSTableWriter implements Closeable
{ {
Types types = createTypes(keyspaceName); Types types = createTypes(keyspaceName);
Schema.instance.submit(SchemaTransformations.addTypes(types, true)); Schema.instance.submit(SchemaTransformations.addTypes(types, true));
tableMetadata = createTable(types); tableMetadata = createTable(types, ksm.userFunctions);
Schema.instance.submit(SchemaTransformations.addTable(tableMetadata, true)); Schema.instance.submit(SchemaTransformations.addTable(tableMetadata, true));
if (buildIndexes && !indexStatements.isEmpty()) if (buildIndexes && !indexStatements.isEmpty())
@ -772,13 +772,13 @@ public class CQLSSTableWriter implements Closeable
* *
* @param types types this table should be created with * @param types types this table should be created with
*/ */
private TableMetadata createTable(Types types) private TableMetadata createTable(Types types, UserFunctions functions)
{ {
ClientState state = ClientState.forInternalCalls(); ClientState state = ClientState.forInternalCalls();
CreateTableStatement statement = schemaStatement.prepare(state); CreateTableStatement statement = schemaStatement.prepare(state);
statement.validate(ClientState.forInternalCalls()); statement.validate(ClientState.forInternalCalls());
TableMetadata.Builder builder = statement.builder(types); TableMetadata.Builder builder = statement.builder(types, functions);
if (partitioner != null) if (partitioner != null)
builder.partitioner(partitioner); builder.partitioner(partitioner);

View File

@ -31,6 +31,7 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.serialization.UDTAwareMetadataSerializer; import org.apache.cassandra.tcm.serialization.UDTAwareMetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version; import org.apache.cassandra.tcm.serialization.Version;
@ -252,6 +253,22 @@ public final class UserFunctions implements Iterable<UserFunction>
.build(); .build();
} }
public static UserFunctions getCurrentUserFunctions(FunctionName name, String keyspace)
{
KeyspaceMetadata ksm = ClusterMetadata.current().schema.getKeyspaces().getNullable(name.hasKeyspace() ? name.keyspace : keyspace);
UserFunctions userFunctions = UserFunctions.none();
if (ksm != null)
userFunctions = ksm.userFunctions;
return userFunctions;
}
public static UserFunctions getCurrentUserFunctions(FunctionName name)
{
if (!name.hasKeyspace())
return UserFunctions.none();
return getCurrentUserFunctions(name, null);
}
@Override @Override
public boolean equals(Object o) public boolean equals(Object o)
{ {

View File

@ -484,11 +484,11 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
jmxObjectName = "org.apache.cassandra.db:type=StorageService"; jmxObjectName = "org.apache.cassandra.db:type=StorageService";
sstablesTracker = new SSTablesGlobalTracker(DatabaseDescriptor.getSelectedSSTableFormat()); sstablesTracker = new SSTablesGlobalTracker(DatabaseDescriptor.getSelectedSSTableFormat());
registerMBeans();
} }
private void registerMBeans() public void registerMBeans()
{ {
logger.info("Initializing storage service mbean");
MBeanWrapper.instance.registerMBean(this, jmxObjectName); MBeanWrapper.instance.registerMBean(this, jmxObjectName);
MBeanWrapper.instance.registerMBean(StreamManager.instance, StreamManager.OBJECT_NAME); MBeanWrapper.instance.registerMBean(StreamManager.instance, StreamManager.OBJECT_NAME);
} }

View File

@ -142,7 +142,8 @@ import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
{ {
LocalLog.LogSpec logSpec = LocalLog.logSpec() LocalLog.LogSpec logSpec = LocalLog.logSpec()
.withStorage(LogStorage.SystemKeyspace) .withStorage(LogStorage.SystemKeyspace)
.afterReplay(Startup::scrubDataDirectories) .afterReplay(Startup::scrubDataDirectories,
(metadata) -> StorageService.instance.registerMBeans())
.withDefaultListeners(); .withDefaultListeners();
ClusterMetadataService.setInstance(new ClusterMetadataService(new UniformRangePlacement(), ClusterMetadataService.setInstance(new ClusterMetadataService(new UniformRangePlacement(),
wrapProcessor, wrapProcessor,
@ -245,7 +246,8 @@ import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
ClusterMetadata emptyFromSystemTables = emptyWithSchemaFromSystemTables(SystemKeyspace.allKnownDatacenters()); ClusterMetadata emptyFromSystemTables = emptyWithSchemaFromSystemTables(SystemKeyspace.allKnownDatacenters());
LocalLog.LogSpec logSpec = LocalLog.logSpec() LocalLog.LogSpec logSpec = LocalLog.logSpec()
.withInitialState(emptyFromSystemTables) .withInitialState(emptyFromSystemTables)
.afterReplay(Startup::scrubDataDirectories) .afterReplay(Startup::scrubDataDirectories,
(metadata) -> StorageService.instance.registerMBeans())
.withStorage(LogStorage.SystemKeyspace) .withStorage(LogStorage.SystemKeyspace)
.withDefaultListeners(); .withDefaultListeners();
@ -305,6 +307,8 @@ import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
metadata = metadata.forceEpoch(metadata.epoch.nextEpoch()); metadata = metadata.forceEpoch(metadata.epoch.nextEpoch());
ClusterMetadataService.unsetInstance(); ClusterMetadataService.unsetInstance();
LocalLog.LogSpec logSpec = LocalLog.logSpec() LocalLog.LogSpec logSpec = LocalLog.logSpec()
.afterReplay(Startup::scrubDataDirectories,
(_metadata) -> StorageService.instance.registerMBeans())
.withPreviousState(prev) .withPreviousState(prev)
.withInitialState(metadata) .withInitialState(metadata)
.withStorage(LogStorage.SystemKeyspace) .withStorage(LogStorage.SystemKeyspace)

View File

@ -148,7 +148,7 @@ public interface Transformation
public Success success() public Success success()
{ {
throw new IllegalStateException("Can't dereference Success for a rejected execution"); throw new IllegalStateException("Can't dereference Success for a rejected execution: " + code + ": " + reason);
} }
public Rejected rejected() public Rejected rejected()

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.tcm.log;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
@ -33,6 +34,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -107,7 +109,7 @@ public abstract class LocalLog implements Closeable
{ {
private ClusterMetadata initial; private ClusterMetadata initial;
private ClusterMetadata prev; private ClusterMetadata prev;
private Startup.AfterReplay afterReplay = (metadata) -> {}; private List<Startup.AfterReplay> afterReplay = Collections.emptyList();
private LogStorage storage = LogStorage.None; private LogStorage storage = LogStorage.None;
private boolean async = true; private boolean async = true;
private boolean defaultListeners = false; private boolean defaultListeners = false;
@ -202,9 +204,9 @@ public abstract class LocalLog implements Closeable
return this; return this;
} }
public LogSpec afterReplay(Startup.AfterReplay afterReplay) public LogSpec afterReplay(Startup.AfterReplay ... afterReplay)
{ {
this.afterReplay = afterReplay; this.afterReplay = Lists.newArrayList(afterReplay);
return this; return this;
} }
@ -541,7 +543,7 @@ public abstract class LocalLog implements Closeable
if (replayComplete.get()) if (replayComplete.get())
throw new IllegalStateException("Can only replay persisted once."); throw new IllegalStateException("Can only replay persisted once.");
LogState logState = storage.getPersistedLogState(); LogState logState = storage.getPersistedLogState();
append(logState); append(logState.flatten());
return waitForHighestConsecutive(); return waitForHighestConsecutive();
} }
@ -612,7 +614,8 @@ public abstract class LocalLog implements Closeable
public ClusterMetadata ready() throws StartupException public ClusterMetadata ready() throws StartupException
{ {
ClusterMetadata metadata = replayPersisted(); ClusterMetadata metadata = replayPersisted();
spec.afterReplay.accept(metadata); for (Startup.AfterReplay ar : spec.afterReplay)
ar.accept(metadata);
logger.info("Marking LocalLog ready at epoch {}", metadata.epoch); logger.info("Marking LocalLog ready at epoch {}", metadata.epoch);
if (!replayComplete.compareAndSet(false, true)) if (!replayComplete.compareAndSet(false, true))
@ -634,7 +637,6 @@ public abstract class LocalLog implements Closeable
logger.info("Notifying all registered listeners of both pre and post commit event"); logger.info("Notifying all registered listeners of both pre and post commit event");
notifyListeners(spec.prev); notifyListeners(spec.prev);
return metadata; return metadata;
} }

View File

@ -95,6 +95,19 @@ public class LogState
return new LogState(baseState, ImmutableList.of()); return new LogState(baseState, ImmutableList.of());
} }
public LogState flatten()
{
if (baseState == null && entries.isEmpty())
return this;
ClusterMetadata metadata = baseState;
if (metadata == null)
metadata = new ClusterMetadata(DatabaseDescriptor.getPartitioner());
for (Entry entry : entries)
metadata = entry.transform.execute(metadata).success().metadata;
return LogState.make(metadata);
}
public boolean isEmpty() public boolean isEmpty()
{ {
return baseState == null && entries.isEmpty(); return baseState == null && entries.isEmpty();

View File

@ -418,6 +418,7 @@ public abstract class CQLTester
ServerTestUtils.daemonInitialization(); ServerTestUtils.daemonInitialization();
if (ROW_CACHE_SIZE_IN_MIB > 0) if (ROW_CACHE_SIZE_IN_MIB > 0)
DatabaseDescriptor.setRowCacheSizeInMiB(ROW_CACHE_SIZE_IN_MIB); DatabaseDescriptor.setRowCacheSizeInMiB(ROW_CACHE_SIZE_IN_MIB);
StorageService.instance.registerMBeans();
StorageService.instance.setPartitionerUnsafe(Murmur3Partitioner.instance); StorageService.instance.setPartitionerUnsafe(Murmur3Partitioner.instance);
// Once per-JVM is enough // Once per-JVM is enough
prepareServer(); prepareServer();

View File

@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils;
import org.junit.Test; import org.junit.Test;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.UserFunctions;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
public class NativeFunctionsTest public class NativeFunctionsTest
@ -144,7 +145,8 @@ public class NativeFunctionsTest
function.argTypes, function.argTypes,
null, null,
null, null,
function.returnType); function.returnType,
UserFunctions.none());
Assertions.assertThat(newFunction).isNotNull(); Assertions.assertThat(newFunction).isNotNull();
Assertions.assertThat(function).isNotEqualTo(newFunction); Assertions.assertThat(function).isNotEqualTo(newFunction);

View File

@ -41,7 +41,6 @@ import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaKeyspace; import org.apache.cassandra.schema.SchemaKeyspace;
import org.apache.cassandra.schema.SchemaKeyspaceTables; import org.apache.cassandra.schema.SchemaKeyspaceTables;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.UserFunctions;
import static java.lang.String.format; import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -133,7 +132,7 @@ public class ColumnMaskTester extends CQLTester
UntypedResultSet columnRows = execute("SELECT * FROM system_schema.columns " + UntypedResultSet columnRows = execute("SELECT * FROM system_schema.columns " +
"WHERE keyspace_name = ? AND table_name = ? AND column_name = ?", "WHERE keyspace_name = ? AND table_name = ? AND column_name = ?",
KEYSPACE, table, column); KEYSPACE, table, column);
ColumnMetadata persistedColumn = SchemaKeyspace.createColumnFromRow(columnRows.one(), keyspaceMetadata.types, UserFunctions.none()); ColumnMetadata persistedColumn = SchemaKeyspace.createColumnFromRow(columnRows.one(), keyspaceMetadata.types, keyspaceMetadata.userFunctions);
// Verify the column mask in the persisted schema // Verify the column mask in the persisted schema
ColumnMask savedMask = persistedColumn.getMask(); ColumnMask savedMask = persistedColumn.getMask();

View File

@ -39,6 +39,7 @@ import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef; import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.schema.Types; import org.apache.cassandra.schema.Types;
import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.ClusterMetadataService;
@ -86,7 +87,7 @@ public class ColumnFamilyStoreClientModeTest
ClientState state = ClientState.forInternalCalls(KEYSPACE); ClientState state = ClientState.forInternalCalls(KEYSPACE);
CreateTableStatement statement = schemaStatement.prepare(state); CreateTableStatement statement = schemaStatement.prepare(state);
statement.validate(state); statement.validate(state);
TableMetadata tableMetadata = statement.builder(types) TableMetadata tableMetadata = statement.builder(types, UserFunctions.none())
.id(TableId.fromUUID(UUID.nameUUIDFromBytes(ArrayUtils.addAll(schemaStatement.keyspace().getBytes(), schemaStatement.table().getBytes())))) .id(TableId.fromUUID(UUID.nameUUIDFromBytes(ArrayUtils.addAll(schemaStatement.keyspace().getBytes(), schemaStatement.table().getBytes()))))
.partitioner(Murmur3Partitioner.instance) .partitioner(Murmur3Partitioner.instance)
.build(); .build();

View File

@ -636,7 +636,7 @@ public class StressCQLSSTableWriter implements Closeable
statement.validate(state); statement.validate(state);
//Build metadata with a portable tableId //Build metadata with a portable tableId
tableMetadata = statement.builder(ksm.types) tableMetadata = statement.builder(ksm.types, ksm.userFunctions)
.id(deterministicId(schemaStatement.keyspace(), schemaStatement.table())) .id(deterministicId(schemaStatement.keyspace(), schemaStatement.table()))
.build(); .build();
Tables tables = Tables.of(tableMetadata); Tables tables = Tables.of(tableMetadata);