Queries the view, with reducing or grouping as per the options.
in CBL_SQLiteViewStorage.m
- (CBLQueryIteratorBlock) reducedQueryWithOptions: (CBLQueryOptions*)options status: (CBLStatus*)outStatus
356885
@Override
    public List<QueryRow> reducedQuery(QueryOptions options) throws CouchbaseLiteException {
        final Predicate<QueryRow> postFilter = options.getPostFilter();

        final int groupLevel = options.getGroupLevel();
        final boolean group = options.isGroup() || (groupLevel > 0);
        final Reducer reduce = delegate.getReduce();
        if (options.isReduceSpecified()) {
            if (options.isReduce() && reduce == null) {
                Log.w(TAG, String.format(Locale.ENGLISH,
                        "Cannot use reduce option in view %s which has no reduce block defined",
                        name));
                throw new CouchbaseLiteException(Status.BAD_PARAM);
            }
        }

        final List<Object> keysToReduce = new ArrayList<Object>(REDUCE_BATCH_SIZE);
        final List<Object> valuesToReduce = new ArrayList<Object>(REDUCE_BATCH_SIZE);
        final Object[] lastKeys = new Object[1];
        lastKeys[0] = null;
        final SQLiteViewStore that = this;

        final List<QueryRow> rows = new ArrayList<QueryRow>();

        Status status = runQuery(options, new QueryRowBlock() {
            @Override
            public Status onRow(byte[] keyData, byte[] valueData, String docID, Cursor cursor) {
                JsonDocument keyDoc = new JsonDocument(keyData);
                JsonDocument valueDoc = new JsonDocument(valueData);
                assert (keyDoc != null);
                Object keyObject = keyDoc.jsonObject();
                if (group && !groupTogether(keyObject, lastKeys[0], groupLevel)) {
                    if (lastKeys[0] != null) {
                        // This pair starts a new group, so reduce & record the last one:
                        Object key = groupKey(lastKeys[0], groupLevel);
                        Object reduced = (reduce != null) ?
                                reduce.reduce(keysToReduce, valuesToReduce, false) :
                                null;
                        QueryRow row = new QueryRow(null, 0, key, reduced, null);
                        if (postFilter == null || postFilter.apply(row))
                            rows.add(row);
                        keysToReduce.clear();
                        valuesToReduce.clear();
                    }
                    lastKeys[0] = keyObject;
                }
                //Log.v(TAG, String.format("Query %s: Will reduce row with key=%s, value=%s",
                //        name, new String(keyData), new String(valueData)));

                /*
                Object valueOrData = valueData;
                if(reduce != null && rowValueIsEntireDoc(valueData)){
                    // map fn emitted 'doc' as value, which was stored as a "*" placeholder; expand now:
                    long sequence = Long.valueOf(cursor.getString(3));
                    RevisionInternal rev = store.getDocument(docID, sequence);
                    if(rev == null)
                        Log.w(TAG, String.format("%s: Couldn't load doc for row value", this));
                    else
                        valueOrData = rev.getProperties();
                }
                */
                keysToReduce.add(keyObject);
                valuesToReduce.add(valueDoc.jsonObject());
                return new Status(Status.OK);
            }
        });

        if (keysToReduce != null && keysToReduce.size() > 0) {
            // Finish the last group (or the entire list, if no grouping):
            Object key = group ? groupKey(lastKeys[0], groupLevel) : null;
            Object reduced = (reduce != null) ?
                    reduce.reduce(keysToReduce, valuesToReduce, false) :
                    null;
            Log.v(TAG, String.format(Locale.ENGLISH, "Query %s: Reduced to key=%s, value=%s",
                    name, key, reduced));
            QueryRow row = new QueryRow(null, 0, key, reduced, null);
            if (postFilter == null || postFilter.apply(row)) {
                rows.add(row);
            }
        }
        return rows;
    }
156371
@Override
    @InterfaceAudience.Private
    public Status updateIndexes(List<ViewStore> inputViews) throws CouchbaseLiteException {
        Log.v(Log.TAG_VIEW, "Re-indexing view: %s", name);
        if (getViewID() <= 0) {
            String msg = "getViewID() < 0";
            throw new CouchbaseLiteException(msg, Status.NOT_FOUND);
        }

        if (!store.beginTransaction())
            throw new CouchbaseLiteException("Error in beginTransaction()", Status.DB_ERROR);

        boolean success = false;
        Cursor cursor = null;
        try {
            // If the view the update is for doesn't need any update, don't do anything:
            final long dbMaxSequence = store.getLastSequence();
            final long forViewLastSequence = getLastSequenceIndexed();
            if (forViewLastSequence >= dbMaxSequence) {
                success = true;
                return new Status(Status.NOT_MODIFIED);
            }

            // Check whether we need to update at all,
            // and remove obsolete emitted results from the 'maps' table:
            long minLastSequence = dbMaxSequence;
            final long[] viewLastSequence = new long[inputViews.size()];
            int deletedCount = 0;
            int i = 0;
            final HashSet<String> docTypes = new HashSet<String>();
            HashMap<String, String> viewDocTypes = null;
            boolean allDocTypes = false;
            final HashMap<Integer, Integer> viewTotalRows = new HashMap<Integer, Integer>();
            final ArrayList<SQLiteViewStore> views = new ArrayList<SQLiteViewStore>();
            final ArrayList<Mapper> mapBlocks = new ArrayList<Mapper>();

            for (ViewStore v : inputViews) {
                assert (v != null);
                SQLiteViewStore view = (SQLiteViewStore) v;
                ViewStoreDelegate delegate = view.getDelegate();
                Mapper map = delegate != null ? delegate.getMap() : null;
                if (map == null) {
                    if (view == this) {
                        String msg = String.format(Locale.ENGLISH, "Cannot index view %s: " +
                                "no map block registered", view.getName());
                        Log.e(Log.TAG_VIEW, msg);
                        throw new CouchbaseLiteException(msg, Status.BAD_REQUEST);
                    }
                    Log.v(Log.TAG_VIEW, "    %s has no map block; skipping it", view.getName());
                    continue;
                }

                views.add(view);
                mapBlocks.add(map);

                int viewID = view.getViewID();
                if (viewID <= 0) {
                    String message = String.format(Locale.ENGLISH, "View '%s' not found in database",
                            view.getName());
                    Log.e(Log.TAG_VIEW, message);
                    throw new CouchbaseLiteException(message, Status.NOT_FOUND);
                }

                int totalRows = view.getTotalRows();
                viewTotalRows.put(viewID, totalRows);

                long last = view == this ? forViewLastSequence : view.getLastSequenceIndexed();
                viewLastSequence[i++] = last;
                if (last < 0) {
                    String msg = String.format(Locale.ENGLISH, "last < 0 (%d)", last);
                    throw new CouchbaseLiteException(msg, Status.INTERNAL_SERVER_ERROR);
                } else if (last < dbMaxSequence) {
                    if (last == 0)
                        view.createIndex();
                    minLastSequence = Math.min(minLastSequence, last);
                    Log.v(Log.TAG_VIEW, "    %s last indexed at #%d", view.getName(), last);

                    String docType = delegate.getDocumentType();
                    if (docType != null) {
                        docTypes.add(docType);
                        if (viewDocTypes == null)
                            viewDocTypes = new HashMap<String, String>();
                        viewDocTypes.put(view.getName(), docType);
                    } else {
                        allDocTypes = true;
                    }

                    int changes = 0;
                    if (last == 0) {
                        changes = store.getStorageEngine().delete(view.queryString("maps_#"), null, null);
                    } else {
                        store.optimizeSQLIndexes();
                        String[] args = {Long.toString(last), Long.toString(last)};
                        changes = store.getStorageEngine().delete(view.queryString("maps_#"),
                                "sequence IN (SELECT parent FROM revs " +
                                        "WHERE sequence>? AND +parent>0 AND +parent<=?)", args);
                    }

                    // Update #deleted rows:
                    deletedCount += changes;

                    // Only count these deletes as changes if this isn't a view reset to 0
                    if (last != 0) {
                        int newTotalRows = viewTotalRows.get(viewID) - changes;
                        viewTotalRows.put(viewID, newTotalRows);
                    }
                }
            }

            if (minLastSequence == dbMaxSequence) {
                Log.v(Log.TAG_VIEW, "minLastSequence (%d) == dbMaxSequence (%d), nothing to do",
                        minLastSequence, dbMaxSequence);
                success = true;
                return new Status(Status.NOT_MODIFIED);
            }

            Log.v(Log.TAG_VIEW, "Updating indexes of (%s) from #%d to #%d ...",
                    viewNames(views), minLastSequence, dbMaxSequence);

            // This is the emit() block, which gets called from within the user-defined map() block
            // that's called down below.
            final AtomicInteger insertedCount = new AtomicInteger(0);
            AbstractMapEmitBlock emitBlock = new AbstractMapEmitBlock() {
                @Override
                public void emit(Object key, Object value) {
                    if (key == null) {
                        Log.w(Log.TAG_VIEW, "emit() called with nil key; ignoring");
                        return;
                    }
                    try {
                        curView.emit(key, value, this.sequence); // emit block's sequence
                        int curViewID = curView.getViewID();
                        viewTotalRows.put(curViewID, viewTotalRows.get(curViewID) + 1);
                    } catch (Exception e) {
                        Log.e(Log.TAG_VIEW, "Error emitting", e);
                        throw new RuntimeException(e);
                    }
                }
            };

            // Now scan every revision added since the last time the view was indexed:

            // NOTE: Below is original Query. In case query result uses a lot of memory,
            //       Android SQLiteDatabase causes null value column. Then it causes the missing
            //       _index data because following logic skip result if column is null.
            //       To avoid the issue, retrieving json field is isolated from original query.
            //       Because json field could be large, maximum size is 2MB.
            // StringBuffer sql = new StringBuffer( "SELECT revs.doc_id, sequence, docid, revid,
            // json, no_attachments, deleted FROM revs, docs WHERE sequence>? AND current!=0 ");

            boolean checkDocTypes = docTypes.size() > 1 || (allDocTypes && docTypes.size() > 0);
            StringBuilder sql = new StringBuilder(
                    "SELECT revs.doc_id, sequence, docid, revid, no_attachments, deleted ");
            if (checkDocTypes)
                sql.append(", doc_type ");
            sql.append("FROM revs ");
            sql.append("JOIN docs ON docs.doc_id = revs.doc_id ");
            sql.append("WHERE sequence>? AND +current>0 ");
            if (minLastSequence == 0) {
                sql.append("AND +deleted=0 ");
            }
            if (!allDocTypes && docTypes.size() > 0) {
                String docTypesString = getJoinedSQLQuotedStrings(
                        docTypes.toArray(new String[docTypes.size()]));
                sql.append("AND doc_type IN (").append(docTypesString).append(") ");
            }
            // order result by deleted ASC so if multiple revs returned the non deleted are the first
            // NOTE: Views broken with concurrent update and delete
            // https://github.com/couchbase/couchbase-lite-java-core/issues/952
            sql.append("ORDER BY +revs.doc_id, +deleted, +revid DESC");
            String[] selectArgs = {Long.toString(minLastSequence)};
            cursor = store.getStorageEngine().rawQuery(sql.toString(), selectArgs);

            boolean keepGoing = cursor.moveToNext(); // Go to first result row
            while (keepGoing) {
                // NOTE: skip row if 1st column is null
                // https://github.com/couchbase/couchbase-lite-java-core/issues/497
                if (cursor.isNull(0)) {
                    keepGoing = cursor.moveToNext();
                    continue;
                }

                long docID = cursor.getLong(0);

                // Reconstitute the document as a dictionary:
                long sequence = cursor.getLong(1);
                String docId = cursor.getString(2);
                if (docId.startsWith("_design/")) {  // design docs don't get indexed!
                    keepGoing = cursor.moveToNext();
                    continue;
                }
                String revID = cursor.getString(3);
                boolean deleted = cursor.getInt(5) > 0;
                String docType = checkDocTypes ? cursor.getString(6) : null;

                // Skip rows with the same doc_id -- these are losing conflicts.
                // NOTE: Or Skip rows if 1st column is null
                // https://github.com/couchbase/couchbase-lite-java-core/issues/497
                ArrayList<String> conflicts = null;
                boolean isNull;
                while ((keepGoing = cursor.moveToNext()) &&
                        ((isNull = cursor.isNull(0)) || cursor.getLong(0) == docID)) {
                    if (isNull)
                        continue;
                    if (!deleted) {
                        if (conflicts == null)
                            conflicts = new ArrayList<String>();
                        conflicts.add(cursor.getString(3));
                    }
                }

                long realSequence = sequence; // because sequence may be changed, below
                if (minLastSequence > 0) {
                    // Find conflicts with documents from previous indexings.
                    Cursor cursor2 = null;
                    try {
                        String[] selectArgs2 = {Long.toString(docID), Long.toString(minLastSequence)};
                        cursor2 = store.getStorageEngine().rawQuery(
                                "SELECT revid, sequence FROM revs "
                                        + "WHERE doc_id=? AND sequence<=? AND current>0 AND deleted=0 "
                                        + "ORDER BY revID DESC ", selectArgs2);

                        if (cursor2.moveToNext()) {
                            String oldRevID = cursor2.getString(0);
                            // This is the revision that used to be the 'winner'.
                            // Remove its emitted rows:
                            long oldSequence = cursor2.getLong(1);
                            String[] args = {Long.toString(oldSequence)};
                            for (SQLiteViewStore view : views) {
                                int changes = view.store.getStorageEngine().
                                        delete(view.queryString("maps_#"), "sequence=?", args);
                                deletedCount += changes;
                                int thisViewID = view.getViewID();
                                int newTotalRows = viewTotalRows.get(thisViewID) - changes;
                                viewTotalRows.put(thisViewID, newTotalRows);
                            }

                            String conflictRevID = oldRevID;
                            if (deleted || RevisionInternal.CBLCompareRevIDs(oldRevID, revID) > 0) {
                                // It still 'wins' the conflict, so it's the one that
                                // should be mapped [again], not the current revision!
                                conflictRevID = revID;
                                revID = oldRevID;
                                deleted = false;
                                sequence = oldSequence;
                            }

                            if (!deleted) {
                                // Conflict revisions:
                                if (conflicts == null)
                                    conflicts = new ArrayList<String>();
                                conflicts.add(conflictRevID);
                                while (cursor2.moveToNext()) {
                                    conflicts.add(cursor2.getString(0));
                                }
                            }
                        }
                    } finally {
                        if (cursor2 != null) {
                            cursor2.close();
                        }
                    }
                }

                if (deleted)
                    continue;

                // Get json blob:
                String[] selectArgs3 = {Long.toString(sequence)};
                byte[] json = SQLiteUtils.byteArrayResultForQuery(store.getStorageEngine(),
                        "SELECT json FROM revs WHERE sequence=?", selectArgs3);

                // Get the document properties, to pass to the map function:
                Map<String, Object> curDoc = store.documentPropertiesFromJSON(
                        json,
                        docId,
                        revID,
                        false,
                        sequence
                );

                if (curDoc == null) {
                    Log.w(Log.TAG_VIEW, "Failed to parse JSON of doc %s rev %s", docID, revID);
                    continue;
                }
                curDoc.put("_local_seq", sequence);

                if (conflicts != null)
                    curDoc.put("_conflicts", conflicts);

                // Call the user-defined map() to emit new key/value pairs from this revision:
                i = -1;
                for (SQLiteViewStore view : views) {
                    curView = view;
                    ++i;
                    if (viewLastSequence[i] < realSequence) {
                        if (checkDocTypes) {
                            String viewDocType = viewDocTypes.get(view.getName());
                            if (viewDocType != null && !viewDocType.equals(docType))
                                continue; // skip; view's documentType doesn't match this doc
                        }
                        Log.v(Log.TAG_VIEW, "#%d: map '%s' for view %s...",
                                sequence, docID, view.getName());
                        try {
                            emitBlock.setSequence(sequence);
                            mapBlocks.get(i).map(curDoc, emitBlock);
                        } catch (Throwable e) {
                            String msg = String.format(Locale.ENGLISH, "Error when calling map block of view '%s'",
                                    view.getName());
                            Log.e(Log.TAG_VIEW, msg, e);
                            throw new CouchbaseLiteException(msg, e, Status.CALLBACK_ERROR);
                        }
                    }
                }
            }

            // Finally, record the last revision sequence number that was indexed and update #rows:
            for (SQLiteViewStore view : views) {
                view.finishCreatingIndex();
                int newTotalRows = viewTotalRows.get(view.getViewID());
                ContentValues updateValues = new ContentValues();
                updateValues.put("lastSequence", dbMaxSequence);
                updateValues.put("total_docs", newTotalRows);
                String[] whereArgs = {Integer.toString(view.getViewID())};
                store.getStorageEngine().update("views", updateValues, "view_id=?", whereArgs);
            }
            Log.v(Log.TAG_VIEW, "...Finished re-indexing (%s) to #%d (deleted %d, added %d)",
                    viewNames(views), dbMaxSequence, deletedCount, insertedCount.intValue());

            success = true;
            return new Status(Status.OK);
        } catch (SQLException ex) {
            throw new CouchbaseLiteException(ex, Status.DB_ERROR);
        } finally {
            curView = null;
            if (cursor != null)
                cursor.close();
            if (store != null) {
                if (!store.endTransaction(success))
                    throw new CouchbaseLiteException("Error in endTransaction()", Status.DB_ERROR);
            }
        }
    }
273929
def query(self):
        
        tree = pypeg2.parse(self._query, parser(), whitespace="")
        for walker in query_walkers():
            tree = tree.accept(walker)
        return tree
1314604
function (cb) {

                req.linz.model.getQuery(req, filters, function (err, result) {

                    if (err) {
                        return cb(err);
                    }

                    query = result;

                    return cb(null);

                });

            }
1174544
def queryMulti(self, queries):
		
		self.lastError = None
		self.affectedRows = 0
		self.rowcount = None
		self.record = None
		cursor = None
		
		try:
			try:
				self._GetConnection()
				#Execute query and store results
				cursor = self.conn.getCursor()
				for query in queries:
					self.conn.query = query
					if query.__class__ == [].__class__:
						self.affectedRows += cursor.execute(query[0], query[1])
					else:
						self.affectedRows += cursor.execute(query)
				self.conn.updateCheckTime()
			except Exception, e:
				self.lastError = e
		finally:
			if cursor is not None:
				cursor.close()
			self._ReturnConnection()
			if self.lastError is not None:
				raise self.lastError
			else:
				return self.affectedRows
1207654
def query_sort(query, options)
      query.order(options[:order]) if options[:order]
      query.order(options[:desc_order], :desc) if options[:desc_order]
      query
    end
217200
protected List<T> queryView(String viewName, ComplexKey key) {
		return db.queryView(createQuery(viewName)
								.includeDocs(true)
								.key(key),
							type);
	}
980607
def query_model(self, model, condition=None, offset=None, limit=None,
                    group_by=None, having=None, order_by=None, fields=None):
        
        if self._query is not None:
            query = self._query
        else:
            query = self.get_select()
            
        if condition is not None:
            if isinstance(query, Result):
                query = query.filter(condition)
            else:
                query = query.where(condition)
        if self.pagination:
            if offset is not None:
                query = query.offset(int(offset))
            if limit is not None:
                query = query.limit(int(limit))
        if order_by is not None:
            if isinstance(order_by, (tuple, list)):
                for order in order_by:
                    query = query.order_by(order)
            else:
                query = query.order_by(order_by)
        if group_by is not None:
            if isinstance(group_by, (tuple, list)):
                query = query.group_by(*group_by)
            else:
                query = query.group_by(group_by)
            if having is not None:
                query = query.having(having)
        return query
1335592
def query(self):
        
        query = QueryBuilder(self, self._query_grammar, self._post_processor)

        return query
1240338
def as_query(opts={})
      Tripod.logger.debug("TRIPOD: building select query for criteria...")

      return_graph = opts.has_key?(:return_graph) ? opts[:return_graph] : true

      Tripod.logger.debug("TRIPOD: with return_graph: #{return_graph.inspect}")

      select_query = "SELECT DISTINCT ?uri "

      if graph_lambdas.empty?

        if return_graph
          # if we are returning the graph, select it as a variable, and include either the <graph> or ?graph in the where clause
          if graph_uri
            select_query += "(<#{graph_uri}> as ?graph) WHERE { GRAPH <#{graph_uri}> { "
          else
            select_query += "?graph WHERE { GRAPH ?graph { "
          end
        else
          select_query += "WHERE { "
          # if we're not returning the graph, only restrict by the <graph> if there's one set at class level
          select_query += "GRAPH <#{graph_uri}> { " if graph_uri
        end

        select_query += self.query_where_clauses.join(" . ")
        select_query += " } "
        select_query += "} " if return_graph || graph_uri # close the graph clause

      else
        # whip through the graph lambdas and add into the query
        # we have multiple graphs so the above does not apply
        select_query += "WHERE { "

        graph_lambdas.each do |lambda_item|
          select_query += "GRAPH ?g { "
          select_query += lambda_item.call
          select_query += " } "
        end

        select_query += self.query_where_clauses.join(" . ")
        select_query += " } "
      end

      select_query += self.extra_clauses.join(" ")

      select_query += [order_clause, limit_clause, offset_clause].join(" ")

      select_query.strip
    end
276982
def groupQuery( self ):
        
        items = self.uiQueryTREE.selectedItems()
        if ( not len(items) > 2 ):
            return
        
        if ( isinstance(items[-1], XJoinItem) ):
            items = items[:-1]
        
        tree   = self.uiQueryTREE
        parent = items[0].parent()
        if ( not parent ):
            parent = tree
        
        preceeding = items[-1]
        
        tree.blockSignals(True)
        tree.setUpdatesEnabled(False)
        
        grp_item = XQueryItem(parent, Q(), preceeding = preceeding)
        for item in items:
            parent = item.parent()
            if ( not parent ):
                tree.takeTopLevelItem(tree.indexOfTopLevelItem(item))
            else:
                parent.takeChild(parent.indexOfChild(item))
            grp_item.addChild(item)
        grp_item.update()
        
        tree.blockSignals(False)
        tree.setUpdatesEnabled(True)
575764
def _query_compressed(options, collection_name, num_to_skip,
                      num_to_return, query, field_selector,
                      opts, check_keys=False, ctx=None):
    """"""
    op_query, max_bson_size = _query(
        options,
        collection_name,
        num_to_skip,
        num_to_return,
        query,
        field_selector,
        opts,
        check_keys)
    rid, msg = _compress(2004, op_query, ctx)
    return rid, msg, max_bson_size
990766
def query(self, query):
    '''
    '''
    self.logger.info('%s: query %s' % (self, query))
    return super(LoggingDatastore, self).query(query)
486797
def base_query
      query = ActionLog
              .where(visibility: %w(public-only all))
              .where(organization: options.fetch(:organization))

      query = query.where(user: options[:user]) if options[:user]

      query = filter_follows(query)

      query
    end
113299
def query(self, constraint, sortby=None, typenames=None, maxrecords=10, startposition=0):
        

        # run the raw query and get total
        # we want to exclude layers which are not valid, as it is done in the search engine
        if 'where' in constraint:  # GetRecords with constraint
            query = self._get_repo_filter(Layer.objects).filter(
                is_valid=True).extra(where=[constraint['where']], params=constraint['values'])
        else:  # GetRecords sans constraint
            query = self._get_repo_filter(Layer.objects).filter(is_valid=True)

        total = query.count()

        # apply sorting, limit and offset
        if sortby is not None:
            if 'spatial' in sortby and sortby['spatial']:  # spatial sort
                desc = False
                if sortby['order'] == 'DESC':
                    desc = True
                query = query.all()
                return [str(total),
                        sorted(query,
                               key=lambda x: float(util.get_geometry_area(getattr(x, sortby['propertyname']))),
                               reverse=desc,
                               )[startposition:startposition+int(maxrecords)]]
            else:
                if sortby['order'] == 'DESC':
                    pname = '-%s' % sortby['propertyname']
                else:
                    pname = sortby['propertyname']
                return [str(total),
                        query.order_by(pname)[startposition:startposition+int(maxrecords)]]
        else:  # no sort
            return [str(total), query.all()[startposition:startposition+int(maxrecords)]]
147080
def _query(self, *criterion):
        
        return self.session.query(
            self.model_class
        ).filter(
            *criterion
        )
892441
def _query(cmd):
    
    action = upkey(cmd).setResultsName("action")
    consist = upkey("consistent").setResultsName("consistent")
    order_by = (Suppress(upkey("order") + upkey("by")) + var).setResultsName("order_by")
    ordering = (upkey("desc") | upkey("asc")).setResultsName("order")
    save = (Suppress(upkey("save")) + filename).setResultsName("save_file")

    return (
        action
        + Optional(consist)
        + selection
        + from_
        + table
        + Optional(keys_in | where)
        + Optional(using)
        + Optional(limit)
        + Optional(scan_limit)
        + Optional(order_by)
        + Optional(ordering)
        + Optional(throttle)
        + Optional(save)
    )