/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.kernel;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;

import org.apache.openjpa.kernel.exps.AggregateListener;
import org.apache.openjpa.kernel.exps.Constant;
import org.apache.openjpa.kernel.exps.FilterListener;
import org.apache.openjpa.kernel.exps.QueryExpressions;
import org.apache.openjpa.kernel.exps.Value;
import org.apache.openjpa.lib.rop.ResultObjectProvider;
import org.apache.openjpa.lib.util.OrderedMap;
import org.apache.openjpa.lib.util.collections.LinkedMap;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;

/**
 * Component that executes queries against the datastore. For
 * expression-based queries, consider subclassing
 * {@link ExpressionStoreManagerQuery}.
 *
 * @author Abe White
 * @since 0.4.0
 */
public interface StoreQuery
    extends QueryOperations, Serializable {

    // linkedmap doesn't allow a size of 0, so use 1
    // This is deprecated
    LinkedMap EMPTY_PARAMS = new LinkedMap(1);
    OrderedMap<Object, Class<?>> EMPTY_ORDERED_PARAMS = new OrderedMap<>();
    ClassMetaData[] EMPTY_METAS = new ClassMetaData[0];
    String[] EMPTY_STRINGS = new String[0];
    Object[] EMPTY_OBJECTS = new Object[0];
    Class<?>[] EMPTY_CLASSES = new Class[0];
    boolean[] EMPTY_BOOLEANS = new boolean[0];

    /**
     * Return the query context that has been set.
     */
    QueryContext getContext();

    /**
     * Set the current query context. This will be called before use.
     */
    void setContext(QueryContext ctx);

    /**
     * This is invoked when the user or a facade creates a new query with
     * an object that the system does not recognize. Return true if
     * the object is recognized by the store, false otherwise.
     */
    boolean setQuery(Object query);

    /**
     * Return the standard filter listener for the given tag, or null.
     */
    FilterListener getFilterListener(String tag);

    /**
     * Return the standard filter listener for the given tag, or null.
     */
    AggregateListener getAggregateListener(String tag);

    /**
     * Create a new key for caching compiled query information. May be null.
     */
    Object newCompilationKey();

    /**
     * Create a new compilation for this query. May be null.
     */
    Object newCompilation();

    /**
     * Get the current compilation for this query. If it is null, do not create.
     */
    Object getCompilation();

    /**
     * Populate internal data from compilation.
     */
    void populateFromCompilation(Object comp);

    /**
     * Invalidate any internal compilation state.
     */
    void invalidateCompilation();

    /**
     * True if this query supports datastore execution, false if it
     * can only run in memory.
     */
    boolean supportsDataStoreExecution();

    /**
     * True if this query supports in-memory execution, false if it
     * can only run against the datastore.
     */
    boolean supportsInMemoryExecution();

    /**
     * Return an executor for in-memory execution of this query.
     * Executors must be cachable and thread safe. If this class returns
     * true from {@link #supportsAbstractExecutors}, the given metadata
     * will always be for the candidate class of this query, or possibly
     * null if the candidate class is not itself persistence capable (like
     * an interface or abstract base class). Otherwise, the given type will
     * be a mapped class.
     *
     * @param subs whether to include dependent mapped subclasses in the
     * results; independent subclasses should never be included
     */
    Executor newInMemoryExecutor(ClassMetaData meta, boolean subs);

    /**
     * Return an executor for datastore execution of this query.
     * Executors must be cachable and thread safe. If this class returns
     * true from {@link #supportsAbstractExecutors}, the given metadata
     * will always be for the candidate class of this query, or possibly
     * null if the candidate class is not itself persistence capable (like
     * an interface or abstract base class). Otherwise, the given type will
     * be a mapped class.
     *
     * @param subs whether to include dependent mapped subclasses in the
     * results; independent subclasses should never be included
     */
    Executor newDataStoreExecutor(ClassMetaData meta, boolean subs);

    /**
     * Return true if this query supports execution against abstract or
     * interface types. Returns false by default, meaning we will only
     * request executors for persistent classes. In this case, we will
     * automatically combine the results of the executors for all
     * implementing classes if we execute a query for an interface for
     * abstract type.
     */
    boolean supportsAbstractExecutors();

    /**
     * Whether this query requires a candidate class.
     */
    boolean requiresCandidateType();

    /**
     * Whether this query requires parameters to be declared.
     */
    boolean requiresParameterDeclarations();

    /**
     * Whether this query supports declared parameters.
     */
    boolean supportsParameterDeclarations();

    Object evaluate(Object value, Object ob, Object[] params,
        OpenJPAStateManager sm);

    /**
     * A query result range.
     */
    public static class Range {
        public long start = 0L;
        public long end = Long.MAX_VALUE;
        public boolean lrs = false;

        public Range() {
        }

        public Range(long start, long end) {
            this.start = start;
            this.end = end;
        }
    }

    /**
     * An executor provides a uniform interface to the mechanism for executing
     * either an in-memory or datastore query. In the common case, the
     * {@link #executeQuery} method will be called before other methods,
     * though this is not guaranteed.
     *
     * @author Marc Prud'hommeaux
     */
    public interface Executor {

        /**
         * Return the result of executing this query with the given parameter
         * values. If this query is a projection and this executor does not
         * pack results itself, each element of the returned result object
         * provider should be an object array containing the projection values.
         *
         * @param lrs true if the query result should be treated as a
         * large result set, assuming the query is not an
         * aggregate and does not have grouping
         * @see #isPacking
         */
        ResultObjectProvider executeQuery(StoreQuery q, Object[] params,
            Range range);

        /**
         * Deleted the objects that result from the execution of the
         * query, retuning the number of objects that were deleted.
         */
        Number executeDelete(StoreQuery q, Object[] params);

        /**
         * Updates the objects that result from the execution of the
         * query, retuning the number of objects that were updated.
         */
        Number executeUpdate(StoreQuery q, Object[] params);

        /**
         * Return a description of the commands that will be sent to
         * the datastore in order to execute the query.
         */
        String[] getDataStoreActions(StoreQuery q, Object[] params,
            Range range);

        /**
         * Validate components of query.
         */
        void validate(StoreQuery q);

        /**
         * Mutate the given range to set any range information stored in
         * the query string and/or parameters.
         */
        void getRange(StoreQuery q, Object[] params, Range range);

        /**
         * Extract the value of the <code>orderIndex</code>th ordering
         * expression in {@link Query#getOrderingClauses} from the
         * given result object. The result object will be an object from
         * the result object provider returned from {@link #executeQuery}.
         * This method is used when several result lists have to be merged
         * in memory. If this exeuctor's parent query supports executors on
         * abstract or interface classes, this method will not be used.
         *
         * @see StoreQuery#supportsAbstractExecutors
         */
        Object getOrderingValue(StoreQuery q, Object[] params,
            Object resultObject, int orderIndex);

        /**
         * Return the ordering direction for all ordering clauses, or empty
         * array if none.
         */
        boolean[] getAscending(StoreQuery q);

        /**
         * Return true if this executor packs projections into the result
         * class itself. Executors for query languages that allow projections
         * without result clauses must return true and perform the result
         * packing themselves.
         */
        boolean isPacking(StoreQuery q);

        /**
         * If this is not a projection but the candidate results are placed
         * into a result class with an alias, return that alias.
         */
        String getAlias(StoreQuery q);

        /**
         * Return the alias for each projection element, or empty array
         * if not a projection.
         */
        String[] getProjectionAliases(StoreQuery q);

        /**
         * Return the expected types of the projections used by this query,
         * or an empty array if not a projection.
         */
        Class<?>[] getProjectionTypes(StoreQuery q);

        /**
         * Return an array of all persistent classes used in this query, or
         * empty array if unknown.
         */
        ClassMetaData[] getAccessPathMetaDatas(StoreQuery q);

        /**
         * Returns the operation this executor is meant to execute.
         *
         * @see QueryOperations
         */
        int getOperation(StoreQuery q);

        /**
         * Return true if the compiled query is an aggregate.
         */
        boolean isAggregate(StoreQuery q);

        boolean isDistinct(StoreQuery q);

        /**
         * Whether the compiled query has grouping.
         */
        boolean hasGrouping(StoreQuery q);

        /**
         * Return a map of parameter names to types. The returned
         * {@link Map#entrySet}'s {@link Iterator} must return values in the
         * order in which they were declared or used.
         */
        OrderedMap<Object, Class<?>> getOrderedParameterTypes(StoreQuery q);

        /**
         * Return a map of parameter names to types. The returned
         * {@link Map#entrySet}'s {@link Iterator} must return values in the
         * order in which they were declared or used.<br>
         *
         * <B>Warning</B>: Deprecated. Use {@linkplain #getOrderedParameterTypes(StoreQuery)} instead.
         */
        @Deprecated LinkedMap getParameterTypes(StoreQuery q);

        /**
         * Return an array from the given user parameter values.
         * The array ordering is same as what this executor expects for its
         * executeXXX() methods as its Object[] parameters.
         * If the given userParams is null or empty return an empty array
         * rather than null.
         *
         * @return array with parameter values ordered in the same way as this
         * receiver's executeXXX() method expects.
         *
         * @since 2.0.0
         */
        Object[] toParameterArray(StoreQuery q, Map<?,?> userParams);

        /**
         * Returns the result class, if any.
         */
        Class<?> getResultClass(StoreQuery q);

        ResultShape<?> getResultShape(StoreQuery q);

        /**
         * Return a map of {@link FieldMetaData} to update
         * {@link Constant}s, in cases where this query is for a bulk update.
	 	 */
		Map<FieldMetaData,Value> getUpdates (StoreQuery q);

        /**
         * Return the parsed query expressions for our candidate types.
         * The expressions are available only after query has been parsed.
         *
         * @since 2.0.0
         */
		QueryExpressions[] getQueryExpressions();
	}
}
