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

oracle.kv.impl.api.table.TableMetadata Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

There is a newer version: 18.3.10
Show newest version
/*-
 * 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.table;

import static oracle.kv.table.TableAPI.SYSDEFAULT_NAMESPACE_NAME;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import oracle.kv.impl.admin.IllegalCommandException;
import oracle.kv.impl.api.table.IndexImpl.AnnotatedField;
import oracle.kv.impl.api.table.IndexImpl.IndexStatus;
import oracle.kv.impl.api.table.TableImpl.TableStatus;
import oracle.kv.impl.metadata.Metadata;
import oracle.kv.impl.metadata.MetadataInfo;
import oracle.kv.impl.metadata.MetadataKey;
import oracle.kv.impl.security.Ownable;
import oracle.kv.impl.security.ResourceOwner;
import oracle.kv.table.FieldDef;
import oracle.kv.table.Index;
import oracle.kv.table.Table;
import oracle.kv.table.TimeToLive;

/**
 * This is internal implementation that wraps Table and Index metadata
 * operations such as table/index creation, etc.
 *
 * TableMetadata stores tables in a tree.  The top level is a map from
 * name (String) to Table and contains top-level tables only.  Each top-level
 * table may or may not contain child tables.  When this class is serialized
 * the entire tree of Table objects, along with their contained Index objects,
 * is serialized.
 *
 * When a table lookup is performed it must be done top-down.  First the lookup
 * walks to the "root" of the metadata structure, which is the map contained in
 * this instance.  For top-level tables the lookup is a simple get.  For child
 * tables the code unwinds down the stack of parents to get the child.
 *
 * When a table is first inserted into TableMetadata it is assigned a
 * numeric id. Ids are allocated from the keyId member.
 *
 * Note that this implementation is not synchronized. If multiple threads
 * access a table metadata instance concurrently, and at least one of the
 * threads modifies the table metadata structurally, it must be synchronized
 * externally.
 */
public class TableMetadata implements TableMetadataHelper,
                                      Metadata,
                                      Serializable {

    private static final long serialVersionUID = 1L;

    /*
     * Cluster during upgrade or new cluster will have namespaces field null.
     * This means semantically that there is only one namespace:
     * the sysdefault ns:
     * {@link oracle.kv.table.TableAPI#SYSDEFAULT_NAMESPACE_NAME}
     */
    private Map namespaces;
    private final Map tables =
        new TreeMap(FieldComparator.instance);

    private int seqNum = Metadata.EMPTY_SEQUENCE_NUMBER;

    private long keyId = INITIAL_KEY_ID;

    public static final int INITIAL_KEY_ID = 1;

    /*
     * Record of changes to the metadata. If null no changes will be kept.
     */
    private final List changeHistory;

    public static class NamespaceImpl implements Ownable, Serializable {

        private static final long serialVersionUID = 1L;

        private final String namespace;
        private final ResourceOwner owner;

        /* owner is null for the "sysdefault" namespace */
        NamespaceImpl(String namespace, ResourceOwner owner) {
            this.namespace = namespace;
            this.owner = owner == null ? null : new ResourceOwner(owner);
        }

        public String getNamespace() {
            return namespace;
        }

        @Override
        public ResourceOwner getOwner() {
            return owner;
        }
    }

    /**
     * Construct a table metadata object. If keepChanges is true any changes
     * made to are recorded and can be accessed through the getMetadataInfo()
     * interface.
     *
     * @param keepChanges
     */
    public TableMetadata(boolean keepChanges) {
        changeHistory = keepChanges ? new LinkedList() : null;
    }

    public TableImpl addTable(String namespace,
                              String name,
                              String parentName,
                              List primaryKey,
                              List primaryKeySizes,
                              List shardKey,
                              FieldMap fieldMap,
                              TimeToLive ttl,
                              TableLimits limits,
                              boolean r2compat,
                              int schemaId,
                              String description,
                              ResourceOwner owner) {

        return addTable(namespace, name, parentName,
                        primaryKey, primaryKeySizes, shardKey, fieldMap, ttl,
                        limits, r2compat, schemaId, description, owner, false);
    }

    public TableImpl addTable(String namespace,
                              String name,
                              String parentName,
                              List primaryKey,
                              List primaryKeySizes,
                              List shardKey,
                              FieldMap fieldMap,
                              TimeToLive ttl,
                              TableLimits limits,
                              boolean r2compat,
                              int schemaId,
                              String description,
                              ResourceOwner owner,
                              boolean sysTable) {
        final TableImpl table = insertTable(namespace, name, parentName,
                                            primaryKey, primaryKeySizes,
                                            shardKey,
                                            fieldMap,
                                            ttl, limits,
                                            r2compat, schemaId,
                                            description,
                                            owner, sysTable);
        addTableChange(table);
        return table;
    }

    private void addTableChange(TableImpl table) {
        bumpSeqNum();
        if (changeHistory != null) {
            changeHistory.add(new AddTable(table, seqNum));
        }
    }

    /**
     * Drops a table. If the table has indexes or child tables an
     * IllegalArgumentException is thrown. If markForDelete is true the table's
     * status is set to DELETING and is not removed.
     *
     * @param tableName the table name
     * @param markForDelete if true mark the table as DELETING
     */
    public void dropTable(String namespace,
                          String tableName,
                          boolean markForDelete) {
        removeTable(namespace, tableName, markForDelete);

        bumpSeqNum();
        if (changeHistory != null) {
            changeHistory.add(new DropTable(namespace, tableName,
                                            markForDelete, seqNum));
        }
    }

    /**
     * Evolves a table using new fields but only if it's not already been done
     * and if the supplied version indicates that the evolution started with
     * the latest table version.
     *
     * If this operation was retried the evolution may have already been
     * applied.  Check field equality and if equal, consider the evolution
     * done.
     *
     * @return true if the evolution happens, false otherwise
     *
     * @throws IllegalCommandException if an attempt is made to evolve a version
     * other than the latest table version
     */
    public boolean evolveTable(TableImpl table, int tableVersion,
                               FieldMap fieldMap, TimeToLive ttl,
                               String description,
                               boolean systemTable) {

        if (table.isSystemTable() != systemTable) {
            if (systemTable) {
                throw new IllegalCommandException
                    ("Table " +
                     NameUtils.makeQualifiedName(null, null, table.getName()) +
                     " is not system table");
            }
            throw new IllegalCommandException
                ("Cannot evolve table " +
                 NameUtils.makeQualifiedName(null, null, table.getName()));
        }
        if (fieldMap.equals(table.getFieldMap())) {
            if (TableImpl.compareTTL(ttl, table.getDefaultTTL())) {
                return false;
            }
        }

        if (tableVersion != table.numTableVersions()) {
            throw new IllegalCommandException
                ("Table evolution must be performed on the latest version, " +
                 "version supplied is " + tableVersion + ", latest is " +
                 table.numTableVersions());
        }

        table.evolve(fieldMap, ttl, description);
        bumpSeqNum();
        if (changeHistory != null) {
            changeHistory.add(new EvolveTable(table, seqNum));
        }
        return true;
    }

    /**
     * Sets the table limits.
     */
    public void setLimits(TableImpl table, TableLimits newLimits) {
        table.setTableLimits(newLimits);
        bumpSeqNum();
        if (changeHistory != null) {
            changeHistory.add(new TableLimit(table, seqNum));
        }
    }
    
    public void addIndex(String namespace,
                         String indexName,
                         String tableName,
                         List fields,
                         List types,
                         String description) {

        insertIndex(namespace, indexName, tableName,
                    fields, types, description);
        bumpSeqNum();
        if (changeHistory != null) {
            changeHistory.add(new AddIndex(namespace, indexName, tableName,
                                           fields, types, description, seqNum));
        }
    }

    public void addTextIndex(String namespace,
                             String indexName,
                             String tableName,
                             List fields,
                             Map properties,
                             String description) {
        final List fieldNames = new ArrayList(fields.size());
        final Map annotations =
                new HashMap(fields.size());
        IndexImpl.populateMapFromAnnotatedFields(fields,
                                                 fieldNames,
                                                 annotations);

        insertTextIndex(namespace, indexName, tableName, fieldNames,
                        annotations, properties, description);

        bumpSeqNum();
        if (changeHistory != null) {
            changeHistory.add(new AddIndex(namespace, indexName, tableName,
                                           fieldNames, annotations, properties,
                                           description, seqNum));
        }
    }

    public void dropIndex(String namespace,
                          String indexName,
                          String tableName) {
        if (removeIndex(namespace, indexName, tableName)) {
            bumpSeqNum();
            if (changeHistory != null) {
                changeHistory.add(new DropIndex(namespace,
                                                indexName,
                                                tableName,
                                                seqNum));
            }
        }
    }

    public boolean updateIndexStatus(String namespace,
                                     String indexName,
                                     String tableName,
                                     IndexStatus status) {
        final IndexImpl index = changeIndexStatus(namespace, indexName,
                                                  tableName, status);
        if (index != null) {
            bumpSeqNum();
            if (changeHistory != null) {
                changeHistory.add(new UpdateIndexStatus(index, seqNum));
            }
            return true;
        }
        return false;
    }

    /*
     * Add the table described.  It must not exist or an exception is thrown.
     * If it has a parent the parent must exist.
     */
    TableImpl insertTable(String namespace,
                          String name,
                          String parentName,
                          List primaryKey,
                          List primaryKeySizes,
                          List shardKey,
                          FieldMap fields,
                          TimeToLive ttl,
                          TableLimits limits,
                          boolean r2compat,
                          int schemaId,
                          String description,
                          ResourceOwner owner,
                          boolean sysTable) {

        TableImpl table = null;

        if (r2compat) {
            verifyIdNotUsed(name);
        }

        if (parentName != null) {
            final TableImpl parent = getTable(namespace,
                                              parentName,
                                              true);
            if (parent.childTableExists(name)) {
                throw new IllegalArgumentException
                    ("Cannot create table.  Table exists: " +
                     NameUtils.makeQualifiedName(namespace, name, parentName));
            }

            if (parent.isSystemTable() != sysTable) {
                throw new IllegalArgumentException
                    ("Cannot create table " + name + ". It must" +
                     ((sysTable) ? " not" : "") + " be a system table, " +
                     "because its parent is " +
                     ((sysTable) ? "" : " not") + " a system table");
            }
            parent.checkChildLimit(name);
            table = TableImpl.createTable(namespace, name, parent,
                                          primaryKey, primaryKeySizes, shardKey,
                                          fields, r2compat, schemaId,
                                          description, true, owner, ttl, limits,
                                          sysTable);
            table.setId(allocateId());
            parent.getMutableChildTables().put(name, table);
        } else {
            final String namespaceName = NameUtils
                .makeQualifiedName(namespace, name);
            if (tables.containsKey(namespaceName)) {
                throw new IllegalArgumentException
                    ("Cannot create table.  Table exists: " + namespaceName);
            }
            table = TableImpl.createTable(namespace, name, null,
                                          primaryKey, primaryKeySizes, shardKey,
                                          fields, r2compat, schemaId,
                                          description, true, owner, ttl, limits,
                                          sysTable);
            table.setId(allocateId());
            tables.put(namespaceName, table);
        }
        return table;

    }

    /*
     * Evolve the table described.  It must not exist or an exception is thrown.
     */
    TableImpl evolveTable(String namespace, String tableName,
                          FieldMap fields, TimeToLive ttl, String description) {
        final TableImpl table = getTable(namespace, tableName, true);
        table.evolve(fields, ttl, description);
        return table;
    }

    /**
     * Removes a table. If the table has indexes or child tables an
     * IllegalArgumentException is thrown. If markForDelete is true the table's
     * status is set to DELETING and is not removed.
     *
     * @param tableName the table name
     * @param markForDelete if true mark the table as DELETING
     *
     * @return the removed table
     */
    Table removeTable(String namespace,
                      String tableName,
                      boolean markForDelete) {
        final TableImpl table = checkForRemove(namespace, tableName,
                                               false /* indexes allowed */);
        if (markForDelete) {
            table.setStatus(TableStatus.DELETING);
            return table;
        }
        final Table parent = table.getParent();

        if (parent != null) {
            ((TableImpl)parent).getMutableChildTables().remove(table.getName());
        } else {
            /* a top-level table */
            tables.remove(
                NameUtils.makeQualifiedName(namespace, table.getName()));
        }
        return table;
    }

    /**
     * Checks to see if it is ok to remove this table. Returns the table
     * instance if the table can be removed. Throws IllegalCommandException
     * if the table does not exist, it is a system table, or if the table is
     * referenced by child tables. If allowIndexes is false an
     * IllegalCommandException is thrown if the table contains indexes.
     */
    public TableImpl checkForRemove(String namespace,
                                    String tableName,
                                    boolean indexesAllowed) {
        final TableImpl table = getTable(namespace, tableName, true);
        final String qname = NameUtils
            .makeQualifiedName(namespace, null, tableName);
        /* getTable(..., true) can return null?? */
        if (table == null) {
            throw new IllegalCommandException
                ("Table " + qname + " does not exist");
        }

        if (table.isSystemTable()) {
            throw new IllegalCommandException
                ("Cannot remove system table: " + qname);
        }

        if (!table.getChildTables().isEmpty()) {
            throw new IllegalCommandException
                ("Cannot remove table " + qname +
                 ", it is still referenced by " +
                 "child tables");
        }

        if (!indexesAllowed && !table.getIndexes().isEmpty()) {
            throw new IllegalCommandException
                    ("Cannot remove table " + qname +
                     ", it still contains indexes");
        }

        return table;
    }

    IndexImpl insertIndex(String namespace,
                          String indexName,
                          String tableName,
                          List fields,
                          List types,
                          String description) {
        final TableImpl table = getTable(namespace, tableName, true);
        if (table.isSystemTable()) {
            throw new IllegalCommandException
                ("Cannot add index " + indexName + " on system table: " +
                 NameUtils.makeQualifiedName(namespace, null, tableName));
        }
        if (table.isDeleting()) {
            throw new IllegalCommandException
                ("Cannot add index " + indexName + " on table: " +
                 NameUtils.makeQualifiedName(namespace, null, tableName) +
                 ", it is being removed");
        }
        if (table.getIndex(indexName) != null) {
            throw new IllegalArgumentException
                ("Index exists: " + indexName + " on table: " +
                 NameUtils.makeQualifiedName(namespace, null, tableName));
        }
        final IndexImpl index = new IndexImpl(indexName, table, fields,
                                              types, description);
        index.setStatus(IndexStatus.POPULATING);
        table.addIndex(index);
        return index;
    }

    boolean removeIndex(String namespace, String indexName, String tableName) {
        final TableImpl table = getTable(namespace, tableName, true);
        if (table.isSystemTable()) {
            throw new IllegalCommandException
                ("Cannot remove index " + indexName + " on system table: " +
                 NameUtils.makeQualifiedName(namespace, null, tableName));
        }

        final Index index = table.getIndex(indexName);
        if (index == null) {
            throw new IllegalArgumentException
                ("Index does not exist: " + indexName + " on table: " +
                 NameUtils.makeQualifiedName(namespace, null, tableName));
        }
        table.removeIndex(indexName);

        return true;
    }

    /*
     * Update the index status to the desired status.  If a change was made
     * return the Index, if the status is unchanged return null, allowing
     * this operation to be an idempotent no-op.
     */
    IndexImpl changeIndexStatus(String namespace,
                                String indexName,
                                String tableName,
                                IndexStatus status) {
        final TableImpl table = getTable(namespace, tableName, true);

        final IndexImpl index = (IndexImpl) table.getIndex(indexName);
        if (index == null) {
            throw new IllegalArgumentException
                ("Index does not exist: " + indexName + " on table: " +
                 NameUtils.makeQualifiedName(namespace, null, tableName));
        }
        if (index.getStatus() == status) {
            return null;
        }
        index.setStatus(status);
        return index;
    }

    IndexImpl insertTextIndex(String namespace,
                              String indexName,
                              String tableName,
                              List fields,
                              Map annotations,
                              Map properties,
                              String description) {
        final TableImpl table = getTable(namespace, tableName, true);
        if (table.isSystemTable()) {
            throw new IllegalCommandException
                ("Cannot add text index " + indexName + " on table: " +
                 NameUtils.makeQualifiedName(namespace, null, tableName));
        }

        if (table.getTextIndex(indexName) != null) {
            throw new IllegalArgumentException
                ("Text Index exists: " + indexName + " on table: " +
                 NameUtils.makeQualifiedName(namespace, null, tableName));
        }
        final IndexImpl index = new IndexImpl(indexName, table, fields, null,
                                              annotations, properties,
                                              description);
        index.setStatus(IndexStatus.POPULATING);
        table.addIndex(index);
        return index;
    }

    /**
     * Return the named table.
     *
     * @param tableName is a "." separated path to the table name, e.g.
     * parent.child.target.  For top-level tables it is a single
     * component
     */
    public TableImpl getTable(String namespace,
                              String tableName,
                              boolean mustExist) {

        final String[] path = TableImpl.parseFullName(tableName);
        return getTable(namespace, path, mustExist);
    }

    /**
     * For compatibility only (used in KVProxy). Do not use internally.
     */
    public TableImpl getTable(String tableName) {
        return getTable(null, tableName);
    }

    public TableImpl getTable(String namespace,
                              String tableName,
                              String parentName) {
        final StringBuilder sb = new StringBuilder();
        if (parentName != null) {
            sb.append(parentName).append(NameUtils.CHILD_SEPARATOR);
        }
        if (tableName != null) {
            sb.append(tableName);
        }
        return getTable(namespace, sb.toString(), false);
    }

    /**
     * @see TableMetadataHelper
     */
    @Override
    public TableImpl getTable(String namespace, String tableName) {
        return getTable(namespace, tableName, false);
    }

    /**
     * @see TableMetadataHelper
     */
    @Override
    public TableImpl getTable(String namespace, String[] tablePath) {
        return getTable(namespace, tablePath, false);
    }

    public TableImpl getTable(String namespace, String[] path,
                              boolean mustExist) {

        if (path == null || path.length == 0) {
            return null;      // TODO ??? thow exception if mustExist is true??
        }

        final String firstKey = NameUtils.makeQualifiedName(namespace, path[0]);
        TableImpl targetTable =  (TableImpl) tables.get(firstKey);

        if (path.length > 1) {
            for (int i = 1; i < path.length && targetTable != null; i++) {
                try {
                    targetTable = getChildTable(path[i], targetTable);
                } catch (IllegalArgumentException ignored) {
                    targetTable = null;
                    break;
                }
            }
        }

        if (targetTable == null && mustExist) {
            throw new IllegalArgumentException
                ("Table: " + makeQualifiedName(namespace, path) +
                 " does not exist in " + this);
        }
        return targetTable;
    }

    public boolean tableExists(String namespace,
                               String tableName,
                               String parentName) {
        return (getTable(namespace, tableName, parentName) != null);
    }

    /**
     * Returns the specified Index or null if it, or its containing table
     * does not exist.
     */
    public Index getIndex(String namespace,
                          String tableName,
                          String indexName) {
        final TableImpl table = getTable(namespace, tableName);
        if (table != null) {
            return table.getIndex(indexName);
        }
        return null;
    }

    public Index getTextIndex(String namespace,
                              String tableName,
                              String indexName) {
        final TableImpl table = getTable(namespace, tableName);
        if (table != null) {
            return table.getTextIndex(indexName);
        }
        return null;
    }

    private static String makeQualifiedName(String namespace,
                                            String[] pathName) {
        final StringBuilder sb = new StringBuilder();
        for (String step : pathName) {
            sb.append(step);
        }
        return NameUtils.makeQualifiedName(namespace, null, sb.toString());
    }

    /**
     * Return the named child table.
     */
    public TableImpl getChildTable(String tableName, Table parent) {
        return (TableImpl) parent.getChildTable(tableName);
    }

    /*
     * Get a table from TableMetadataKey.  This is used by RepNodes to return
     * tables requested by clients.  In this path it's necessary to filter out
     * created, but not-yet-populated indexes.
     */
    public TableImpl getTable(TableMetadataKey mdKey) {
        final String tableName =
            NameUtils.getFullNameFromQualifiedName(mdKey.getTableName());
        final String namespace =
            NameUtils.getNamespaceFromQualifiedName(mdKey.getTableName());
        TableImpl table = getTable(namespace, tableName);
        if (table != null && table.getIndexes().size() > 0) {
            /* clone, filter */
            table = table.clone();

            for (final Iterator> it =
                    table.getMutableIndexes().entrySet().iterator();
                it.hasNext();) {
                final Map.Entry entry = it.next();
                if (!((IndexImpl)entry.getValue()).getStatus().isReady()) {
                    it.remove();
                }
            }
        }
        return table;
    }

    /**
     * Return all top-level tables.
     */
    public Map getTables() {
        return tables;
    }

    /* public for access from ShowCommand (for now) */
    public Map getTables(String namespace) {

        if (namespace == null) {
            return tables;
        }

        final Map nsTables =
            new TreeMap(FieldComparator.instance);

        final String prefix = namespace + ":";

        for (Map.Entry entry : tables.entrySet()) {
            if (entry.getKey().toLowerCase().startsWith(prefix.toLowerCase())) {
                nsTables.put(
                    NameUtils.getFullNameFromQualifiedName(entry.getKey()),
                    entry.getValue());
            }
        }
        return nsTables;
    }

    /**
     * Returns a sorted list of all tables. In this method parent and child
     * tables are listed independently, in alphabetical order, which means
     * parents first.
     */
    public List listTables(String namespace) {
        final List list = listTables(namespace, true);
        Collections.sort(list);
        return list;
    }

    /**
     * Adds all table names, parent and child to the list.  Child tables are
     * listed before parent tables because the iteration is depth-first.  This
     * simplifies code that does things like removing all tables or code that
     * depends on this order.  If other orders are desirable a parameter or
     * other method could be added to affect the iteration.
     *
     * @param namespace if non-null, only tables in the specified namespace are
     * returned and the names are full names (no namespace prefix). If null,
     * only tables in sysdefault namespace are returned, unless the allTables
     * parameter is true.
     *
     * @param allTables if true, all tables are returned unless the namespace
     * parameter is non-null. In this case the names have the namespace prefix
     * added. The format is [namespace:]full-table-name.
     */
    public List listTables(final String namespace,
                                   final boolean allTables) {
        final List tableList = new ArrayList();
        iterateTables(new TableMetadataIteratorCallback() {
                @Override
                public boolean tableCallback(Table table) {
                    if (namespace != null &&
                        namespace.equalsIgnoreCase(
                            ((TableImpl)table).getInternalNamespace())) {
                        tableList.add(table.getFullName());
                    } else if (namespace == null) {
                        if (allTables) {
                            tableList.add(table.getFullNamespaceName());
                        } else if (
                            ((TableImpl)table).getInternalNamespace() == null) {
                            tableList.add(table.getFullName());
                        }
                    }
                    return true;
                }
            });
        return tableList;
    }

    /**
     * Returns the number of tables in the structure, including
     * child tables.
     */
    private int numTables() {
        final int[] num = new int[1];
        iterateTables(new TableMetadataIteratorCallback() {
                @Override
                public boolean tableCallback(Table table) {
                    ++num[0];
                    return true;
                }
            });
        return num[0];
    }

    /**
     * Returns true if there are no tables defined.
     *
     * @return true if there are no tables defined
     */
    public boolean isEmpty() {
        return tables.isEmpty();
    }

    /**
     * Returns all available namespaces.
     */
    public Set listNamespaces() {
        Set keySet = new HashSet();
        keySet.add(SYSDEFAULT_NAMESPACE_NAME);
        if (namespaces != null) {
            keySet.addAll(namespaces.keySet());
        }

        return keySet;
    }

    /**
     * Returns all text indexes.
     */
    public List getTextIndexes() {

        final List textIndexes = new ArrayList();

        iterateTables(new TableMetadataIteratorCallback() {
                @Override
                public boolean tableCallback(Table table) {
                    textIndexes.addAll
                        (table.getIndexes(Index.IndexType.TEXT).values());
                    return true;
                }
            });

        return textIndexes;
    }

    /**
     * Convenience method for getting all text index names.
     */
    public Set getTextIndexNames() {
        final Set textIndexNames = new HashSet();
        for (Index ti : getTextIndexes()) {
            textIndexNames.add(ti.getName());
        }
        return textIndexNames;
    }

    private void bumpSeqNum() {
        seqNum++;
    }

    /*
     * Bump and return a new table id. Verify that the string version of
     * the id doesn't already exist as a table name.  If so, bump again.
     */
    private long allocateId() {
        while (true) {
            ++keyId;
            try {
                verifyIdNotUsed(TableImpl.createIdString(keyId));
                return keyId;
            } catch (IllegalArgumentException iae) {
                /* try the next id */
            }
        }
    }

    /*
     * Returns the highest table ID that has been allocated.
     */
    public long getMaxTableId() {
        return keyId;
    }
    
    /* -- From Metadata -- */

    @Override
    public MetadataType getType() {
        return MetadataType.TABLE;
    }

    @Override
    public int getSequenceNumber() {
        return seqNum;
    }

    @Override
    public TableChangeList getChangeInfo(int startSeqNum) {
        return new TableChangeList(seqNum, getChanges(startSeqNum));
    }

    @Override
    public TableMetadata pruneChanges(int limitSeqNum, int maxChanges) {
        final int firstChangeSeqNum = getFirstChangeSeqNum();
        if (firstChangeSeqNum == -1) {
            /* No changes to prune. */
            return this;
        }

        final int firstRetainedChangeSeqNum =
                            Math.min(getSequenceNumber() - maxChanges + 1,
                                     limitSeqNum);
        if (firstRetainedChangeSeqNum <= firstChangeSeqNum) {
            /* Nothing to prune. */
            return this;
        }

        for (final Iterator itr = changeHistory.iterator();
             itr.hasNext() &&
             (itr.next().getSequenceNumber() < firstRetainedChangeSeqNum);) {
            itr.remove();
        }
        return this;
    }

    /* Not private for unit tests */
    int getFirstChangeSeqNum() {
        return (changeHistory == null) ? -1 :
                    changeHistory.isEmpty() ?
                                -1 : changeHistory.get(0).getSequenceNumber();
    }

    /* Unit tests */
    int getChangeHistorySize() {
        return (changeHistory == null) ? 0 : changeHistory.size();
    }

    /* -- Change support methods -- */

    private List getChanges(int startSeqNum) {

        /* Skip if we are out of date, or don't have changes */
        if ((startSeqNum >= seqNum) ||
            (changeHistory == null) ||
            changeHistory.isEmpty()) {
            return null;
        }

        /* Also skip if they are way out of date (or not initialized) */
        if (startSeqNum < changeHistory.get(0).getSequenceNumber()) {
            return null;
        }

        List list = null;

        for (TableChange change : changeHistory) {
            if (change.getSequenceNumber() > startSeqNum) {
                if (list == null) {
                    list = new LinkedList();
                }
                list.add(change);
            }
        }
        return list;
    }

    /**
     * Updates the metadata data from an info object. Returns true
     * if the table metadata was modified.
     *
     * @param metadataInfo info object to update from
     * @return true if the table metadata was modified
     */
    public boolean update(MetadataInfo metadataInfo) {

        if (metadataInfo instanceof TableChangeList) {
            return apply((TableChangeList)metadataInfo);
        }
        throw new IllegalArgumentException("Unknow metadata info: " +
                                           metadataInfo);
    }

    private boolean apply(TableChangeList changeList) {
        if (changeList.isEmpty()) {
            return false;
        }

        final int origSeqNum = seqNum;

        for (TableChange change : changeList) {
            if (change.getSequenceNumber() <= seqNum) {
                /* ignore older changes that will have been applied */
                continue;
            }
            if (change.getSequenceNumber() > (seqNum + 1)) {
                /*
                 * if there is an out of order change, fail. Changes should
                 * appear in a list from lower to higher. Generally it should
                 * not be possible to get here because changes are always sent
                 * relative to the current sequence number.
                 */
                break;
            }
            if (!change.apply(this)) {
                break;
            }
            seqNum = change.getSequenceNumber();
            if (changeHistory != null) {
                changeHistory.add(change);
            }
        }
        return origSeqNum != seqNum;
    }

    /**
     * Creates a copy of this TableMetadata object.
     *
     * @return the new TableMetadata instance
     */
    public TableMetadata getCopy() {
        try {
            final ByteArrayOutputStream bos = new ByteArrayOutputStream();
            final ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.close();

            final ByteArrayInputStream bis =
                new ByteArrayInputStream(bos.toByteArray());
            final ObjectInputStream ois = new ObjectInputStream(bis);

            return (TableMetadata)ois.readObject();
        } catch (IOException ioe) {
            throw new IllegalStateException("Unexpected exception", ioe);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

    @Override
    public String toString() {
        return "TableMetadata[" + seqNum + ", n:" +
            (namespaces == null ? "1" : namespaces.size() + 1) + ", " +
            ", t:" + tables.size() + ", " +
            ((changeHistory == null) ? "-" : changeHistory.size()) + "]";
    }

    /**
     * Compares two TableMetadata instances by comparing all tables, including
     * child tables.  This might logically be implemented as an override of
     * equals but that might mean adding hashCode() to avoid warnings and
     * that's not necessary.  If anyone ever needs a true equals() overload
     * then this can change.
     *
     * @return true if the objects have the same content, false otherwise.
     */
    public boolean compareMetadata(final TableMetadata omd) {
        final int num = numTables();
        if (num == omd.numTables()) {
            final int[] numCompared = new int[1];
                iterateTables(new TableMetadataIteratorCallback() {
                        @Override
                        public boolean tableCallback(Table table) {
                            if (!existsAndEqual((TableImpl) table, omd)) {
                                return false;
                            }
                            ++numCompared[0];
                            return true;
                        }
                    });
                return numCompared[0] == num;
        }
        return false;
    }

    /**
     * Iterates all tables and ensures that the string version of the
     * id for the table doesn't match the idString. Throws if it
     * exists.  This is called when creating a new table in r2compat mode.
     */
    private void verifyIdNotUsed(final String idString) {
        iterateTables(new TableMetadataIteratorCallback() {
                @Override
                public boolean tableCallback(Table table) {
                    final String tableId = ((TableImpl)table).getIdString();
                    if (tableId.equals(idString)) {
                        throw new IllegalArgumentException
                            ("Cannot create a table overlay with the name " +
                             idString + ", it exists as a table Id");
                    }
                    return true;
                }
            });
    }

    /**
     * Returns true if the table name exists in the TableMetadata and
     * the two tables are equal.
     */
    private static boolean existsAndEqual(TableImpl table,
                                          TableMetadata md) {
        final TableImpl otherTable = md.getTable(table.getInternalNamespace(),
                                                 table.getFullName());
        if (otherTable != null && table.equals(otherTable)) {

            /*
             * Check child tables individually.  Table equality does not
             * consider children.
             */
            for (Table child : table.getChildTables().values()) {
                if (!existsAndEqual((TableImpl) child, md)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    public static class TableMetadataKey implements MetadataKey, Serializable {
        private static final long serialVersionUID = 1L;
        private final String tableName;

        public TableMetadataKey(final String tableName) {
            this.tableName = tableName;
        }

        public TableMetadataKey(final String namespace,
                                final String tableName) {
            this.tableName = NameUtils.makeQualifiedName(namespace, tableName);
        }

        public String getTableName() {
            return tableName;
        }

        public MetadataKey getMetadataKey() {
            return this;
        }

        /*
         * For debugging
         */
        @Override
        public String toString() {
            return "TableMetadataKey[" +
                   (tableName != null ? tableName : "null") + "]";
        }
    }

    /**
     * Iterate over all tables, calling back to the callback for each.
     */
    public void iterateTables(TableMetadataIteratorCallback callback) {
        for (Table table : getTables().values()) {
            if (!iterateTables(table, callback)) {
                break;
            }
        }
    }

    /**
     * Implements iteration of all tables, depth-first (i.e. child tables are
     * visited before parents.
     */
    private static boolean
        iterateTables(Table table, TableMetadataIteratorCallback callback) {
        for (Table child : table.getChildTables().values()) {
            if (!iterateTables(child, callback)) {
                return false;
            }
        }
        if (!callback.tableCallback(table)) {
            return false;
        }
        return true;
    }

    /**
     * An interface used for operations that need to iterate the entire tree of
     * metadata.
     */
    public interface TableMetadataIteratorCallback {

        /**
         * Returns true if the iteration should continue, false if not.
         */
        boolean tableCallback(Table t);
    }

    private void ensureNamespaceMap() {
        if (namespaces == null) {
            namespaces = new TreeMap(
                FieldComparator.instance);
            /*
             * use TreeMap to present the keys in order
             * Note: TreeMap doesn't allow a null key.
             */
        }
    }

    public boolean hasNamespace(String namespace) {
        if (NameUtils.isInternalInitialNamespace(namespace)) {
            return true;
        }

        if (namespaces == null) {
            return false;
        }

        return namespaces.containsKey(namespace);
    }

    public NamespaceImpl getNamespace(String namespace) {
        if ( NameUtils.isInternalInitialNamespace(namespace) ) {
            /* In the case of the "sysdefault" namespace use a null value for
            ResourceOwner */
            return new NamespaceImpl(namespace, null /* ResourceOwner */);
        }

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

        return namespaces.get(namespace);
    }

    public NamespaceImpl createNamespace(String namespace, ResourceOwner owner)
    {
        if ( hasNamespace(namespace) ) {
            throw new IllegalArgumentException("Cannot create " +
                "namespace. Namespace already exists: '" + namespace + "' ");
        }

        NamespaceImpl nsObj = new NamespaceImpl(namespace, owner);
        ensureNamespaceMap();
        namespaces.put(namespace, nsObj);

        bumpSeqNum();
        if (changeHistory != null) {
            changeHistory.add(new AddNamespaceChange(namespace, owner, seqNum));
        }

        return nsObj;
    }

    public NamespaceImpl dropNamespace(String namespace) {
        if ( !hasNamespace(namespace) ) {
            throw new IllegalArgumentException("Cannot drop " +
                "namespace. Namespace doesn't exist: '" + namespace + "' ");
        }

        if ( !isNamespaceEmpty(namespace) ) {
            throw new IllegalArgumentException("Cannot drop " +
                "namespace. Namespace is not empty: '" + namespace + "' ");
        }

        ensureNamespaceMap();
        NamespaceImpl res = namespaces.remove(namespace);

        bumpSeqNum();
        if (changeHistory != null) {
            changeHistory.add(new RemoveNamespaceChange(namespace, seqNum));
        }

        return res;
    }

    /**
     * Returns true for namespaces that do not contain any tables.
     * For the "sysdefault" namespace
     * {@link oracle.kv.table.TableAPI#SYSDEFAULT_NAMESPACE_NAME} it always
     * returns false.
     */
    public boolean isNamespaceEmpty(String namespace) {
        if (NameUtils.isInternalInitialNamespace(namespace)) {
            return false;
        }

        if ( !hasNamespace(namespace) ) {
            return true;
        }

        for (Table table : getTables().values()) {
            if ( namespace!=null &&
                namespace.equals(table.getNamespace())) {
                return false;
            }
        }

        return true;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy