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

oracle.kv.impl.admin.TableDdlOperation 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.admin;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import oracle.kv.impl.admin.DdlHandler.DdlOperation;
import oracle.kv.impl.api.table.IndexImpl;
import oracle.kv.impl.api.table.IndexImpl.AnnotatedField;
import oracle.kv.impl.api.table.NameUtils;
import oracle.kv.impl.api.table.TableImpl;
import oracle.kv.impl.api.table.TableLimits;
import oracle.kv.impl.api.table.TableMetadata;
import oracle.kv.impl.security.AccessCheckUtils.TableContext;
import oracle.kv.impl.security.KVStorePrivilege;
import oracle.kv.impl.security.NamespacePrivilege;
import oracle.kv.impl.security.OperationContext;
import oracle.kv.impl.security.SystemPrivilege;
import oracle.kv.impl.security.TablePrivilege;
import oracle.kv.table.FieldDef;
import oracle.kv.table.Index;
import oracle.kv.table.Table;

/**
 * This class represents the table Ddl operations and their execution.
 * TableDDLOperations are generated after parsing a table ddl statement.
 */
abstract class TableDdlOperation  implements DdlOperation {

    private final String opName;
    private final TableImpl table;

    TableDdlOperation(String opName, TableImpl table) {
        this.opName = opName;
        this.table = table;
    }

    TableImpl getTable() {
        return table;
    }

    String opName() {
        return opName;
    }

    /**
     * Operation of creating table, needing CREATE_ANY_TABLE privilege.
     */
    public static class CreateTable extends TableDdlOperation {
        private final boolean ifNotExists;

        public CreateTable(TableImpl table,
                           TableLimits limits,
                           boolean ifNotExists) {
            super("CREATE TABLE", table);
            if (limits != null) {
                table.setTableLimits(limits);
            }
            this.ifNotExists = ifNotExists;
        }

        @Override
        public OperationContext getOperationCtx() {
            return new OperationContext() {
                @Override
                public String describe() {
                    final String nsName = getTable().getFullNamespaceName();
                    return opName() + (ifNotExists ? " IF NOT EXISTS " : " ") +
                        nsName;
                }

                @Override
                public List
                    getRequiredPrivileges() {

                    return Collections.singletonList(
                        new NamespacePrivilege.CreateTableInNamespace(
                            getTable().getInternalNamespace()));
                }
            };
        }

        @Override
        public void perform(DdlHandler ddlHandler) {
            final TableImpl table = getTable();
            String exceptionMsg;
            try {
                final Admin admin = ddlHandler.getAdmin();

                /* tableExistsAndEqual will throw if it exists and not equal */
                if (ifNotExists && tableExistsAndEqual(ddlHandler)) {
                    ddlHandler.operationSucceeds();
                    return;
                }

                final int planId = admin.getPlanner().createAddTablePlan
                    ("CreateTable", table, table.getParentName(), false);
                ddlHandler.approveAndExecute(planId);
                return;
            } catch (TableAlreadyExistsException taee) {
                if (ifNotExists) {
                    ddlHandler.operationSucceeds();
                    return;
                }
                exceptionMsg = taee.getMessage();
            } catch (IllegalCommandException ice) {
                exceptionMsg = ice.getMessage();
            }
            final String nsName = getTable().getFullNamespaceName();
            ddlHandler.operationFails(opName() + " failed for table " +
                                      nsName + ": " + exceptionMsg);
        }

        /**
         * Returns true if the table exists in the current metadata and is the
         * same as the table parameter.
         *
         * If the named table does not exist return false
         * If the named table exists and the definitions match, return true.
         * If the named table exists and the definitions don't match, throw
         * an exception, handled by the caller.
         */
        private boolean tableExistsAndEqual(DdlHandler ddlHandler) {
            final TableImpl table = getTable();
            final TableMetadata md = ddlHandler.getTableMetadata();
            assert md != null;
            final TableImpl existing = md.getTable(table.getInternalNamespace(),
                                                   table.getFullName());
            if (existing != null) {
                if (existing.fieldsEqual(table)) {
                    return true;
                }
                throw new IllegalCommandException
                    ("Table exists but definitions do not match");
            }
            return false;
        }
    }

    /**
     * Operation of evolving table, needing EVOLVE_TABLE privilege.
     */
    public static class EvolveTable extends TableDdlOperation {
        private final TableContext opCtx;

        public EvolveTable(TableImpl table) {
            super("EVOLVE TABLE", table);
            opCtx = new TableContext(opName(), table,
                new TablePrivilege.EvolveTable(table.getId(),
                    table.getInternalNamespace()));
        }

        @Override
        public OperationContext getOperationCtx() {
            return opCtx;
        }

        @Override
        public void perform(DdlHandler ddlHandler) {
            final TableImpl table = getTable();
            try {
                /*
                 * When TableDdl evolved the table it incremented the
                 * table version, which means that the table version is
                 * one more than it should be.  Subtract that one to
                 * reflect the fact that the same evolution will happen again.
                 */
                int tableVersion = table.getTableVersion() - 1;
                assert(tableVersion > 0);
                final int planId =
                    ddlHandler.getAdmin().getPlanner().createEvolveTablePlan
                    ("AlterTable", table.getInternalNamespace(),table.getFullName(),
                     tableVersion, table.getFieldMap(),
                     table.getDefaultTTL());
                ddlHandler.approveAndExecute(planId);
            } catch (IllegalCommandException ice) {
                ddlHandler.operationFails("ALTER TABLE failed for table " +
                       table.getFullName() + ": " + ice.getMessage());
            }
        }
    }

    /**
     * Operation of dropping table, needing DROP_ANY_TABLE privilege and
     * DROP_INDEX (if to remove data) for non-owners.
     */
    public static class DropTable extends TableDdlOperation {
        private final OperationContext opCtx;
        private final boolean ifExists;
        private final String tableName;
        private final String namespace;

        public DropTable(String namespace,
                         String tableName,
                         TableImpl tableIfExists,
                         boolean ifExists) {
            super("DROP TABLE", tableIfExists);

            this.ifExists = ifExists;
            this.tableName = tableName;
            this.namespace = namespace;

            if (tableIfExists == null) {
                opCtx = new NoTableOpContext(tableName);
            } else {
                final List privsToCheck = new ArrayList<>();
                if (!tableIfExists.getIndexes().isEmpty()) {
                    privsToCheck.add(
                        new TablePrivilege.DropIndex(tableIfExists.getId(),
                            namespace));
                }
                privsToCheck.add(
                    new NamespacePrivilege.DropTableInNamespace(namespace));
                opCtx = new TableContext(opName(), tableIfExists,
                                         privsToCheck);
            }
        }

        @Override
        public void perform(DdlHandler ddlHandler) {
            final Admin admin = ddlHandler.getAdmin();
            String exceptionMsg;
            try {
                final int planId = admin.getPlanner().createRemoveTablePlan
                    ("DropTable", namespace, tableName);
                ddlHandler.approveAndExecute(planId);
                return;
            } catch (TableNotFoundException tnfe) {
                if (ifExists) {
                    ddlHandler.operationSucceeds();
                    return;
                }
                exceptionMsg = tnfe.getMessage();
            } catch (IllegalCommandException ice) {
                exceptionMsg = ice.getMessage();
            }
            ddlHandler.operationFails(opName() + " failed for table " +
                                      tableName + ": " + exceptionMsg);
        }

        @Override
        public OperationContext getOperationCtx() {
            return opCtx;
        }
    }

    /**
     * Create an index (normal or full-text). Requires CREATE_INDEX privilege.
     */
    public static class CreateIndex extends TableDdlOperation {
        private final OperationContext opCtx;
        private final boolean ifNotExists;
        private final String indexName;
        private final String tableName;
        private final String namespace;
        private final String[] newFields;
        private final FieldDef.Type[] newTypes;
        private final AnnotatedField[] annotatedFields;
        private final Map properties;
        private final String indexComments;
        private final boolean override;
        private final boolean isFullText;

        public CreateIndex(TableImpl tableIfExists,
                           String namespace,
                           String tableName,
                           String indexName,
                           String[] newFields,
                           FieldDef.Type[] newTypes,
                           AnnotatedField[] annotatedFields,
                           Map properties,
                           String indexComments,
                           boolean ifNotExists,
                           boolean override) {
            super("CREATE [FULLTEXT] INDEX" +
                  (ifNotExists ? " IF NOT EXISTS" : "") ,
                  tableIfExists);

            this.ifNotExists = ifNotExists;
            this.indexName = indexName;
            this.tableName = tableName;
            this.namespace = namespace;
            this.newFields = newFields;
            this.newTypes = newTypes;
            this.annotatedFields = annotatedFields;
            this.properties = properties;
            this.indexComments = indexComments;
            this.override = override;
            isFullText = (annotatedFields != null);

            assert newFields == null || annotatedFields == null;

            if (tableIfExists == null) {
                opCtx = new NoTableOpContext(tableName);
            } else {
                opCtx = new TableContext(
                    opName(), tableIfExists,
                    new TablePrivilege.CreateIndex(tableIfExists.getId(),
                        namespace));
            }
        }

        @Override
        public OperationContext getOperationCtx() {
            return opCtx;
        }

        /**
         * Returns true if the index in the current tableDdl instance exists
         * and is equal to the fields of the current tableDdl.
         */
        private boolean indexExistsAndEqual(DdlHandler ddlHandler) {
            final TableMetadata md = ddlHandler.getTableMetadata();
            assert md != null;
            final TableImpl idxTable = md.getTable(namespace, tableName);
            if (idxTable != null) {
                IndexImpl index = (IndexImpl) idxTable.getIndex(indexName);
                if (index != null) {
                    if (isFullText) {
                        if (properties == null) {
                            if (!index.getProperties().isEmpty()) {
                                return false;
                            }
                        } else if (!Objects.equals(properties,
                                                   index.getProperties())) {
                            return false;
                        }
                        List fields =
                            index.getFieldsWithAnnotations();
                        return compareAnnotatedFields(fields);
                    }
                    /*
                     * At this time, purposely do not compare the type
                     * arrays, if they exist, when checking index equality.
                     * This prevents creation of 2 indexes with fields like
                     * this, for example:
                     *  (json.a as string) (json.a)
                     * For this test, those will be considered equivalent.
                     */
                    return compareFields(index);
                }
            }
            return false;
        }

        /**
         * Compare our list of regular fields against the given list.
         */
        private boolean compareFields(IndexImpl index) {
            return index.compareIndexFields(Arrays.asList(newFields));
        }

        /**
         * Compare our list of annotated fields against the given list.
         * This method is package-visible for testing purposes.
         */
        boolean compareAnnotatedFields(List fields) {
            if (annotatedFields.length == fields.size()) {
                for (int i = 0; i < annotatedFields.length; i++) {
                    if (!annotatedFields[i].equals(fields.get(i))) {
                        return false;
                    }
                }
            }
            return true;
        }

        @Override
        public void perform(DdlHandler ddlHandler) {
            final Admin admin = ddlHandler.getAdmin();
            if (ifNotExists && indexExistsAndEqual(ddlHandler)) {
                ddlHandler.operationSucceeds();
                return;
            }
            String exceptionMsg;
            try {
                int planId;
                if (!isFullText) {
                    planId = admin.getPlanner().createAddIndexPlan
                        ("CreateIndex", namespace, indexName,
                         tableName, newFields,
                         newTypes, indexComments);
                } else {
                    planId = admin.getPlanner().createAddTextIndexPlan
                        ("CreateTextIndex", namespace, indexName, tableName,
                         annotatedFields, properties, indexComments,
                         override);
                }
                ddlHandler.approveAndExecute(planId);
                return;
            } catch (IndexAlreadyExistsException iaee) {
                if (ifNotExists) {
                    ddlHandler.operationSucceeds();
                    return;
                }
                exceptionMsg = iaee.getMessage();
            } catch (IllegalCommandException ice) {
                exceptionMsg = ice.getMessage();
            }
            final String nsName =
                NameUtils.makeQualifiedName(namespace, tableName);
            ddlHandler.operationFails(
                                "CREATE " + (isFullText ? "[FULLTEXT] " : "") +
                                "INDEX failed for table " +
                                nsName + ", index " + indexName + ": " +
                                exceptionMsg);
        }
    }

    /**
     * Operation of dropping index, needing DROP_INDEX privilege.
     */
    public static class DropIndex extends TableDdlOperation {
        private final OperationContext opCtx;
        private final boolean ifExists;
        private final String indexName;
        private final String tableName;
        private final String namespace;
        private final boolean override;

        public DropIndex(String namespace,
                         String tableName,
                         TableImpl tableIfExists,
                         String indexName,
                         boolean ifExists,
                         boolean override) {

            super("DROP INDEX", tableIfExists);
            this.ifExists = ifExists;
            this.indexName = indexName;
            this.tableName = tableName;
            this.namespace = namespace;
            this.override = override;

            if (tableIfExists == null) {
                opCtx = new NoTableOpContext(tableName);
            } else {
                opCtx = new TableContext(opName(), tableIfExists,
                    new TablePrivilege.DropIndex(tableIfExists.getId(),
                        namespace));
            }
        }

        @Override
        public OperationContext getOperationCtx() {
            return opCtx;
        }

        @Override
        public void perform(DdlHandler ddlHandler) {
            final Admin admin = ddlHandler.getAdmin();
            String exceptionMsg;
            try {
                final int planId = admin.getPlanner().createRemoveIndexPlan
                    ("DropIndex", namespace, indexName, tableName, override);
                ddlHandler.approveAndExecute(planId);
                return;
            } catch (IndexNotFoundException infe) {
                if (ifExists) {
                    ddlHandler.operationSucceeds();
                    return;
                }
                exceptionMsg = infe.getMessage();
            } catch (IllegalCommandException ice) {
                exceptionMsg = ice.getMessage();
            }
            final String nsName =
                NameUtils.makeQualifiedName(namespace, tableName);
            ddlHandler.operationFails("DROP INDEX failed for table " + nsName +
                                      ", index " + indexName + ": " +
                                      exceptionMsg);
        }
    }


    /**
     * TODO for show and describe:
     *  o implement tabular output for tables and fields.  This may involve
     *  a new table formatter class or two
     *
     *  o implement index output (both types)
     *  o all indexes?
     *
     * Much of this display formatting should be modularized into formatting
     * interfaces and moved to other locations and perhaps be considered for
     * the public API (e.g. TableFormatter, IndexFormatter, etc).
     */

    /**
     * Operation of showing table or index, needing DBVIEW privilege.
     */
    public static class ShowTableOrIndex extends TableDdlOperation {

        private final String tableName;
        private final String namespace;
        private final boolean isShowTables;
        private final boolean showIndexes;
        private final boolean asJson;

        public ShowTableOrIndex(String namespace,
                                String tableName,
                                boolean isShowTables,
                                boolean showIndexes,
                                boolean asJson) {
            super("ShowTableOrIndex", null /* no need table */);
            this.tableName = tableName;
            this.namespace = namespace;
            this.isShowTables = isShowTables;
            this.showIndexes = showIndexes;
            this.asJson = asJson;
        }

        @Override
        public void perform(DdlHandler ddlHandler) {

            String resultString = null;
            final TableMetadata md = ddlHandler.getTableMetadata();

            /* show tables */
            if (isShowTables) {
                List tableList = md.listTables(namespace);
                resultString = formatList(namespace,
                                          "tables",
                                          tableList,
                                          asJson);
                ddlHandler.operationSucceeds();
            } else {
                /* table or index */
                final String nsName =
                    NameUtils.makeQualifiedName(namespace, tableName);
                TableImpl table = md.getTable(namespace, tableName);
                if (table == null) {
                    ddlHandler.operationFails(
                        "Table does not exist: " + nsName);
                    return;
                }

                ddlHandler.operationSucceeds();
                if (showIndexes) {
                    resultString = formatList
                        (namespace, "indexes",
                         new ArrayList(table.getIndexes().keySet()),
                         asJson);
                } else {
                    resultString = formatTableNames(namespace, table, asJson);
                }
            }
            ddlHandler.setResultString(resultString);
        }

        private static String formatTableNames(String namespace,
                                               TableImpl table,
                                               boolean asJson) {
            Table current = table;
            while (current.getParent() != null) {
                current = current.getParent();
            }
            List tableNames = new ArrayList();
            listTableHierarchy(current, tableNames);
            return formatList(namespace, "tableHierarchy" , tableNames, asJson);
        }

        private static void listTableHierarchy(Table table,
                                               List tableNames) {
            tableNames.add(table.getFullName());
            for (Table t : table.getChildTables().values()) {
                listTableHierarchy(t, tableNames);
            }
        }

        /**
         * Formats the list of tables.  If asJson is true a JSON output format
         * is used, otherwise it is a CRLF separated string of names.
         * JSON:  {"tables" : ["t1", "t2", ..., "tN"]}
         */
        private static String formatList(String namespace,
                                         String listName,
                                         List list,
                                         boolean asJson) {
            StringBuilder sb = new StringBuilder();
            boolean first = true;
            if (asJson) {
                sb.append("{");
                if (namespace != null) {
                    sb.append("\"namespace\": ").append("\"").append(namespace)
                        .append("\"\n");
                }
                sb.append("\"").append(listName).append("\"");
                sb.append(" : [");
                for (String s : list) {
                    if (!first) {
                        sb.append(",");
                    }
                    first = false;
                    sb.append("\"");
                    sb.append(s);
                    sb.append("\"");
                }
                sb.append("]}");
            } else {
                sb.append(listName);
                if (namespace != null) {
                    sb.append("(namespace ").append(namespace).append(")");
                }
                for (String s : list) {
                    /*
                     * Indent list members by 2 spaces.
                     */
                    sb.append("\n  ");
                    sb.append(s);
                }
            }
            return sb.toString();
        }

        @Override
        public OperationContext getOperationCtx() {
            return new OperationContext() {
                @Override
                public String describe() {
                    final StringBuilder sb = new StringBuilder();
                    sb.append("SHOW");
                    if (asJson) {
                        sb.append(" AS JSON");
                    }
                    if (isShowTables) {
                        sb.append(" TABLES");
                    } else if (showIndexes) {
                        sb.append( " INDEXES ON ");
                        sb.append(tableName);
                    } else {
                        sb.append(" TABLE ");
                        sb.append(tableName);
                    }
                    return sb.toString();
                }
                @Override
                public List
                    getRequiredPrivileges() {
                        return SystemPrivilege.dbviewPrivList;
                }
            };
        }
    }

    /**
     * Operation of describing table, needing DBVIEW privilege.
     */
    public static class DescribeTable extends TableDdlOperation {
        private final String tableName;
        private final String namespace;
        private final String indexName;
        private final boolean asJson;
        private final List> fieldPaths;

        public DescribeTable(String namespace,
                             String tableName,
                             String indexName,
                             List> fieldPaths,
                             boolean asJson) {
            super("DESCRIBE TABLE", null /* table */);

            assert tableName != null;
            this.asJson = asJson;
            this.namespace = namespace;
            this.tableName = tableName;
            this.indexName = indexName;
            this.fieldPaths = fieldPaths;
        }

        /**
         * TODO:
         *  o implement tabular output for tables and fields.  This may involve
         *  a new table formatter class or two
         *
         * Much of this display formatting should be modularized into formatting
         * interfaces and moved to other locations and perhaps be considered for
         * the public API (e.g. TableFormatter, IndexFormatter, etc).
         */

        @Override
        public void perform(DdlHandler ddlHandler) {

            String resultString = null;

            final TableMetadata md = ddlHandler.getTableMetadata();
            final String nsName = NameUtils.makeQualifiedName(namespace,
                tableName);
            TableImpl table = md.getTable(namespace, tableName);
            if (table == null) {
                ddlHandler.operationFails("Table does not exist: " + nsName);
                return;
            }
            if (indexName != null) {
                Index index = table.getIndex(indexName);
                if (index == null) {
                    ddlHandler.operationFails(
                        "Index does not exist: " + indexName + ", on table " +
                        nsName);
                    return;
                }
                resultString = formatIndex((IndexImpl)index, asJson);
            } else {

                /*
                 * formatTable can throw IAE.
                 */
                try {
                    resultString = formatTable(table, asJson);
                    if (resultString == null) {
                        return;
                    }
                } catch (IllegalArgumentException iae) {
                    ddlHandler.operationFails(iae.getMessage());
                    return;
                }
            }
            ddlHandler.setResultString(resultString);
        }

        /**
         * TODO: handle non-JSON output and nested field lists (see TableImpl).
         */
        private String formatTable(TableImpl table,
                                   boolean asJson1) {
            return table.formatTable(asJson1, fieldPaths);
        }

        private static String formatIndex(IndexImpl index,
                                          boolean asJson1) {
            return index.formatIndex(asJson1);
        }

        @Override
        public OperationContext getOperationCtx() {
            return new OperationContext() {
                @Override
                public String describe() {
                    return opName() + ": " + tableName;
                }
                @Override
                public List
                    getRequiredPrivileges() {
                        return SystemPrivilege.dbviewPrivList;
                }
            };
        }
    }

    /**
     * A special context for the case where the table does not exist, but we
     * need to do some check first, e.g., in the case with "IF EXISTS" option.
     */
    private static final class NoTableOpContext
        implements OperationContext {
        private final String phantomTable;
        NoTableOpContext(String phantomTable) {
            this.phantomTable = phantomTable;
        }
        @Override
        public String describe() {
            return "Operation on an non-existing table: " + phantomTable;
        }
        @Override
        public List
            getRequiredPrivileges() {
                return SystemPrivilege.dbviewPrivList;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy