/*
 * 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.jdbc.meta;

import java.sql.SQLException;

import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.kernel.JDBCStore;
import org.apache.openjpa.jdbc.sql.Joins;
import org.apache.openjpa.jdbc.sql.Result;
import org.apache.openjpa.jdbc.sql.RowManager;
import org.apache.openjpa.jdbc.sql.SQLBuffer;
import org.apache.openjpa.jdbc.sql.Select;
import org.apache.openjpa.jdbc.sql.SelectExecutor;
import org.apache.openjpa.kernel.OpenJPAStateManager;

/**
 * Maps a persistent field to the relational schema.
 *
 * @author Abe White
 * @since 0.4.0
 */
public interface FieldStrategy
    extends Strategy {

    /**
     * Set the class mapping using this strategy. This will be called before
     * use.
     */
    void setFieldMapping(FieldMapping owner);

    /**
     * Return whether this mapping can perform the given select type.
     * Return 0 if the given type is not supported. If the given type is an
     * eager parallel type, return the number of UNIONed selects necessary
     * to fetch the data. Otherwise, return any positive number if the type
     * is supported. The given state manager may be null if selecting
     * multiple instances.
     *
     * @see Select
     */
    int supportsSelect(Select sel, int type, OpenJPAStateManager sm,
        JDBCStore store, JDBCFetchConfiguration fetch);

    /**
     * Fill in parallel eager select for related objects.
     *
     * @see #select
     */
    void selectEagerParallel(SelectExecutor sel, OpenJPAStateManager sm,
        JDBCStore store, JDBCFetchConfiguration fetch, int eagerMode);

    /**
     * Fill in joined select to related objects.
     *
     * @see #select
     */
    void selectEagerJoin(Select sel, OpenJPAStateManager sm,
        JDBCStore store, JDBCFetchConfiguration fetch, int eagerMode);

    /**
     * Whether the eager joins or selects used by this field are to-many.
     */
    boolean isEagerSelectToMany();

    /**
     * Select the virtual row columns of this mapping.
     *
     * @param sel the select to add to
     * @param sm the instance being loaded, or null if not
     * initialized yet or selecting for multiple instances
     * @param store the current store manager
     * @param fetch fetch configuration
     * @param eagerMode the eager fetch mode to use; this may be more
     * restrictive than the mode of the fetch configuration
     * @return &gt; 0 if this mapping requires the selected data
     * (if any), 0 if it selected data but does not
     * require it, or &lt; 0 if no data was selected
     */
    int select(Select sel, OpenJPAStateManager sm, JDBCStore store,
        JDBCFetchConfiguration fetch, int eagerMode);

    /**
     * Load the batched eager result.
     *
     * @param res originally the {@link Result} to load from, but this
     * method may return a processed result form that will be
     * passed to subsequent calls
     */
    Object loadEagerParallel(OpenJPAStateManager sm, JDBCStore store,
        JDBCFetchConfiguration fetch, Object res)
        throws SQLException;

    /**
     * Load the joined eager result.
     */
    void loadEagerJoin(OpenJPAStateManager sm, JDBCStore store,
        JDBCFetchConfiguration fetch, Result res)
        throws SQLException;

    /**
     * Load virtual row data; the given result is not guaranteed to contain
     * data for this field, so the field mapping should make sure the
     * result contains its needed column data before loading.
     */
    void load(OpenJPAStateManager sm, JDBCStore store,
        JDBCFetchConfiguration fetch, Result res)
        throws SQLException;

    /**
     * Load secondary data using a connection from the store manager.
     */
    void load(OpenJPAStateManager sm, JDBCStore store,
        JDBCFetchConfiguration fetch)
        throws SQLException;

    ///////////
    // Queries
    ///////////

    /**
     * Translate the given external field value to its datastore equivalent.
     * If the mapping occupies multiple columns in the datastore, return an
     * object array, else return a single object. Simply returns the given
     * object by default.
     */
    Object toDataStoreValue(Object val, JDBCStore store);

    /**
     * Translate the given key value to its datastore equivalent. If the
     * mapping occupies multiple columns in the datastore, return an object
     * array, else return a single object. Simply returns the given object by
     * default.
     */
    Object toKeyDataStoreValue(Object val, JDBCStore store);

    /**
     * Append a where clause to the given statement checking if this value
     * is empty. Appends impossible-to-satisfy SQL by default.
     */
    void appendIsEmpty(SQLBuffer sql, Select sel, Joins joins);

    /**
     * Append a where clause to the given statement checking if this value
     * is not empty. Appends always-satisfied SQL by default.
     */
    void appendIsNotEmpty(SQLBuffer sql, Select sel, Joins joins);

    /**
     * Append a where clause to the given statement checking if this value
     * is null.
     */
    void appendIsNull(SQLBuffer sql, Select sel, Joins joins);

    /**
     * Append a where clause to the given statement checking if this value
     * is not null.
     */
    void appendIsNotNull(SQLBuffer sql, Select sel, Joins joins);

    /**
     * Append a where clause to the given statement checking the size
     * of the value.
     */
    void appendSize(SQLBuffer sql, Select sel, Joins joins);

    /**
     * Append the ordered column alias to the given statement.
     */
    void appendIndex(SQLBuffer sql, Select sel, Joins joins);

    /**
     * Append the entity discriminator value to the given statement.
     */
    void appendType(SQLBuffer sql, Select sel, Joins joins);

    /**
     * Join this value to the class table. Does nothing by default.
     */
    Joins join(Joins joins, boolean forceOuter);

    /**
     * Join the key value to the class table. Does nothing by default.
     */
    Joins joinKey(Joins joins, boolean forceOuter);

    /**
     * Join this value's table to the table for the related first class object
     * type, if any. Does nothing by default.
     *
     * @param traverse if true, throw proper exception if it is not
     * possible for this mapping to traverse into the related type
     */
    Joins joinRelation(Joins joins, boolean forceOuter,
        boolean traverse);

    /**
     * Join this value's table to the table for the related first class object
     * key type, if any. Does nothing by default.
     *
     * @param traverse if true, throw proper exception if it is not
     * possible for this mapping to traverse into the related type
     */
    Joins joinKeyRelation(Joins joins, boolean forceOuter,
        boolean traverse);

    /**
     * Load this field value using the given result. The result
     * will contain the columns from {@link ValueMapping#getColumns}.
     */
    Object loadProjection(JDBCStore store, JDBCFetchConfiguration fetch,
        Result res, Joins joins)
        throws SQLException;

    /**
     * Load this field's key value using the given result. The result will
     * contain the columns from {@link ValueMapping#getColumns}.
     */
    Object loadKeyProjection(JDBCStore store,
        JDBCFetchConfiguration fetch, Result res, Joins joins)
        throws SQLException;

    //////////////
    // Versioning
    //////////////

    /**
     * Return true if this field can be used as part of a state image for
     * optimistic locking.
     */
    boolean isVersionable();

    /**
     * Add a WHERE condition to the row for this field such that the field's
     * current DB value must equal the given previous value. Only versionable
     * mappings must implement this method meaningfully.
     */
    void where(OpenJPAStateManager sm, JDBCStore store, RowManager rm,
        Object prevValue)
        throws SQLException;
}
