All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.impl.api.query.PreparedStatementImpl Maven / Gradle / Ivy

/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.api.query;

import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import oracle.kv.impl.api.KVStoreImpl;
import oracle.kv.impl.api.table.FieldDefFactory;
import oracle.kv.impl.api.table.FieldDefImpl;
import oracle.kv.impl.api.table.FieldDefSerialization;
import oracle.kv.impl.api.table.FieldMap;
import oracle.kv.impl.api.table.PrimaryKeyImpl;
import oracle.kv.impl.api.table.RecordDefImpl;
import oracle.kv.impl.async.AsyncIterationHandleImpl;
import oracle.kv.impl.query.QueryException;
import oracle.kv.impl.query.compiler.QueryControlBlock;
import oracle.kv.impl.query.compiler.StaticContext.VarInfo;
import oracle.kv.impl.query.runtime.PlanIter;
import oracle.kv.impl.topo.PartitionId;
import oracle.kv.impl.topo.RepGroupId;
import oracle.kv.impl.util.SerialVersion;
import oracle.kv.impl.util.SerializationUtil;

import oracle.kv.StatementResult;
import oracle.kv.query.BoundStatement;
import oracle.kv.query.ExecuteOptions;
import oracle.kv.query.PreparedStatement;
import oracle.kv.table.FieldDef;
import oracle.kv.table.FieldValue;
import oracle.kv.table.PrimaryKey;
import oracle.kv.table.RecordDef;
import oracle.kv.table.RecordValue;

/**
 * Implementation of a PreparedStatement. This class contains the query plan,
 * along with enough information to construct a runtime context in which the
 * query can be executed (RuntimeControlBlock).
 *
 * An instance of PreparedStatementImpl is created by CompilerAPI.prepare(),
 * after the query has been compiled.
 */
public class PreparedStatementImpl
    implements PreparedStatement,
               InternalStatement {

    /**
     * The type of distribution for the query.
     */
    public enum DistributionKind {
        SINGLE_PARTITION, /** the query goes to a single partition */
        ALL_PARTITIONS,   /** the query goes to all partitions */
        ALL_SHARDS,       /** the query goes to all shards (it uses an index) */
        UNKNOWN           /** the distribution is UNKNOWN */
    }

    /*
     * The query plan
     */
    private final PlanIter queryPlan;

    /*
     * The type of the result
     */
    private final RecordDefImpl resultDef;

    /*
     * Whether the query result should be wrapped in a one-field record
     */
    private boolean wrapResultInRecord;

    /*
     * The number of registers required to run the plan
     */
    private final int numRegisters;

    /*
     * The number of iterators in the plan
     */
    private final int numIterators;

    /*
     * externalVars maps the name of each external var declared in the query
     * to the numeric id and data type for that var.
     */
    private final Map externalVars;

    /*
     * The DistributionKind
     */
    private final DistributionKind distributionKind;

    /*
     * The PartitionId for SINGLE_PARTITION distribution, null or
     * PartitionID.NULL_ID otherwise.
     */
    private final PartitionId partitionId;

    /*
     * The PrimaryKey for SINGLE_PARTITION distribution. It is only valid for
     * SINGLE_PARTITION distribution. It is null or empty otherwise.
     */
    private final PrimaryKeyImpl shardKey;

    private final long tableId;

    private final int tableVersion;

    private final String tableName;

    /*
     * Needed for unit testing only
     */
    private QueryControlBlock qcb;

    public PreparedStatementImpl(
        PlanIter queryPlan,
        FieldDefImpl resultDef,
        int numRegisters,
        int numIterators,
        Map externalVars,
        QueryControlBlock qcb) {

        this.queryPlan = queryPlan;
        this.numRegisters = numRegisters;
        this.numIterators = numIterators;
        this.externalVars = externalVars;
        this.qcb = qcb;
        this.distributionKind = qcb.getPushedDistributionKind();
        this.tableId = qcb.getTargetTableId();
        this.tableVersion = qcb.getTargetTableVersion();
        this.tableName = qcb.getTargetTableName();
        PrimaryKeyImpl pkey = qcb.getPushedPrimaryKey();

        if (pkey != null && pkey.isEmpty()) {
            this.shardKey = null;
        } else {
            this.shardKey = pkey;
        }

        if (shardKey != null &&
            distributionKind == DistributionKind.SINGLE_PARTITION) {
            partitionId = shardKey.getPartitionId(qcb.getStore());
        } else {
            partitionId = PartitionId.NULL_ID;
        }

        if (qcb.wrapResultInRecord()) {

            wrapResultInRecord = true;

            String fname = qcb.getResultColumnName();
            if (fname == null) {
                fname = "Column_1";
            }

            FieldMap fieldMap = new FieldMap();
            fieldMap.put(fname,
                         resultDef,
                         true/*nullable*/,
                         null/*defaultValue*/);

            this.resultDef = FieldDefFactory.createRecordDef(fieldMap, null);

        } else {
            wrapResultInRecord = false;
            this.resultDef = (RecordDefImpl)resultDef;
        }
    }

    @Override
    public RecordDef getResultDef() {
        return resultDef;
    }

    boolean wrapResultInRecord() {
        return wrapResultInRecord;
    }

    @Override
    public Map getVariableTypes() {
        return getExternalVarsTypes();
    }

    @Override
    public FieldDef getVariableType(String variableName) {
        return getExternalVarType(variableName);
    }

    @Override
    public BoundStatement createBoundStatement() {
        return new BoundStatementImpl(this);
    }

    public boolean hasSort() {
        return qcb.hasSort();
    }

    /*
     * Needed for unit testing only
     */
    public QueryControlBlock getQCB() {
        return qcb;
    }

    public PlanIter getQueryPlan() {
    	return queryPlan;
    }

    public int getNumRegisters() {
        return numRegisters;
    }

    public int getNumIterators() {
        return numIterators;
    }

    public Map getExternalVarsTypes() {

        Map varsMap = new HashMap();

        if (externalVars == null) {
            return varsMap;
        }

        for (Map.Entry entry : externalVars.entrySet()) {
            String varName = entry.getKey();
            VarInfo vi = entry.getValue();
            varsMap.put(varName, vi.getType().getDef());
        }

        return varsMap;
    }

    boolean hasExternalVars() {
        return (externalVars != null && !externalVars.isEmpty());
    }

    public FieldDef getExternalVarType(String name) {

        if (externalVars == null) {
            return null;
        }

        VarInfo vi = externalVars.get(name);
        if (vi != null) {
            return vi.getType().getDef();
        }

        return null;
    }

    long getTableId() {
        return tableId;
    }

    public int getTableVersion() {
        return tableVersion;
    }

    public String getTableName() {
        return tableName;
    }

    /**
     * Convert the map of external vars (maping names to values) to an array
     * with the values only. The array is indexed by an internally assigned id
     * to each external variable. This method also checks that all the external
     * vars declared in the query have been bound.
     */
    public FieldValue[] getExternalVarsArray(Map vars) {

        if (externalVars == null) {
            assert(vars.isEmpty());
            return null;
        }

        int count = 0;
        for (Map.Entry entry : externalVars.entrySet()) {
            String name = entry.getKey();
            ++count;

            if (vars.get(name) == null) {
                throw new IllegalArgumentException(
                    "Variable " + name + " has not been bound");
            }
        }

        FieldValue[] array = new FieldValue[count];

        count = 0;
        for (Map.Entry entry : vars.entrySet()) {
            String name = entry.getKey();
            FieldValue value = entry.getValue();

            VarInfo vi = externalVars.get(name);
            if (vi == null) {
                throw new IllegalStateException(
                    "Variable " + name + " does not appear in query");
            }

            array[vi.getId()] = value;
            ++count;
        }

        assert(count == array.length);
        return array;
    }

    @Override
    public String toString() {
        return queryPlan.display();
    }

    @Override
    public StatementResult executeSync(
        KVStoreImpl store,
        ExecuteOptions options) {

        if (options == null) {
            options = new ExecuteOptions();
        }

        return new QueryStatementResultImpl(
            store.getTableAPIImpl(), options, this, false /* async */);
    }

    @Override
    public AsyncIterationHandleImpl
        executeAsync(KVStoreImpl store,
                     ExecuteOptions options) {

        if (options == null) {
            options = new ExecuteOptions();
        }

        final QueryStatementResultImpl result =
            new QueryStatementResultImpl(store.getTableAPIImpl(), options,
                                         this, true /* async */);
        return result.getExecutionHandle();
    }

    /*
     * These methods may eventually be part of the public PreparedStatement
     * interface. If done, DistributionKind would move there as well.
     */

    /**
     * Returns the DistributionKind for the prepared query
     */
    public DistributionKind getDistributionKind() {
        return distributionKind;
    }

    /**
     * Returns the shard key for the prepared query. It is only valid if
     * DistributionKind is SINGLE_PARTITION. Otherwise it returns null.
     */
    public PrimaryKey getShardKey() {
        return shardKey;
    }

    /**
     * Returns the PartitionId for queries with DistributionKind of
     * SINGLE_PARTITION. NOTE: for queries with variables that are part of the
     * shard key this method will return an incorrect PartitionId. This is
     * because the query compiler puts a placeholder value in the PrimaryKey
     * that is a valid value for the type. TBD: detect this and throw an
     * exception if this method is called on such a query.
     */
    public PartitionId getPartitionId() {
        return partitionId;
    }

    /**
     * Not part of the public PreparedStatement interface available to
     * external clients. This method is employed when the Oracle NoSQL DB
     * Hive/BigDataSQL integration mechanism is used to process a query,
     * and disjoint partition sets are specified for each split.
     */
    public StatementResult executeSyncPartitions(
        KVStoreImpl store,
        ExecuteOptions options,
        final Set partitions) {

        return new QueryStatementResultImpl(
            store.getTableAPIImpl(), options, this, false /* async */,
            partitions, null);
    }

    /**
     * Not part of the public PreparedStatement interface available to
     * external clients. This method is employed when the Oracle NoSQL DB
     * Hive/BigDataSQL integration mechanism is used to process a query,
     * and disjoint shard sets are specified for each split.
     */
    public StatementResult executeSyncShards(
        final KVStoreImpl store,
        final ExecuteOptions options,
        final Set shards) {

        return new QueryStatementResultImpl(
            store.getTableAPIImpl(), options, this, false /* async */, null,
            shards);
    }

    /**
     * Serialize a prepared statement into a byte array for use by remote
     * callers such as the cloud driver.
     * The information is:
     *   serialVersion
     *   distributionKind
     *   table name
     *   table id
     *   externalVars
     *   client-side query plan (to ReceiveIter)
     *   numIterators and numRegisters
     *   resultDef
     *   boolean queryReturnsRecord
     */
    public byte[] toByteArray()
        throws IOException {

        assert qcb != null;
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final DataOutput out = new DataOutputStream(baos);

        toByteArray(out);
        return baos.toByteArray(); /* is there a more efficient way? */
    }

    /**
     * Serialize to the provided object
     */
    public void toByteArray(DataOutput out)
        throws IOException {

        /*
         * The current version of the client serves as the serial version
         * for the entire prepared query
         */
        final short version = SerialVersion.CURRENT;
        out.writeShort(version);
        out.writeByte((distributionKind != null) ?
            distributionKind.ordinal() : -1);
        SerializationUtil.writeNonNullString(out, version, tableName);
        SerializationUtil.writePackedLong(out, tableId);
        SerializationUtil.writePackedInt(out, tableVersion);

        /* external variables, may be null */
        writeExternalVars(out, version);

        /* Query Plan */
        PlanIter.serializeIter(queryPlan, out, version);

        /* numIterators and numRegisters */
        out.writeInt(numIterators);
        out.writeInt(numRegisters);

        FieldDefSerialization.writeFieldDef(resultDef, out, version);
        out.writeBoolean(wrapResultInRecord);
    }

    public PreparedStatementImpl(DataInput in)
        throws IOException {

        try {
            final short version = in.readShort();
            if (version < SerialVersion.V16 || version > SerialVersion.CURRENT) {
                raiseDeserializeError("unexpected version value: " + version);
            }

            byte ordinal = in.readByte();
            if (ordinal != (byte)(-1)) {
                if (ordinal < 0 || ordinal > DistributionKind.values().length) {
                    raiseDeserializeError("unexpected value for DistributionKind");
                }
                distributionKind = DistributionKind.values()[ordinal];
            } else {
                distributionKind = null;
            }

            tableName = SerializationUtil.readString(in, version);
            if (tableName == null) {
                raiseDeserializeError("tableName should not be null");
            }
            tableId = SerializationUtil.readPackedLong(in);
            if (tableId < 0) {
                raiseDeserializeError("tableId should not be negative value");
            }
            tableVersion = SerializationUtil.readPackedInt(in);
            if (tableVersion < 0) {
                raiseDeserializeError("tableVersion should not be negative value");
            }

            /* external variables, if any */
            externalVars = readExternalVars(in, version);

            /* Query plan */
            queryPlan = PlanIter.deserializeIter(in, version);
            if (queryPlan == null) {
                raiseDeserializeError("query plan is null");
            }

            /* numIterators and numRegisters */
            numIterators = in.readInt();
            if (numIterators < 1) {
                raiseDeserializeError
                    ("numIterators should not be 0 or negative value");
            }
            numRegisters = in.readInt();
            if (numRegisters < 0) {
                raiseDeserializeError
                    ("numRegisters should not be 0 or negative value");
            }

            resultDef = (RecordDefImpl)
                FieldDefSerialization.readFieldDef(in, version);

            wrapResultInRecord = in.readBoolean();

            /* to make the compiler happy about final members that aren't used */
            partitionId = null;
            shardKey = null;
        } catch (QueryException qe) {
            throw qe.getIllegalArgument();
        } catch (RuntimeException re) {
            throw new IllegalArgumentException
            ("Read PreparedStatement failed: " + re);
        }
    }


    private void raiseDeserializeError(String msg) {
        throw new QueryException
            ("Deserializing PreparedStatement failed: " + msg);
    }

    private void writeExternalVars(DataOutput out, short version)
        throws IOException {

        if (externalVars == null) {
            out.writeInt(0);
        }
        out.writeInt(externalVars.size());
        for (Map.Entry entry : externalVars.entrySet()) {
            SerializationUtil.writeNonNullString(out, version, entry.getKey());
            VarInfo info = entry.getValue();
            out.writeInt(info.getId());
            PlanIter.serializeExprType(info.getType(), out, version);
        }
    }

    private Map readExternalVars(DataInput in, short version)
        throws IOException {
        int numVars = in.readInt();
        if (numVars == 0) {
            return null;
        }

        if (numVars < 0) {
            raiseDeserializeError("Unexpected negtive value: " + numVars);
        }
        try {
            Map vars = new HashMap(numVars);
            for (int i = 0; i < numVars; i++) {
                String name = SerializationUtil.readString(in, version);
                VarInfo info = VarInfo.createVarInfo(
                    in.readInt(), // id
                    PlanIter.deserializeExprType(in, version));
                vars.put(name, info);
            }
            return vars;
        } catch (RuntimeException re) {
            raiseDeserializeError(re.getMessage());
        }
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy