oracle.kv.hadoop.table.TableRecordReaderBase.java Source code

Java tutorial

Introduction

Here is the source code for oracle.kv.hadoop.table.TableRecordReaderBase.java

Source

/*-
 * Copyright (C) 2011, 2017 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.hadoop.table;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import oracle.kv.Consistency;
import oracle.kv.Direction;
import oracle.kv.FaultException;
import oracle.kv.KVSecurityConstants;
import oracle.kv.KVSecurityException;
import oracle.kv.KVStore;
import oracle.kv.KVStoreConfig;
import oracle.kv.KVStoreFactory;
import oracle.kv.ParamConstant;
import oracle.kv.StatementResult;
import oracle.kv.impl.api.KVStoreImpl;
import oracle.kv.impl.api.table.TableAPIImpl;
import oracle.kv.impl.security.util.KVStoreLogin;
import oracle.kv.impl.topo.RepGroupId;
import oracle.kv.query.ExecuteOptions;
import oracle.kv.table.FieldDef;
import oracle.kv.table.FieldRange;
import oracle.kv.table.IndexKey;
import oracle.kv.table.MultiRowOptions;
import oracle.kv.table.PrimaryKey;
import oracle.kv.table.RecordValue;
import oracle.kv.table.Row;
import oracle.kv.table.Table;
import oracle.kv.table.TableIterator;
import oracle.kv.table.TableIteratorOptions;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;

/**
 * Abstract parent class for RecordReader implementations used to read
 * table rows from an InputSplit. This class provides common, useful,
 * and/or convenient mechanisms that are intended to be shared by
 * subclasses of this class.
 * <p>
 * @since 3.1
 */
abstract class TableRecordReaderBase<K, V> extends RecordReader<K, V> {

    private static final Log LOG = LogFactory.getLog("oracle.kv.hadoop.table.TableRecordReaderBase");

    private static final String FILE_SEP = System.getProperty("file.separator");
    private static final String USER_SECURITY_DIR = System.getProperty("user.dir") + FILE_SEP
            + "TABLE_RECORD_READER_SECURITY_DIR";

    private static String[] requiredRangeNames = { "name", "start", "end" };

    private KVStore kvstore;
    private Table kvTable;
    private String tableName;

    /* Iterator input parameters, set in initialize() */
    private PrimaryKey primaryKey;
    private IndexKey indexKey;
    private MultiRowOptions rowOpts;
    private TableIteratorOptions itrOpts = null;
    private ExecuteOptions execOpts = null;

    /* List of remaining partitions to read from if partitionSet iteration. */
    private List<Set<Integer>> partitionSets;

    /* Initial number of partitionSets if using partitionSet iteration. */
    private int startNPartitionSets;

    /*
     * Fields related to how a query will be executed; index scan via IndexKey,
     * table scan via PrimaryKey, or NoSQL DB native query.
     */
    private int queryBy = TableInputSplit.QUERY_BY_PRIMARY_ALL_PARTITIONS;
    private String whereClause = null;
    private String nativeQuery = null;

    /* Set referencing ids of the shards (RepGroups) for index iteratation. */
    private Set<RepGroupId> shardSet;

    /* Initial number of shards when using index (shard) iteration. */
    private int startNShards;

    /*
     * The current iterator. Note that the elements of both IndexKey and
     * PrimaryKey based table iterators are instances of Row; whereas the
     * elements of the table iterator returned when executing a NoSQL DB
     * native query are RecordValue instances from which a Row must be
     * created.
     */
    private TableIterator<? extends RecordValue> iter;
    private StatementResult statementResult;

    protected Row current;

    /**
     * Called once at initialization.
     * @param split the split that defines the range of records to read
     * @param context the information about the task
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {

        final TableInputSplit tableInputSplit = (TableInputSplit) split;
        tableName = tableInputSplit.getTableName();

        LOG.debug("split = " + tableInputSplit);

        final String kvStoreName = tableInputSplit.getKVStoreName();
        final String[] kvHelperHosts = tableInputSplit.getKVHelperHosts();

        if (kvstore == null) {

            final KVStoreConfig storeConfig = new KVStoreConfig(kvStoreName, kvHelperHosts);

            final String kvStoreSecurityFile = createLocalKVSecurity(tableInputSplit.getSecurityLogin(),
                    tableInputSplit.getSecurityTrust());
            storeConfig.setSecurityProperties(KVStoreLogin.createSecurityProperties(kvStoreSecurityFile));

            /*
             * If the same Hive CLI session is used to run queries that must
             * connect to different KVStores where one store is non-secure and
             * the other(s) is secure, then since the security information is
             * stored in the state of the splits, if an attempt is made to
             * connect to the non-secure store and that security information
             * is non-null, then a FaultException will be thrown when
             * attempting to connect to the store. This is because that
             * non-null security information will cause the connection
             * mechanism to attempt a secure connection instead of a
             * non-secure connection. To address this, FaultException is
             * caught and the connection attempt is retried with no security
             * information.
             */
            RuntimeException cause = null;
            try {
                kvstore = KVStoreFactory.getStore(storeConfig, tableInputSplit.getSecurityCredentials(), null);
            } catch (FaultException fe) {
                cause = fe;
            } catch (KVSecurityException kse) {
                cause = kse;
            }
            if (cause != null) {
                if (tableInputSplit.getSecurityCredentials() != null) {
                    final KVStoreConfig kvStoreConfigNonSecure = new KVStoreConfig(kvStoreName, kvHelperHosts);
                    kvStoreConfigNonSecure.setSecurityProperties(KVStoreLogin.createSecurityProperties(null));
                    kvstore = KVStoreFactory.getStore(kvStoreConfigNonSecure);
                } else {
                    throw cause;
                }
            }
        }

        this.kvTable = kvstore.getTableAPI().getTable(tableName);
        if (kvTable == null) {
            final String msg = "Store does not contain table [name=" + tableName + "]";
            LOG.error(msg);
            throw new RuntimeException(msg);
        }
        primaryKey = getPrimaryKey(kvTable, tableInputSplit.getPrimaryKeyProperty());

        /* Construct MultiRowOptions */
        rowOpts = new MultiRowOptions(getFieldRange(kvTable, tableInputSplit.getFieldRangeProperty()));

        /* Construct TableIteratorOptions */
        final Direction direction = tableInputSplit.getDirection();
        final Consistency consistency = tableInputSplit.getConsistency();
        final long timeout = tableInputSplit.getTimeout();
        final TimeUnit timeoutUnit = tableInputSplit.getTimeoutUnit();
        final int maxRequests = tableInputSplit.getMaxRequests();
        final int batchSize = tableInputSplit.getBatchSize();

        if ((timeout >= 0 && timeoutUnit != null) || maxRequests >= 0 || batchSize >= 0 || direction != null) {

            execOpts = new ExecuteOptions();

            execOpts = execOpts.setConsistency(consistency);

            if (timeout >= 0 && timeoutUnit != null) {
                execOpts = execOpts.setTimeout(timeout, timeoutUnit);
            }

            if (maxRequests >= 0) {
                execOpts = execOpts.setMaxConcurrentRequests(maxRequests);
            }

            if (batchSize >= 0) {
                execOpts = execOpts.setResultsBatchSize(batchSize);
            }

            if (direction != null) {
                itrOpts = execOpts.createTableIteratorOptions(direction);
            }
        }

        partitionSets = tableInputSplit.getPartitionSets();
        startNPartitionSets = partitionSets.size();

        queryBy = tableInputSplit.getQueryBy();
        whereClause = tableInputSplit.getWhereClause();
        nativeQuery = "SELECT * FROM " + tableName + " WHERE " + whereClause;

        shardSet = tableInputSplit.getShardSet();
        startNShards = shardSet.size();

        /* Debug: log how the query will be satisfied. */
        if (LOG.isDebugEnabled()) {

            if (queryBy == TableInputSplit.QUERY_BY_INDEX) {
                LOG.debug("pushing index key (scanning ALL_SHARDS)");
            } else if (queryBy == TableInputSplit.QUERY_BY_PRIMARY_ALL_PARTITIONS) {
                LOG.debug("pushing primary key (scanning ALL_PARTITIONS)");
            } else if (queryBy == TableInputSplit.QUERY_BY_PRIMARY_SINGLE_PARTITION) {
                LOG.debug("pushing primary key (scanning SINGLE_PARTITION)");
            } else {
                if (queryBy == TableInputSplit.QUERY_BY_ONQL_ALL_PARTITIONS) {
                    LOG.debug("pushing native query (scanning " + "ALL_PARTITIONS) - [" + nativeQuery + "]");
                } else if (queryBy == TableInputSplit.QUERY_BY_ONQL_SINGLE_PARTITION) {
                    LOG.debug("pushing native query (scanning " + "SINGLE_PARTITION) - [" + nativeQuery + "]");

                } else if (queryBy == TableInputSplit.QUERY_BY_ONQL_SHARDS) {
                    LOG.debug("pushing native query (scanning " + "ALL_SHARDS) - [" + nativeQuery + "]");
                } else {
                    LOG.debug("pushing native query (using default, all " + "partitions in 1 split) - ["
                            + nativeQuery + "]");
                }
            }
        }
    }

    /**
     * Read the next row.
     * @return true if a key/value pair was read
     */
    @Override
    public boolean nextKeyValue() {

        try {
            if (iter == null) {
                iter = getNextIterator();
            }

            while (iter != null) {

                if (iter.hasNext()) {

                    final RecordValue nextRecord = iter.next();
                    if (nextRecord instanceof Row) {
                        current = (Row) nextRecord;
                    } else {
                        current = kvTable.createRow(nextRecord);
                    }
                    LOG.trace("next row: " + current);
                    return true;
                }

                current = null;

                if (statementResult != null) {
                    statementResult.close();
                    statementResult = null;
                }

                iter = getNextIterator();
            }

            LOG.trace("exit loop: return false");
            return false;

        } catch (Exception e) {
            LOG.error("Exception while iterating table [" + e + "]", e);
            return false;
        }
    }

    private TableIterator<? extends RecordValue> getNextIterator() {

        /*
         * 1. When the query is IndexKey based, each split is mapped to one
         *    shard. In this case, return a TableIterator that returns only
         *    the rows associated with the given IndexKey and corresponding
         *    shards.
         */
        if (queryBy == TableInputSplit.QUERY_BY_INDEX) {

            if (indexKey != null) {

                if (shardSet.isEmpty()) {
                    return null;
                }

                /*
                 * Each split mapped to one shard means one record reader per
                 * shard. As a result, the shardSet will contain only one
                 * element; the single shard from the associated split.
                 */
                final Set<RepGroupId> repGroupIds = new HashSet<RepGroupId>(shardSet);
                shardSet.clear();

                return ((TableAPIImpl) kvstore.getTableAPI()).tableIterator(indexKey, rowOpts, itrOpts,
                        repGroupIds);
            }
        }

        /*
         * 2. When the query is PrimaryKey (or ShardKey) based, each split is
         *    mapped to a set of partitions. If PrimaryKey based, the union
         *    of all partition sets from each split will equal the set of all
         *    partitions from the store; and a TableIterator that returns
         *    only the rows associated with the given key and corresponding
         *    partitions will be returned. If the query is ShardKey based,
         *    then the associated set of partitions will contain only one
         *    element; the partition in which the ShardKey is located.
         */
        if (queryBy == TableInputSplit.QUERY_BY_PRIMARY_ALL_PARTITIONS
                || queryBy == TableInputSplit.QUERY_BY_PRIMARY_SINGLE_PARTITION) {

            if (partitionSets.isEmpty()) {
                return null;
            }

            final Set<Integer> partitions = partitionSets.remove(0);
            assert partitions.size() > 0;

            return ((TableAPIImpl) kvstore.getTableAPI()).tableIterator(primaryKey, rowOpts, itrOpts, partitions);
        }

        /*
         * 3. When the query will be satisfied via a partition based NATIVE
         *    NoSQL DB ONQL iterator.
         */
        if (queryBy == TableInputSplit.QUERY_BY_ONQL_ALL_PARTITIONS
                || queryBy == TableInputSplit.QUERY_BY_ONQL_SINGLE_PARTITION) {

            return getNextOnqlIteratorPartitions(nativeQuery);
        }

        /*
         * 4. All possible values of queryBy have been handled except for
         *    TableInputSplit.QUERY_BY_ONQL_SHARDS; which means the query
         *    will be satisfied via a shard based NATIVE NoSQL DB ONQL
         *    iterator.
         */
        return getNextOnqlIteratorShards(nativeQuery);
    }

    private TableIterator<? extends RecordValue> getNextOnqlIteratorPartitions(final String query) {

        if (partitionSets.isEmpty()) {
            return null;
        }

        final Set<Integer> partitions = partitionSets.remove(0);
        assert partitions.size() > 0;

        statementResult = ((KVStoreImpl) kvstore).executeSyncPartitions(query, execOpts, partitions);

        if (statementResult == null) {
            return null;
        }
        return statementResult.iterator();
    }

    private TableIterator<? extends RecordValue> getNextOnqlIteratorShards(final String query) {
        if (shardSet.isEmpty()) {
            return null;
        }
        final Set<RepGroupId> repGroupIds = new HashSet<RepGroupId>(shardSet);
        shardSet.clear();

        statementResult = ((KVStoreImpl) kvstore).executeSyncShards(query, execOpts, repGroupIds);

        if (statementResult == null) {
            return null;
        }
        return statementResult.iterator();
    }

    /**
     * The current progress of the record reader through its data.
     *
     * @return a number between 0.0 and 1.0 that is the fraction of the data
     * read
     */
    @Override
    public float getProgress() {

        if (queryBy == TableInputSplit.QUERY_BY_INDEX) {
            return (shardSet == null) ? 0 : (float) (startNShards - shardSet.size()) / (float) startNShards;
        }
        return (partitionSets == null) ? 0
                : (float) (startNPartitionSets - partitionSets.size()) / (float) startNPartitionSets;
    }

    /**
     * Close the record reader.
     */
    @Override
    public void close() throws IOException {
        LOG.trace("close iterator and store");
        if (iter != null) {
            iter.close();
        }
        if (kvstore != null) {
            kvstore.close();
        }
    }

    /**
     * Constructs a PrimaryKey for iteration from the given String property.
     * This method assumes that the value of the given prop parameter is a
     * list of name:value pairs in JSON FORMAT like the following:
     * <code>
     *   -Doracle.kv.primaryKey="{\"name\":\"stringVal\",\"name\":floatVal}"
     * </code>
     * For example,
     * <code>
     *   -Doracle.kv.primaryKey="{\"make"\":\"ford\",\"price\":23450.23}"
     * </code>
     * where the list itself is enclosed in un-escaped double quotes and
     * corresponding curly brace; and each field name component -- as well
     * as each STRING type field value component -- is enclosed in ESCAPED
     * double quotes.
     *
     * Note that the double quotes that encapsulate the string values
     * referenced by each field name and each string type field value
     * MUST BE ESCAPED; otherwise, a parsing error will occur. This is
     * because the hadoop command interpreter strips off the double quotes
     * surrounding the name and value components before passing the system
     * property on to the Java VM in which the MapReduce job executes.
     * Escaping the double quotes in the way described above preserves
     * the double quotes so that the value of the system property is in
     * valid JSON format when parsed by this method below.
     */
    private PrimaryKey getPrimaryKey(final Table table, final String prop) {

        PrimaryKey retKey = table.createPrimaryKey();
        if (prop == null) {
            /* Send wildcard if the property is null. */
            return retKey;
        }

        final String warnStr = "Invalid JSON in property [" + ParamConstant.PRIMARY_KEY.getName() + "=" + prop
                + "]: " + "must be a list of name:value pairs in JSON format having " + "the form -D"
                + ParamConstant.PRIMARY_KEY.getName() + "=" + "\"{\\\"fieldName\\\":\\\"stringValue\\\","
                + "\\\"fieldName\\\":floatValue, ... }\"; where the list "
                + "is enclosed in un-escaped double quotes and curly braces, "
                + "and each fieldName component and each STRING type fieldValue "
                + "component is enclosed in ESCAPED double quotes. ";

        final String proceedStr = "Proceeding with full PrimaryKey wildcard.";
        try {
            retKey = table.createPrimaryKeyFromJson(prop, false);
        } catch (IllegalArgumentException e) {
            LOG.warn(warnStr + proceedStr);
            e.printStackTrace();
        }
        return retKey;
    }

    /**
     * Constructs a FieldRange to use in a table iteration from the given
     * String property. This method assumes that the value of the given
     * rangeFieldProp parameter is a list of name:value pairs in JSON FORMAT
     * like the following:
     * <code>
     *   -Doracle.kv.fieldRange="{\"name\":\"fieldName\",
     *      \"start\":\"startVal\",[\"startInclusive\":true|false],
     *      \"end\"\"endVal\",[\"endInclusive\":true|false]}"
     * </code>
     * For example, if the PrimaryKey is specified using the fields,
     * 'primary-key -field type -field make -field model -field color', then
     * one might specify a field range with the following system properties:
     * <code>
     *   -Doracle.kv.primaryKey="{\"type\":\"truck\"}"
     *   -Doracle.kv.fieldRange="{\"name\":\"make\",
     *      \"start\":\"Chrysler\",\"startInclusive\":true,
     *      \"end\"\"GM\",\"endInclusive\":false}"
     * </code>
     * Note that the list itself is enclosed in un-escaped double quotes and
     * corresponding curly brace, but each name component -- as well as each
     * STRING type value component -- is enclosed in ESCAPED double quotes.
     *
     * Note also that the double quotes that encapsulate the string values
     * referenced by each name and each string type value MUST BE ESCAPED;
     * otherwise, a parsing error will occur. This is because the hadoop
     * command interpreter strips off the double quotes surrounding the name
     * and value components before passing the system property on to the
     * Java VM in which the MapReduce job executes. Escaping the double
     * quotes in the way described above preserves the double quotes so that
     * the value of the system property is in valid JSON format when parsed
     * by this method below.
     */
    private FieldRange getFieldRange(final Table table, final String rangeFieldProp) {

        FieldRange retRange = null;

        if (rangeFieldProp == null) {
            return retRange;
        }

        final String warnStr = "Invalid JSON in property [" + ParamConstant.FIELD_RANGE.getName() + "="
                + rangeFieldProp + "]: must be a list of name:value pairs in JSON format having " + "the form -D"
                + ParamConstant.FIELD_RANGE.getName() + "=" + "\"{\\\"name\\\":\\\"fieldName\\\","
                + "\\\"start\\\":\\\"stringStartVal\\\"|scalarStartVal," + "[\\\"startInclusive\\\":true|false],"
                + "\\\"end\\\":\\\"stringEndVal\\\"|scalarEndVal,"
                + "[\\\"endInclusive\\\":true|false]}\"; where the list "
                + "is enclosed in un-escaped double quotes and curly braces, "
                + "and each name component and each STRING type value component "
                + "is enclosed in ESCAPED double quotes. ";

        final String proceedStr = "Proceeding with full range of values "
                + "for the PrimaryKey rather than a sub-range ";
        try {
            /* TODO: replace the call to the createFieldRange method from this
             *       class with Table.createFieldRange(rangeFieldProp) once
             *       that method is added to oracle.kv.table.Table; that is,
             *
             *       retRange = table.createFieldRange(rangeFieldProp);
             */
            retRange = createFieldRange(table, rangeFieldProp);
        } catch (IllegalArgumentException e) {
            LOG.warn(warnStr + proceedStr);
            e.printStackTrace();
        }
        return retRange;
    }

    /*
     * NOTE: this method is temporary. Once the createFieldRange method is
     *       added to oracle.kv.table.Table, the getFieldRange method above
     *       should be changed to invoke Table.createFieldRange rather than
     *       this method; and this method can be removed.
     */
    private FieldRange createFieldRange(final Table table, final String rangeFieldProp) {
        FieldRange retRange = null;

        if (rangeFieldProp == null) {
            return retRange;
        }

        final String missingLeftBraceStr = "invalid JSON format: system property does not begin "
                + "with left curly brace ['{']";
        final String missingRightBraceStr = "invalid JSON format: system property does not end "
                + "with right curly brace ['}']";
        if (!rangeFieldProp.startsWith("{")) {
            throw new IllegalArgumentException(missingLeftBraceStr);
        }
        if (!rangeFieldProp.endsWith("}")) {
            throw new IllegalArgumentException(missingRightBraceStr);
        }

        final String dq = "\"";
        final String colon = ":";
        final String missingNameBase = "missing required range field name ";

        /* Verify prop contains all required names in form, "requiredName": */
        for (String requiredName : requiredRangeNames) {
            final String missingName = missingNameBase + "[" + requiredName + "]";

            final int indxOf = rangeFieldProp.indexOf(dq + requiredName + dq + colon);

            if (indxOf < 0) {
                throw new IllegalArgumentException(missingName);
            }
        }

        /* Build FieldRange from system property inside curly braces. */
        String nameVal = null;
        String startVal = null;
        boolean startInclusive = true;
        String endVal = null;
        boolean endInclusive = true;
        FieldDef.Type fieldType = null;

        final String invalidComponentBase = "invalid JSON format: invalid \"name\":\"value\" pair "
                + "in system property ";

        /* Strip off the enclosing curly braces from system property. */
        final String prop = rangeFieldProp.substring(1, rangeFieldProp.length() - 1);

        final String[] propComponents = prop.split(",");
        for (String propComponent : propComponents) {

            final String invalidComponent = invalidComponentBase + "[" + propComponent + "]";

            /* Each component must be "name":"value" pair; otherwise errorr. */
            final String[] rangeComponents = propComponent.split(":");
            if (rangeComponents.length != 2) {
                throw new IllegalArgumentException(invalidComponent);
            }

            /*
             * Each name must be encapsulated by double quotes (verified above)
             * and each value of type STRING must be encapsulated by double
             * quotes. Scalar values are not encapsulated by double quotes.
             * Strip off double quotes before constructing the FieldRange.
             */
            final String name = rangeComponents[0].substring(1, rangeComponents[0].length() - 1);

            /* For values, handle both scalars and strings appropriately. */
            final String val = rangeComponents[1];
            if ("name".equals(name.toLowerCase())) {
                /* The name of the field over which to range is a STRING. */
                if (!(val.startsWith(dq) && val.endsWith(dq))) {
                    throw new IllegalArgumentException(invalidComponent);
                }
                nameVal = val.substring(1, val.length() - 1);
                fieldType = table.getField(nameVal).getType();
            } else if ("start".equals(name.toLowerCase())) {
                /* Test for un-matched double quotes. */
                if (val.startsWith(dq) && !val.endsWith(dq)) {
                    throw new IllegalArgumentException(invalidComponent);
                } else if (!val.startsWith(dq) && val.endsWith(dq)) {
                    throw new IllegalArgumentException(invalidComponent);
                } else if (val.startsWith(dq) && val.endsWith(dq)) {
                    startVal = val.substring(1, val.length() - 1); /* string */
                } else {
                    startVal = val; /* scalar */
                }
            } else if ("startinclusive".equals(name.toLowerCase())) {
                startInclusive = Boolean.parseBoolean(val);
            } else if ("end".equals(name.toLowerCase())) {
                /* Test for un-matched double quotes. */
                if (val.startsWith(dq) && !val.endsWith(dq)) {
                    throw new IllegalArgumentException(invalidComponent);
                } else if (!val.startsWith(dq) && val.endsWith(dq)) {
                    throw new IllegalArgumentException(invalidComponent);
                } else if (val.startsWith(dq) && val.endsWith(dq)) {
                    endVal = val.substring(1, val.length() - 1); /* string */
                } else {
                    endVal = val; /* scalar */
                }
            } else if ("endinclusive".equals(name.toLowerCase())) {
                endInclusive = Boolean.parseBoolean(val);
            }
        }

        if (nameVal == null) {
            throw new IllegalArgumentException(invalidComponentBase);
        }
        if (startVal == null && fieldType == null) {
            throw new IllegalArgumentException(invalidComponentBase);
        }

        /* Verification complete. Construct the FieldRange return value. */

        retRange = table.createFieldRange(nameVal);

        if (FieldDef.Type.STRING.equals(fieldType)) {
            retRange.setStart(startVal, startInclusive);
            retRange.setEnd(endVal, endInclusive);
        } else {
            try {
                if (FieldDef.Type.INTEGER.equals(fieldType)) {
                    retRange.setStart(Integer.parseInt(startVal), startInclusive);
                    retRange.setEnd(Integer.parseInt(endVal), endInclusive);
                } else if (FieldDef.Type.LONG.equals(fieldType)) {
                    retRange.setStart(Long.parseLong(startVal), startInclusive);
                    retRange.setEnd(Long.parseLong(endVal), endInclusive);
                } else if (FieldDef.Type.FLOAT.equals(fieldType)) {
                    retRange.setStart(Float.parseFloat(startVal), startInclusive);
                    retRange.setEnd(Float.parseFloat(endVal), endInclusive);
                } else if (FieldDef.Type.DOUBLE.equals(fieldType)) {
                    retRange.setStart(Double.parseDouble(startVal), startInclusive);
                    retRange.setEnd(Double.parseDouble(endVal), endInclusive);
                } else {
                    throw new IllegalArgumentException(invalidComponentBase);
                }
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException(new NumberFormatException(invalidComponentBase));
            }
        }
        return retRange;
    }

    /**
     * Convenience method that retrieves the login configuration and trust
     * credentials as resources from the classpath, and writes that
     * information to corresponding files on the user's local file system.
     * The login file and trust file that are created by this method can
     * then be used when attempting to interact with a secure KVStore.
     * <p>
     * This method returns a string containing the fully qualified path to
     * the login file that is created. The login file will reference the
     * name of the trust file that is created; which must be written to
     * the same directory in which the login file resides.
     */
    private static String createLocalKVSecurity(final String loginFlnm, final String trustFlnm) throws IOException {

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

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

        /*
         * If loginFile and/or trustFile is a filename and not an absolute
         * path, then generate local versions of the file.
         */
        final File userSecurityDirFd = new File(USER_SECURITY_DIR);
        if (!userSecurityDirFd.exists()) {
            if (!userSecurityDirFd.mkdirs()) {
                throw new IOException("failed to create " + userSecurityDirFd);
            }
        }

        InputStream loginStream = null;
        InputStream trustStream = null;

        final ClassLoader cl = TableRecordReaderBase.class.getClassLoader();
        if (cl != null) {
            loginStream = cl.getResourceAsStream(loginFlnm);
            trustStream = cl.getResourceAsStream(trustFlnm);
        } else {
            loginStream = ClassLoader.getSystemResourceAsStream(loginFlnm);
            trustStream = ClassLoader.getSystemResourceAsStream(trustFlnm);
        }

        /*
         * Retrieve the login configuration as a resource from the classpath,
         * and write that information to the user's local file system.
         */
        final Properties loginProps = new Properties();
        if (loginStream != null) {
            loginProps.load(loginStream);
        }

        /* Strip off the path of the trust file. */
        final String trustProp = loginProps.getProperty(KVSecurityConstants.SSL_TRUSTSTORE_FILE_PROPERTY);
        if (trustProp != null) {
            final File trustPropFd = new File(trustProp);
            if (!trustPropFd.exists()) {
                loginProps.setProperty(KVSecurityConstants.SSL_TRUSTSTORE_FILE_PROPERTY, trustPropFd.getName());
            }
        }

        final File loginFd = new File(USER_SECURITY_DIR + FILE_SEP + loginFlnm);
        final FileOutputStream loginFos = new FileOutputStream(loginFd);
        loginProps.store(loginFos, null);
        loginFos.close();

        /*
         * Retrieve the trust credentials as a resource from the classpath,
         * and write that information to the user's local file system.
         */
        final File trustFd = new File(USER_SECURITY_DIR + FILE_SEP + trustFlnm);
        final FileOutputStream trustFlnmFos = new FileOutputStream(trustFd);

        if (trustStream != null) {
            int nextByte = trustStream.read();
            while (nextByte != -1) {
                trustFlnmFos.write(nextByte);
                nextByte = trustStream.read();
            }
        }
        trustFlnmFos.close();

        return loginFd.toString();
    }

    public Table getKvTable() {
        return kvTable;
    }

    public void setIndexKey(final IndexKey key) {
        indexKey = key;
        primaryKey = null;
    }

    public void setPrimaryKey(final PrimaryKey key) {
        primaryKey = key;
        indexKey = null;
    }

    public void setMultiRowOptions(final FieldRange fieldRange) {
        rowOpts = new MultiRowOptions(fieldRange);
    }

    /**
     * For testing only; to support the use of a mocked store.
     */
    public void setStore(final KVStore testStore) {
        this.kvstore = testStore;
    }

    public void setQueryInfo(int newQueryBy, String newWhereClause) {
        this.queryBy = newQueryBy;
        this.whereClause = newWhereClause;
    }
}