/* * 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. */ /* * Copyright (C) 2015 ScyllaDB * * Modified by ScyllaDB */ /* * This file is part of Scylla. * * Scylla is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Scylla is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Scylla. If not, see . */ #pragma once #include "cql3/statements/raw/cf_statement.hh" #include "cql3/statements/raw/select_statement.hh" #include "cql3/cql_statement.hh" #include "cql3/selection/selection.hh" #include "cql3/selection/raw_selector.hh" #include "cql3/restrictions/statement_restrictions.hh" #include "cql3/result_set.hh" #include "exceptions/unrecognized_entity_exception.hh" #include "service/client_state.hh" #include "core/shared_ptr.hh" #include "core/distributed.hh" #include "validation.hh" namespace cql3 { namespace statements { /** * Encapsulates a completely parsed SELECT query, including the target * column family, expression, result count, and ordering clause. * */ class select_statement : public cql_statement { public: using parameters = raw::select_statement::parameters; private: static constexpr int DEFAULT_COUNT_PAGE_SIZE = 10000; static thread_local const ::shared_ptr _default_parameters; schema_ptr _schema; uint32_t _bound_terms; ::shared_ptr _parameters; ::shared_ptr _selection; ::shared_ptr _restrictions; bool _is_reversed; ::shared_ptr _limit; template using compare_fn = raw::select_statement::compare_fn; using result_row_type = raw::select_statement::result_row_type; using ordering_comparator_type = raw::select_statement::ordering_comparator_type; /** * The comparator used to orders results when multiple keys are selected (using IN). */ ordering_comparator_type _ordering_comparator; query::partition_slice::option_set _opts; cql_stats& _stats; private: future<::shared_ptr> do_execute(distributed& proxy, service::query_state& state, const query_options& options); friend class select_statement_executor; public: select_statement(schema_ptr schema, uint32_t bound_terms, ::shared_ptr parameters, ::shared_ptr selection, ::shared_ptr restrictions, bool is_reversed, ordering_comparator_type ordering_comparator, ::shared_ptr limit, cql_stats& stats); virtual bool uses_function(const sstring& ks_name, const sstring& function_name) const override; // Creates a simple select based on the given selection // Note that the results select statement should not be used for actual queries, but only for processing already // queried data through processColumnFamily. static ::shared_ptr for_selection( schema_ptr schema, ::shared_ptr selection, cql_stats& stats); virtual ::shared_ptr get_result_metadata() const override; virtual uint32_t get_bound_terms() override; virtual future<> check_access(const service::client_state& state) override; virtual void validate(distributed&, const service::client_state& state) override; virtual bool depends_on_keyspace(const sstring& ks_name) const; virtual bool depends_on_column_family(const sstring& cf_name) const; virtual future<::shared_ptr> execute(distributed& proxy, service::query_state& state, const query_options& options) override; virtual future<::shared_ptr> execute_internal(distributed& proxy, service::query_state& state, const query_options& options) override; future<::shared_ptr> execute(distributed& proxy, lw_shared_ptr cmd, dht::partition_range_vector&& partition_ranges, service::query_state& state, const query_options& options, gc_clock::time_point now); shared_ptr process_results(foreign_ptr> results, lw_shared_ptr cmd, const query_options& options, gc_clock::time_point now); #if 0 private ResultMessage.Rows pageAggregateQuery(QueryPager pager, QueryOptions options, int pageSize, long now) throws RequestValidationException, RequestExecutionException { Selection.ResultSetBuilder result = _selection->resultSetBuilder(now); while (!pager.isExhausted()) { for (org.apache.cassandra.db.Row row : pager.fetchPage(pageSize)) { // Not columns match the query, skip if (row.cf == null) continue; processColumnFamily(row.key.getKey(), row.cf, options, now, result); } } return new ResultMessage.Rows(result.build(options.getProtocolVersion())); } static List readLocally(String keyspaceName, List cmds) { Keyspace keyspace = Keyspace.open(keyspaceName); List rows = new ArrayList(cmds.size()); for (ReadCommand cmd : cmds) rows.add(cmd.getRow(keyspace)); return rows; } public ResultMessage.Rows executeInternal(QueryState state, QueryOptions options) throws RequestExecutionException, RequestValidationException { int limit = getLimit(options); long now = System.currentTimeMillis(); Pageable command = getPageableCommand(options, limit, now); List rows = command == null ? Collections.emptyList() : (command instanceof Pageable.ReadCommands ? readLocally(keyspace(), ((Pageable.ReadCommands)command).commands) : ((RangeSliceCommand)command).executeLocally()); return processResults(rows, options, limit, now); } public ResultSet process(List rows) throws InvalidRequestException { QueryOptions options = QueryOptions.DEFAULT; return process(rows, options, getLimit(options), System.currentTimeMillis()); } #endif const sstring& keyspace() const; const sstring& column_family() const; query::partition_slice make_partition_slice(const query_options& options); ::shared_ptr get_restrictions() const; #if 0 private SliceQueryFilter sliceFilter(ColumnSlice slice, int limit, int toGroup) { return sliceFilter(new ColumnSlice[]{ slice }, limit, toGroup); } private SliceQueryFilter sliceFilter(ColumnSlice[] slices, int limit, int toGroup) { assert ColumnSlice.validateSlices(slices, _schema.comparator, _is_reversed) : String.format("Invalid slices: " + Arrays.toString(slices) + (_is_reversed ? " (reversed)" : "")); return new SliceQueryFilter(slices, _is_reversed, limit, toGroup); } #endif private: int32_t get_limit(const query_options& options) const; bool needs_post_query_ordering() const; #if 0 private int updateLimitForQuery(int limit) { // Internally, we don't support exclusive bounds for slices. Instead, we query one more element if necessary // and exclude it later (in processColumnFamily) return restrictions.isNonCompositeSliceWithExclusiveBounds() && limit != Integer.MAX_VALUE ? limit + 1 : limit; } private SortedSet getRequestedColumns(QueryOptions options) throws InvalidRequestException { // Note: getRequestedColumns don't handle static columns, but due to CASSANDRA-5762 // we always do a slice for CQL3 tables, so it's ok to ignore them here assert !restrictions.isColumnRange(); SortedSet columns = new TreeSet(cfm.comparator); for (Composite composite : restrictions.getClusteringColumnsAsComposites(options)) columns.addAll(addSelectedColumns(composite)); return columns; } private SortedSet addSelectedColumns(Composite prefix) { if (cfm.comparator.isDense()) { return FBUtilities.singleton(cfm.comparator.create(prefix, null), cfm.comparator); } else { SortedSet columns = new TreeSet(cfm.comparator); // We need to query the selected column as well as the marker // column (for the case where the row exists but has no columns outside the PK) // Two exceptions are "static CF" (non-composite non-compact CF) and "super CF" // that don't have marker and for which we must query all columns instead if (cfm.comparator.isCompound() && !cfm.isSuper()) { // marker columns.add(cfm.comparator.rowMarker(prefix)); // selected columns for (ColumnDefinition def : selection.getColumns()) if (def.isRegular() || def.isStatic()) columns.add(cfm.comparator.create(prefix, def)); } else { // We now that we're not composite so we can ignore static columns for (ColumnDefinition def : cfm.regularColumns()) columns.add(cfm.comparator.create(prefix, def)); } return columns; } } public List getValidatedIndexExpressions(QueryOptions options) throws InvalidRequestException { if (!restrictions.usesSecondaryIndexing()) return Collections.emptyList(); List expressions = restrictions.getIndexExpressions(options); ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(columnFamily()); SecondaryIndexManager secondaryIndexManager = cfs.indexManager; secondaryIndexManager.validateIndexSearchersForQuery(expressions); return expressions; } private CellName makeExclusiveSliceBound(Bound bound, CellNameType type, QueryOptions options) throws InvalidRequestException { if (restrictions.areRequestedBoundsInclusive(bound)) return null; return type.makeCellName(restrictions.getClusteringColumnsBounds(bound, options).get(0)); } private Iterator applySliceRestriction(final Iterator cells, final QueryOptions options) throws InvalidRequestException { final CellNameType type = cfm.comparator; final CellName excludedStart = makeExclusiveSliceBound(Bound.START, type, options); final CellName excludedEnd = makeExclusiveSliceBound(Bound.END, type, options); return Iterators.filter(cells, new Predicate() { public boolean apply(Cell c) { // For dynamic CF, the column could be out of the requested bounds (because we don't support strict bounds internally (unless // the comparator is composite that is)), filter here return !((excludedStart != null && type.compare(c.name(), excludedStart) == 0) || (excludedEnd != null && type.compare(c.name(), excludedEnd) == 0)); } }); } private ResultSet process(List rows, QueryOptions options, int limit, long now) throws InvalidRequestException { Selection.ResultSetBuilder result = selection.resultSetBuilder(now); for (org.apache.cassandra.db.Row row : rows) { // Not columns match the query, skip if (row.cf == null) continue; processColumnFamily(row.key.getKey(), row.cf, options, now, result); } ResultSet cqlRows = result.build(options.getProtocolVersion()); orderResults(cqlRows); // Internal calls always return columns in the comparator order, even when reverse was set if (isReversed) cqlRows.reverse(); // Trim result if needed to respect the user limit cqlRows.trim(limit); return cqlRows; } // Used by ModificationStatement for CAS operations void processColumnFamily(ByteBuffer key, ColumnFamily cf, QueryOptions options, long now, Selection.ResultSetBuilder result) throws InvalidRequestException { CFMetaData cfm = cf.metadata(); ByteBuffer[] keyComponents = null; if (cfm.getKeyValidator() instanceof CompositeType) { keyComponents = ((CompositeType)cfm.getKeyValidator()).split(key); } else { keyComponents = new ByteBuffer[]{ key }; } Iterator cells = cf.getSortedColumns().iterator(); if (restrictions.isNonCompositeSliceWithExclusiveBounds()) cells = applySliceRestriction(cells, options); CQL3Row.RowIterator iter = cfm.comparator.CQL3RowBuilder(cfm, now).group(cells); // If there is static columns but there is no non-static row, then provided the select was a full // partition selection (i.e. not a 2ndary index search and there was no condition on clustering columns) // then we want to include the static columns in the result set (and we're done). CQL3Row staticRow = iter.getStaticRow(); if (staticRow != null && !iter.hasNext() && !restrictions.usesSecondaryIndexing() && restrictions.hasNoClusteringColumnsRestriction()) { result.newRow(options.getProtocolVersion()); for (ColumnDefinition def : selection.getColumns()) { switch (def.kind) { case PARTITION_KEY: result.add(keyComponents[def.position()]); break; case STATIC: addValue(result, def, staticRow, options); break; default: result.add((ByteBuffer)null); } } return; } while (iter.hasNext()) { CQL3Row cql3Row = iter.next(); // Respect requested order result.newRow(options.getProtocolVersion()); // Respect selection order for (ColumnDefinition def : selection.getColumns()) { switch (def.kind) { case PARTITION_KEY: result.add(keyComponents[def.position()]); break; case CLUSTERING_COLUMN: result.add(cql3Row.getClusteringColumn(def.position())); break; case COMPACT_VALUE: result.add(cql3Row.getColumn(null)); break; case REGULAR: addValue(result, def, cql3Row, options); break; case STATIC: addValue(result, def, staticRow, options); break; } } } } private static void addValue(Selection.ResultSetBuilder result, ColumnDefinition def, CQL3Row row, QueryOptions options) { if (row == null) { result.add((ByteBuffer)null); return; } if (def.type.isMultiCell()) { List cells = row.getMultiCellColumn(def.name); ByteBuffer buffer = cells == null ? null : ((CollectionType)def.type).serializeForNativeProtocol(cells, options.getProtocolVersion()); result.add(buffer); return; } result.add(row.getColumn(def.name)); } #endif }; } }