Search in sources :

Example 1 with WhereClause

use of org.apache.cassandra.cql3.WhereClause in project cassandra by apache.

the class CreateViewStatement method announceMigration.

public Event.SchemaChange announceMigration(QueryState queryState, boolean isLocalOnly) throws RequestValidationException {
    // We need to make sure that:
    //  - primary key includes all columns in base table's primary key
    //  - make sure that the select statement does not have anything other than columns
    //    and their names match the base table's names
    //  - make sure that primary key does not include any collections
    //  - make sure there is no where clause in the select statement
    //  - make sure there is not currently a table or view
    //  - make sure baseTable gcGraceSeconds > 0
    properties.validate();
    if (properties.useCompactStorage)
        throw new InvalidRequestException("Cannot use 'COMPACT STORAGE' when defining a materialized view");
    // specific replica would break
    if (!baseName.getKeyspace().equals(keyspace()))
        throw new InvalidRequestException("Cannot create a materialized view on a table in a separate keyspace");
    TableMetadata metadata = Schema.instance.validateTable(baseName.getKeyspace(), baseName.getColumnFamily());
    if (metadata.isCounter())
        throw new InvalidRequestException("Materialized views are not supported on counter tables");
    if (metadata.isView())
        throw new InvalidRequestException("Materialized views cannot be created against other materialized views");
    if (metadata.params.gcGraceSeconds == 0) {
        throw new InvalidRequestException(String.format("Cannot create materialized view '%s' for base table " + "'%s' with gc_grace_seconds of 0, since this value is " + "used to TTL undelivered updates. Setting gc_grace_seconds" + " too low might cause undelivered updates to expire " + "before being replayed.", cfName.getColumnFamily(), baseName.getColumnFamily()));
    }
    Set<ColumnIdentifier> included = Sets.newHashSetWithExpectedSize(selectClause.size());
    for (RawSelector selector : selectClause) {
        Selectable.Raw selectable = selector.selectable;
        if (selectable instanceof Selectable.WithFieldSelection.Raw)
            throw new InvalidRequestException("Cannot select out a part of type when defining a materialized view");
        if (selectable instanceof Selectable.WithFunction.Raw)
            throw new InvalidRequestException("Cannot use function when defining a materialized view");
        if (selectable instanceof Selectable.WritetimeOrTTL.Raw)
            throw new InvalidRequestException("Cannot use function when defining a materialized view");
        if (selector.alias != null)
            throw new InvalidRequestException("Cannot use alias when defining a materialized view");
        Selectable s = selectable.prepare(metadata);
        if (s instanceof Term.Raw)
            throw new InvalidRequestException("Cannot use terms in selection when defining a materialized view");
        ColumnMetadata cdef = (ColumnMetadata) s;
        included.add(cdef.name);
    }
    Set<ColumnMetadata.Raw> targetPrimaryKeys = new HashSet<>();
    for (ColumnMetadata.Raw identifier : Iterables.concat(partitionKeys, clusteringKeys)) {
        if (!targetPrimaryKeys.add(identifier))
            throw new InvalidRequestException("Duplicate entry found in PRIMARY KEY: " + identifier);
        ColumnMetadata cdef = identifier.prepare(metadata);
        if (cdef.type.isMultiCell())
            throw new InvalidRequestException(String.format("Cannot use MultiCell column '%s' in PRIMARY KEY of materialized view", identifier));
        if (cdef.isStatic())
            throw new InvalidRequestException(String.format("Cannot use Static column '%s' in PRIMARY KEY of materialized view", identifier));
        if (cdef.type instanceof DurationType)
            throw new InvalidRequestException(String.format("Cannot use Duration column '%s' in PRIMARY KEY of materialized view", identifier));
    }
    // build the select statement
    Map<ColumnMetadata.Raw, Boolean> orderings = Collections.emptyMap();
    List<ColumnMetadata.Raw> groups = Collections.emptyList();
    SelectStatement.Parameters parameters = new SelectStatement.Parameters(orderings, groups, false, true, false);
    SelectStatement.RawStatement rawSelect = new SelectStatement.RawStatement(baseName, parameters, selectClause, whereClause, null, null);
    ClientState state = ClientState.forInternalCalls();
    state.setKeyspace(keyspace());
    rawSelect.prepareKeyspace(state);
    rawSelect.setBoundVariables(getBoundVariables());
    ParsedStatement.Prepared prepared = rawSelect.prepare(true);
    SelectStatement select = (SelectStatement) prepared.statement;
    StatementRestrictions restrictions = select.getRestrictions();
    if (!prepared.boundNames.isEmpty())
        throw new InvalidRequestException("Cannot use query parameters in CREATE MATERIALIZED VIEW statements");
    String whereClauseText = View.relationsToWhereClause(whereClause.relations);
    Set<ColumnIdentifier> basePrimaryKeyCols = new HashSet<>();
    for (ColumnMetadata definition : Iterables.concat(metadata.partitionKeyColumns(), metadata.clusteringColumns())) basePrimaryKeyCols.add(definition.name);
    List<ColumnIdentifier> targetClusteringColumns = new ArrayList<>();
    List<ColumnIdentifier> targetPartitionKeys = new ArrayList<>();
    // This is only used as an intermediate state; this is to catch whether multiple non-PK columns are used
    boolean hasNonPKColumn = false;
    for (ColumnMetadata.Raw raw : partitionKeys) hasNonPKColumn |= getColumnIdentifier(metadata, basePrimaryKeyCols, hasNonPKColumn, raw, targetPartitionKeys, restrictions);
    for (ColumnMetadata.Raw raw : clusteringKeys) hasNonPKColumn |= getColumnIdentifier(metadata, basePrimaryKeyCols, hasNonPKColumn, raw, targetClusteringColumns, restrictions);
    // We need to include all of the primary key columns from the base table in order to make sure that we do not
    // overwrite values in the view. We cannot support "collapsing" the base table into a smaller number of rows in
    // the view because if we need to generate a tombstone, we have no way of knowing which value is currently being
    // used in the view and whether or not to generate a tombstone. In order to not surprise our users, we require
    // that they include all of the columns. We provide them with a list of all of the columns left to include.
    boolean missingClusteringColumns = false;
    StringBuilder columnNames = new StringBuilder();
    List<ColumnIdentifier> includedColumns = new ArrayList<>();
    for (ColumnMetadata def : metadata.columns()) {
        ColumnIdentifier identifier = def.name;
        boolean includeDef = included.isEmpty() || included.contains(identifier);
        if (includeDef && def.isStatic()) {
            throw new InvalidRequestException(String.format("Unable to include static column '%s' which would be included by Materialized View SELECT * statement", identifier));
        }
        boolean defInTargetPrimaryKey = targetClusteringColumns.contains(identifier) || targetPartitionKeys.contains(identifier);
        if (includeDef && !defInTargetPrimaryKey) {
            includedColumns.add(identifier);
        }
        if (!def.isPrimaryKeyColumn())
            continue;
        if (!defInTargetPrimaryKey) {
            if (missingClusteringColumns)
                columnNames.append(',');
            else
                missingClusteringColumns = true;
            columnNames.append(identifier);
        }
    }
    if (missingClusteringColumns)
        throw new InvalidRequestException(String.format("Cannot create Materialized View %s without primary key columns from base %s (%s)", columnFamily(), baseName.getColumnFamily(), columnNames.toString()));
    if (targetPartitionKeys.isEmpty())
        throw new InvalidRequestException("Must select at least a column for a Materialized View");
    if (targetClusteringColumns.isEmpty())
        throw new InvalidRequestException("No columns are defined for Materialized View other than primary key");
    TableParams params = properties.properties.asNewTableParams();
    if (params.defaultTimeToLive > 0) {
        throw new InvalidRequestException("Cannot set default_time_to_live for a materialized view. " + "Data in a materialized view always expire at the same time than " + "the corresponding data in the parent table.");
    }
    TableMetadata.Builder builder = TableMetadata.builder(keyspace(), columnFamily(), properties.properties.getId()).isView(true).params(params);
    add(metadata, targetPartitionKeys, builder::addPartitionKeyColumn);
    add(metadata, targetClusteringColumns, builder::addClusteringColumn);
    add(metadata, includedColumns, builder::addRegularColumn);
    ViewMetadata definition = new ViewMetadata(keyspace(), columnFamily(), metadata.id, metadata.name, included.isEmpty(), rawSelect, whereClauseText, builder.build());
    try {
        MigrationManager.announceNewView(definition, isLocalOnly);
        return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily());
    } catch (AlreadyExistsException e) {
        if (ifNotExists)
            return null;
        throw e;
    }
}
Also used : ClientState(org.apache.cassandra.service.ClientState) RawSelector(org.apache.cassandra.cql3.selection.RawSelector) ColumnMetadata(org.apache.cassandra.schema.ColumnMetadata) Selectable(org.apache.cassandra.cql3.selection.Selectable) InvalidRequestException(org.apache.cassandra.exceptions.InvalidRequestException) StatementRestrictions(org.apache.cassandra.cql3.restrictions.StatementRestrictions) ViewMetadata(org.apache.cassandra.schema.ViewMetadata) TableMetadata(org.apache.cassandra.schema.TableMetadata) DurationType(org.apache.cassandra.db.marshal.DurationType) TableParams(org.apache.cassandra.schema.TableParams) AlreadyExistsException(org.apache.cassandra.exceptions.AlreadyExistsException)

Example 2 with WhereClause

use of org.apache.cassandra.cql3.WhereClause in project cassandra by apache.

the class CreateViewStatement method apply.

public Keyspaces apply(Keyspaces schema) {
    if (!DatabaseDescriptor.getMaterializedViewsEnabled())
        throw ire("Materialized views are disabled. Enable in cassandra.yaml to use.");
    /*
         * Basic dependency validations
         */
    KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
    if (null == keyspace)
        throw ire("Keyspace '%s' doesn't exist", keyspaceName);
    if (keyspace.createReplicationStrategy().hasTransientReplicas())
        throw new InvalidRequestException("Materialized views are not supported on transiently replicated keyspaces");
    TableMetadata table = keyspace.tables.getNullable(tableName);
    if (null == table)
        throw ire("Base table '%s' doesn't exist", tableName);
    if (keyspace.hasTable(viewName))
        throw ire("Cannot create materialized view '%s' - a table with the same name already exists", viewName);
    if (keyspace.hasView(viewName)) {
        if (ifNotExists)
            return schema;
        throw new AlreadyExistsException(keyspaceName, viewName);
    }
    if (table.isCounter())
        throw ire("Materialized views are not supported on counter tables");
    if (table.isView())
        throw ire("Materialized views cannot be created against other materialized views");
    // Guardrails on table properties
    Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
    // Guardrail to limit number of mvs per table
    Iterable<ViewMetadata> tableViews = keyspace.views.forTable(table.id);
    Guardrails.materializedViewsPerTable.guard(Iterables.size(tableViews) + 1, String.format("%s on table %s", viewName, table.name), state);
    if (table.params.gcGraceSeconds == 0) {
        throw ire("Cannot create materialized view '%s' for base table " + "'%s' with gc_grace_seconds of 0, since this value is " + "used to TTL undelivered updates. Setting gc_grace_seconds" + " too low might cause undelivered updates to expire " + "before being replayed.", viewName, tableName);
    }
    /*
         * Process SELECT clause
         */
    Set<ColumnIdentifier> selectedColumns = new HashSet<>();
    if (// SELECT *
    rawColumns.isEmpty())
        table.columns().forEach(c -> selectedColumns.add(c.name));
    rawColumns.forEach(selector -> {
        if (null != selector.alias)
            throw ire("Cannot use aliases when defining a materialized view (got %s)", selector);
        if (!(selector.selectable instanceof Selectable.RawIdentifier))
            throw ire("Can only select columns by name when defining a materialized view (got %s)", selector.selectable);
        // will throw IRE if the column doesn't exist in the base table
        ColumnMetadata column = (ColumnMetadata) selector.selectable.prepare(table);
        selectedColumns.add(column.name);
    });
    selectedColumns.stream().map(table::getColumn).filter(ColumnMetadata::isStatic).findAny().ifPresent(c -> {
        throw ire("Cannot include static column '%s' in materialized view '%s'", c, viewName);
    });
    if (partitionKeyColumns.isEmpty())
        throw ire("Must provide at least one partition key column for materialized view '%s'", viewName);
    HashSet<ColumnIdentifier> primaryKeyColumns = new HashSet<>();
    concat(partitionKeyColumns, clusteringColumns).forEach(name -> {
        ColumnMetadata column = table.getColumn(name);
        if (null == column || !selectedColumns.contains(name))
            throw ire("Unknown column '%s' referenced in PRIMARY KEY for materialized view '%s'", name, viewName);
        if (!primaryKeyColumns.add(name))
            throw ire("Duplicate column '%s' in PRIMARY KEY clause for materialized view '%s'", name, viewName);
        AbstractType<?> type = column.type;
        if (type.isMultiCell()) {
            if (type.isCollection())
                throw ire("Invalid non-frozen collection type '%s' for PRIMARY KEY column '%s'", type, name);
            else
                throw ire("Invalid non-frozen user-defined type '%s' for PRIMARY KEY column '%s'", type, name);
        }
        if (type.isCounter())
            throw ire("counter type is not supported for PRIMARY KEY column '%s'", name);
        if (type.referencesDuration())
            throw ire("duration type is not supported for PRIMARY KEY column '%s'", name);
    });
    // If we give a clustering order, we must explicitly do so for all aliases and in the order of the PK
    if (!clusteringOrder.isEmpty() && !clusteringColumns.equals(new ArrayList<>(clusteringOrder.keySet())))
        throw ire("Clustering key columns must exactly match columns in CLUSTERING ORDER BY directive");
    /*
         * We need to include all of the primary key columns from the base table in order to make sure that we do not
         * overwrite values in the view. We cannot support "collapsing" the base table into a smaller number of rows in
         * the view because if we need to generate a tombstone, we have no way of knowing which value is currently being
         * used in the view and whether or not to generate a tombstone. In order to not surprise our users, we require
         * that they include all of the columns. We provide them with a list of all of the columns left to include.
         */
    List<ColumnIdentifier> missingPrimaryKeyColumns = Lists.newArrayList(filter(transform(table.primaryKeyColumns(), c -> c.name), c -> !primaryKeyColumns.contains(c)));
    if (!missingPrimaryKeyColumns.isEmpty()) {
        throw ire("Cannot create materialized view '%s' without primary key columns %s from base table '%s'", viewName, join(", ", transform(missingPrimaryKeyColumns, ColumnIdentifier::toString)), tableName);
    }
    Set<ColumnIdentifier> regularBaseTableColumnsInViewPrimaryKey = new HashSet<>(primaryKeyColumns);
    transform(table.primaryKeyColumns(), c -> c.name).forEach(regularBaseTableColumnsInViewPrimaryKey::remove);
    if (regularBaseTableColumnsInViewPrimaryKey.size() > 1) {
        throw ire("Cannot include more than one non-primary key column in materialized view primary key (got %s)", join(", ", transform(regularBaseTableColumnsInViewPrimaryKey, ColumnIdentifier::toString)));
    }
    /*
         * Process WHERE clause
         */
    if (whereClause.containsTokenRelations())
        throw new InvalidRequestException("Cannot use token relation when defining a materialized view");
    if (whereClause.containsCustomExpressions())
        throw ire("WHERE clause for materialized view '%s' cannot contain custom index expressions", viewName);
    StatementRestrictions restrictions = new StatementRestrictions(StatementType.SELECT, table, whereClause, VariableSpecifications.empty(), false, false, true, true);
    List<ColumnIdentifier> nonRestrictedPrimaryKeyColumns = Lists.newArrayList(filter(primaryKeyColumns, name -> !restrictions.isRestricted(table.getColumn(name))));
    if (!nonRestrictedPrimaryKeyColumns.isEmpty()) {
        throw ire("Primary key columns %s must be restricted with 'IS NOT NULL' or otherwise", join(", ", transform(nonRestrictedPrimaryKeyColumns, ColumnIdentifier::toString)));
    }
    // See CASSANDRA-13798
    Set<ColumnMetadata> restrictedNonPrimaryKeyColumns = restrictions.nonPKRestrictedColumns(false);
    if (!restrictedNonPrimaryKeyColumns.isEmpty() && !Boolean.getBoolean("cassandra.mv.allow_filtering_nonkey_columns_unsafe")) {
        throw ire("Non-primary key columns can only be restricted with 'IS NOT NULL' (got: %s restricted illegally)", join(",", transform(restrictedNonPrimaryKeyColumns, ColumnMetadata::toString)));
    }
    /*
         * Validate WITH params
         */
    attrs.validate();
    if (attrs.hasOption(TableParams.Option.DEFAULT_TIME_TO_LIVE)) {
        throw ire("Cannot set default_time_to_live for a materialized view. " + "Data in a materialized view always expire at the same time than " + "the corresponding data in the parent table.");
    }
    /*
         * Build the thing
         */
    TableMetadata.Builder builder = TableMetadata.builder(keyspaceName, viewName);
    if (attrs.hasProperty(TableAttributes.ID))
        builder.id(attrs.getId());
    builder.params(attrs.asNewTableParams()).kind(TableMetadata.Kind.VIEW);
    partitionKeyColumns.forEach(name -> builder.addPartitionKeyColumn(name, getType(table, name)));
    clusteringColumns.forEach(name -> builder.addClusteringColumn(name, getType(table, name)));
    selectedColumns.stream().filter(name -> !primaryKeyColumns.contains(name)).forEach(name -> builder.addRegularColumn(name, getType(table, name)));
    ViewMetadata view = new ViewMetadata(table.id, table.name, rawColumns.isEmpty(), whereClause, builder.build());
    view.metadata.validate();
    return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.views.with(view)));
}
Also used : AuditLogContext(org.apache.cassandra.audit.AuditLogContext) Change(org.apache.cassandra.transport.Event.SchemaChange.Change) java.util(java.util) Iterables(com.google.common.collect.Iterables) Iterables.transform(com.google.common.collect.Iterables.transform) Permission(org.apache.cassandra.auth.Permission) AbstractType(org.apache.cassandra.db.marshal.AbstractType) org.apache.cassandra.cql3(org.apache.cassandra.cql3) AlreadyExistsException(org.apache.cassandra.exceptions.AlreadyExistsException) View(org.apache.cassandra.db.view.View) Guardrails(org.apache.cassandra.db.guardrails.Guardrails) Lists(com.google.common.collect.Lists) String.join(java.lang.String.join) Iterables.concat(com.google.common.collect.Iterables.concat) KeyspacesDiff(org.apache.cassandra.schema.Keyspaces.KeyspacesDiff) DatabaseDescriptor(org.apache.cassandra.config.DatabaseDescriptor) InvalidRequestException(org.apache.cassandra.exceptions.InvalidRequestException) ImmutableSet(com.google.common.collect.ImmutableSet) SchemaChange(org.apache.cassandra.transport.Event.SchemaChange) ClientState(org.apache.cassandra.service.ClientState) StatementRestrictions(org.apache.cassandra.cql3.restrictions.StatementRestrictions) AuditLogEntryType(org.apache.cassandra.audit.AuditLogEntryType) StatementType(org.apache.cassandra.cql3.statements.StatementType) Selectable(org.apache.cassandra.cql3.selection.Selectable) ReversedType(org.apache.cassandra.db.marshal.ReversedType) Target(org.apache.cassandra.transport.Event.SchemaChange.Target) RawSelector(org.apache.cassandra.cql3.selection.RawSelector) Iterables.filter(com.google.common.collect.Iterables.filter) org.apache.cassandra.schema(org.apache.cassandra.schema) AlreadyExistsException(org.apache.cassandra.exceptions.AlreadyExistsException) InvalidRequestException(org.apache.cassandra.exceptions.InvalidRequestException) StatementRestrictions(org.apache.cassandra.cql3.restrictions.StatementRestrictions)

Aggregations

StatementRestrictions (org.apache.cassandra.cql3.restrictions.StatementRestrictions)2 RawSelector (org.apache.cassandra.cql3.selection.RawSelector)2 Selectable (org.apache.cassandra.cql3.selection.Selectable)2 AlreadyExistsException (org.apache.cassandra.exceptions.AlreadyExistsException)2 InvalidRequestException (org.apache.cassandra.exceptions.InvalidRequestException)2 ClientState (org.apache.cassandra.service.ClientState)2 ImmutableSet (com.google.common.collect.ImmutableSet)1 Iterables (com.google.common.collect.Iterables)1 Iterables.concat (com.google.common.collect.Iterables.concat)1 Iterables.filter (com.google.common.collect.Iterables.filter)1 Iterables.transform (com.google.common.collect.Iterables.transform)1 Lists (com.google.common.collect.Lists)1 String.join (java.lang.String.join)1 java.util (java.util)1 AuditLogContext (org.apache.cassandra.audit.AuditLogContext)1 AuditLogEntryType (org.apache.cassandra.audit.AuditLogEntryType)1 Permission (org.apache.cassandra.auth.Permission)1 DatabaseDescriptor (org.apache.cassandra.config.DatabaseDescriptor)1 org.apache.cassandra.cql3 (org.apache.cassandra.cql3)1 StatementType (org.apache.cassandra.cql3.statements.StatementType)1