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

oracle.kv.shell.PutCommand Maven / Gradle / Ivy

Go to download

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

The 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.shell;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import oracle.kv.KVStore;
import oracle.kv.Key;
import oracle.kv.Value;
import oracle.kv.impl.admin.client.CommandShell;
import oracle.kv.impl.api.table.NameUtils;
import oracle.kv.impl.api.table.TableJsonUtils;
import oracle.kv.impl.util.CommandParser;
import oracle.kv.shell.CommandUtils.RunTableAPIOperation;
import oracle.kv.table.FieldDef;
import oracle.kv.table.FieldValue;
import oracle.kv.table.PrimaryKey;
import oracle.kv.table.RecordValue;
import oracle.kv.table.Row;
import oracle.kv.table.Table;
import oracle.kv.table.TableAPI;
import oracle.kv.table.WriteOptions;
import oracle.kv.util.shell.CommandWithSubs;
import oracle.kv.util.shell.LoadTableUtils;
import oracle.kv.util.shell.Shell;
import oracle.kv.util.shell.ShellCommand;
import oracle.kv.util.shell.ShellException;

import org.apache.avro.Schema;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonProcessingException;

public class PutCommand extends CommandWithSubs {
    final static String VALUE_FLAG = "-value";
    final static String JSON_FLAG = "-json";
    final static String FILE_FLAG = "-file";
    final static String IFABSENT_FLAG = "-if-absent";
    final static String IFABSENT_FLAG_DESC = IFABSENT_FLAG;
    final static String IFPRESENT_FLAG = "-if-present";
    final static String IFPRESENT_FLAG_DESC = IFPRESENT_FLAG;

    final static String COMMAND_OVERVIEW =
        "The put command encapsulates commands that put key/value pairs " +
        "into a store" + eol + "and rows into a table.";

    private static final
        List subs =
                   Arrays.asList(new PutKVCommand(),
                                 new PutTableCommand());
    public PutCommand() {
        super(subs, "put", 3, 2);
        overrideJsonFlag = true;
    }

    @Override
    protected String getCommandOverview() {
        return COMMAND_OVERVIEW;
    }

    static class PutKVCommand extends SubCommand {
        final static String KV_COMMAND = "kv";
        final static String KEY_FLAG = "-key";
        final static String KEY_FLAG_DESC = KEY_FLAG + " ";
        final static String HEX_FLAG = "-hex";
        final static String HEX_FLAG_DESC = HEX_FLAG;
        final static String VALUE_FLAG_DESC = VALUE_FLAG + " ";
        final static String JSON_SCHEMA_FLAG_DESC = JSON_FLAG + " ";
        final static String FILE_FLAG_DESC = FILE_FLAG;

        final static String PUT_KV_SYNTAX =
            "put " + KV_COMMAND + " " + KEY_FLAG_DESC + " " +
            VALUE_FLAG_DESC + " [" + FILE_FLAG_DESC + "] " + "[" +
            HEX_FLAG_DESC + " | " + JSON_SCHEMA_FLAG_DESC + "]" + eolt +
            "[" + IFABSENT_FLAG_DESC + " | " + IFPRESENT_FLAG_DESC + "]";

        final static String PUT_KV_DESCRIPTION =
            "Puts the specified key, value pair into the store" + eolt +
            FILE_FLAG + " indicates that the value parameter is a file that " +
            "contains the" + eolt + "actual value" + eolt +
            HEX_FLAG + " indates that the value is a BinHex encoded byte " +
            "value with Base64" + eolt +
            JSON_FLAG + " indicates that the value is a JSON string." + eolt +
            JSON_FLAG + " and -file can be used together." + eolt +
            IFABSENT_FLAG + " indicates to put a key/value pair only if " +
            "no value for " + eolt + "the given key is present. " + eolt +
            IFPRESENT_FLAG + " indicates to put a key/value pair only if " +
            "a value for " + eolt + "the given key is present. ";

        PutKVCommand() {
            super(KV_COMMAND, 2);
        }

        @SuppressWarnings("null")
        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            Shell.checkHelp(args, this);
            Key key = null;
            Value value = null;
            byte[] valueBytes = null;
            String stringValue = null;
            String schemaName = null;
            boolean isPutIfAbsent = false;
            boolean isPutIfPresent = false;
            boolean isJson = false;
            boolean isFile = false;
            boolean isBinHex = false;
            KVStore store = ((CommandShell)shell).getStore();

            for (int i = 1; i < args.length; i++) {
                String arg = args[i];
                if (KEY_FLAG.equals(arg)) {
                    String keyString = Shell.nextArg(args, i++, this);
                    try {
                        key = CommandUtils.createKeyFromURI(keyString);
                    } catch (IllegalArgumentException iae) {
                        invalidArgument(iae.getMessage());
                    }
                } else if (VALUE_FLAG.equals(arg)) {
                    stringValue = Shell.nextArg(args, i++, this);
                } else if (FILE_FLAG.equals(arg)) {
                    isFile = true;
                } else if (HEX_FLAG.equals(arg)) {
                    isBinHex = true;
                } else if (JSON_FLAG.equals(arg)) {
                    schemaName = Shell.nextArg(args, i++, this);
                    isJson = true;
                } else if (IFABSENT_FLAG.equals(arg)) {
                    isPutIfAbsent = true;
                } else if (IFPRESENT_FLAG.equals(arg)) {
                    isPutIfPresent = true;
                } else {
                    shell.unknownArgument(arg, this);
                }
            }

            if (key == null) {
                shell.requiredArg(KEY_FLAG, this);
            }
            if (stringValue == null) {
                shell.requiredArg(VALUE_FLAG, this);
            }

            /* By default, set isPutIfAbsent/ifPutIfPresent to true. */
            if (!isPutIfAbsent && !isPutIfPresent) {
                isPutIfAbsent = true;
                isPutIfPresent = true;
            }

            if (isFile) {
                valueBytes = CommandUtils.readFromFile(stringValue);
            } else {
                valueBytes = stringValue.getBytes();
            }

            if (isJson) {
                InputStream in = null;
                in = new ByteArrayInputStream(valueBytes);
                value = createJsonValue(schemaName, in, store);
            } else if (isBinHex) {
                byte[] decoded =
                    CommandUtils.decodeBase64(new String(valueBytes));
                value = Value.createValue(decoded);
            } else {
                /* create the actual value */
                value = Value.createValue(valueBytes);
            }

            String retString = null;
            boolean updated = false;
            try {
                if (isPutIfAbsent && isPutIfPresent) {
                    if (store.putIfAbsent(key, value) == null) {
                        store.putIfPresent(key, value);
                        updated = true;
                    }
                } else if (isPutIfAbsent) {
                    if (store.putIfAbsent(key, value) == null) {
                        retString = "A value was already present with the " +
                            "given key " + CommandUtils.createURI(key) + ".";
                    }
                } else {
                    updated = true;
                    if (store.putIfPresent(key, value) == null) {
                        retString = "No existing value was present with the " +
                            "given key " + CommandUtils.createURI(key) + ".";
                    }
                }
            } catch (Exception e) {
                throw new ShellException("Exception from NoSQL DB in put. " +
                                         e.getMessage(), e);
            }

            if (retString == null) {
                retString = "Operation successful, record " +
                            (updated?"updated.":"inserted.");
            } else {
                retString = "Operation failed, " + retString;
            }
            return retString;
        }

        @Override
        protected String getCommandSyntax() {
            return PUT_KV_SYNTAX;
        }

        @Override
        protected String getCommandDescription() {
            return PUT_KV_DESCRIPTION;
        }

        @SuppressWarnings("deprecation")
        private Value createJsonValue(String schema,
                                      InputStream content,
                                      KVStore store)
            throws ShellException {

            try {
                oracle.kv.avro.AvroCatalog catalog = store.getAvroCatalog();
                catalog.refreshSchemaCache(null);
                Map schemaMap = catalog.getCurrentSchemas();
                Schema sch = schemaMap.get(schema);
                if (sch == null) {
                    throw new ShellException("Schema does not exist in the " +
                                             "catalog: " + schema);
                }
                JsonNode obj =
                    TableJsonUtils.getObjectMapper().readTree(content);
                oracle.kv.avro.JsonAvroBinding jsonBinding =
                    catalog.getJsonBinding(sch);
                return jsonBinding.toValue(new oracle.kv.avro.JsonRecord(obj,
                                                                         sch));
            } catch (JsonProcessingException jpe) {
                throw new ShellException(eolt +
                                         "Could not create JSON from input: " +
                                         eolt + jpe.getMessage(), jpe);
            } catch (IOException ioe) {
                throw new ShellException(eolt +
                                         "Could not create JSON from input: " +
                                         eolt + ioe.getMessage(), ioe);
            } catch (IllegalArgumentException iae) {
                String errMsg = (iae.getCause() != null)?
                                iae.getCause().getMessage():iae.getMessage();
                throw new ShellException(eolt +
                                         "Could not create JSON from input: " +
                                         eolt + errMsg, iae);
            }
        }
    }

    /*
     * table put command
     * */
    static class PutTableCommand extends SubCommand {
        final static String TABLE_COMMAND = "table";

        final static String TABLE_FLAG = "-name";
        final static String TABLE_FLAG_DESC = TABLE_FLAG + " ";
        final static String FIELD_FLAG = "-field";
        final static String FIELD_FLAG_DESC = FIELD_FLAG + " ";
        final static String VALUE_FLAG_DESC = VALUE_FLAG + " ";
        final static String NULL_VALUE_FLAG = "-null-value";
        final static String NULL_VALUE_FLAG_DESC = "-null-value";
        final static String JSON_FLAG_DESC = JSON_FLAG + " ";
        final static String FILE_FLAG_DESC = FILE_FLAG + " ";
        final static String UPDATE_FLAG = "-update";
        final static String UPDATE_FLAG_DESC = UPDATE_FLAG;
        final static String EXACT_FLAG = "-exact";
        final static String EXACT_FLAG_DESC = EXACT_FLAG;

        final static String PUT_TABLE_SYNTAX =
            "put " + TABLE_COMMAND + " " + TABLE_FLAG_DESC +
            " [" + IFABSENT_FLAG_DESC + " | " + IFPRESENT_FLAG_DESC + "]" +
            eolt + "[" + JSON_FLAG_DESC + "] [" + FILE_FLAG_DESC + "]" +
            "[" + EXACT_FLAG_DESC + "] [" + UPDATE_FLAG_DESC + "]";

        final static String PUT_TABLE_DESCRIPTION =
            "Put a row into the named table.  The table name is an " +
            "optionally" + eolt + "namespace qualified " +
            "dot-separated name with the format " + eolt +
            "[ns:]tableName[.childTableName]+." + eolt +
            IFABSENT_FLAG + " indicates to put a row only if the row does " +
            "not exist." + eolt +
            IFPRESENT_FLAG + " indicates to put a row only if the row " +
            "already exists." + eolt +
            JSON_FLAG + " indicates that the value is a JSON string." + eolt +
            FILE_FLAG + " can be used to load JSON strings from a file." +
            eolt + EXACT_FLAG + " indicates that the input json string or file"
            + " must contain values" + eolt + "for all columns in the table, " +
            "and cannot contain extraneous fields." + eolt +
            UPDATE_FLAG + " can be used to partially update the " +
            "existing record.";

        private final static String currentPutParams = "currentPutParams";
        private final TableCmdWithSubs cmdSubs = new TablePutSubs();

        PutTableCommand() {
            super(TABLE_COMMAND, 3);
        }

        @Override
        public String execute(String[] args, Shell shell)
            throws ShellException {

            PutArgs putParams =
                (PutArgs)getVariable(currentPutParams);
            if (putParams == null) {
                Shell.checkHelp(args, this);
                String tableName = null;
                String namespace = null;
                String jsonString = null;
                String fileName = null;
                Boolean ifAbsent = null;
                boolean isUpdate = false;
                boolean jsonExact = false;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if (TABLE_FLAG.equals(arg)) {
                        tableName = Shell.nextArg(args, i++, this);
                    } else if (IFABSENT_FLAG.equals(arg)) {
                        ifAbsent = true;
                    } else if (IFPRESENT_FLAG.equals(arg)) {
                        ifAbsent = false;
                    } else if (JSON_FLAG.equals(arg)) {
                        jsonString = Shell.nextArg(args, i++, this);
                    } else if (FILE_FLAG.equals(arg)) {
                        fileName = Shell.nextArg(args, i++, this);
                    } else if (UPDATE_FLAG.equals(arg)) {
                        isUpdate = true;
                        ifAbsent = false;
                    } else if (EXACT_FLAG.equals(arg)) {
                        jsonExact = true;
                    } else {
                        shell.unknownArgument(arg, this);
                    }
                }

                if (tableName == null) {
                    shell.requiredArg(TABLE_FLAG, this);
                }

                final CommandShell cmdShell = ((CommandShell)shell);
                final TableAPI tableImpl = cmdShell.getStore().getTableAPI();
                namespace = NameUtils.getNamespaceFromQualifiedName(tableName);
                final String fullName =
                    NameUtils.getFullNameFromQualifiedName(tableName);
                if (namespace == null) {
                    namespace = cmdShell.getNamespace();
                }

                Table table = CommandUtils.findTable(tableImpl,
                                                     namespace,
                                                     fullName);

                final WriteOptions wro =
                    new WriteOptions(cmdShell.getStoreDurability(),
                                     cmdShell.getRequestTimeout(),
                                     TimeUnit.MILLISECONDS);

                if (fileName != null) {
                    return putJsonFromFile(tableImpl, table, fileName, wro,
                                           ifAbsent, isUpdate, jsonExact);
                }
                Row row = null;
                if (jsonString != null) {
                    try {
                        /*
                         * Put a single row parsed from the inputed JSON string.
                         */
                        row = createRowFromJson(tableImpl, table, jsonString,
                                                isUpdate, jsonExact);
                    } catch (IllegalArgumentException iae) {
                        throw new ShellException(iae.getMessage(), iae);
                    }
                    return doPutRow(tableImpl, row, wro, ifAbsent);
                }
                row = table.createRow();
                ShellCommand cmd = this.clone();
                cmd.addVariable(currentPutParams,
                                new PutArgs(row, ifAbsent, isUpdate));
                cmd.setPrompt(tableName);
                shell.pushCurrentCommand(cmd);
                return null;
            }
            return cmdSubs.execute(putParams, args, shell);
        }

        private static Row createRowFromJson(TableAPI tableImpl,
                                             Table table,
                                             String json,
                                             boolean isUpdate,
                                             boolean isExact) {
            Row row;
            if (isUpdate == true) {
                PrimaryKey key = table.createPrimaryKeyFromJson(json, false);
                row = tableImpl.get(key, null);
                if (row == null) {
                    throw new IllegalArgumentException(
                        "No existing record was present with the given " +
                        "primary key: " + key.toJsonString(false));
                }
                row.copyFrom(table.createRowFromJson(json, isExact));
            } else {
                row = table.createRowFromJson(json, isExact);
            }
            return row;
        }

        private String putJsonFromFile(final TableAPI tableImpl,
                                       final Table table,
                                       final String fileName,
                                       final WriteOptions wro,
                                       final Boolean ifAbsent,
                                       final boolean isUpdate,
                                       final boolean jsonExact)
            throws ShellException {

            final StringBuilder message = new StringBuilder();
            long numRows = 0;
            try {
                numRows = new LoadTableUtils.Loader(tableImpl) {
                    @Override
                    public Row createRowFromJson(Table target, String json) {
                        return PutTableCommand.createRowFromJson(tableImpl,
                                                                 table,
                                                                 json,
                                                                 isUpdate,
                                                                 jsonExact);
                    }

                    @Override
                    public boolean doPut(TableAPI tableAPI,
                                         Row row,
                                         WriteOptions options) {

                        PutResult ret = executePutOp(tableAPI, row,
                                                     options, ifAbsent);
                        if (ret == PutResult.NONE) {
                            message.append(getReturnMessage(ret,
                                                            ifAbsent,
                                                            row));
                            return false;
                        }
                        return true;
                    }
                }.loadJsonToTable(table, fileName, wro, true);
            } catch (IOException ioe) {
                throw new ShellException(ioe.getMessage(), ioe);
            } catch (RuntimeException rte) {
                throw new ShellException(rte.getMessage(), rte);
            }

            if (message.length() > 0) {
                return message.toString();
            }
            final String op = (ifAbsent == null) ? "Loaded " :
                                (ifAbsent ? "Inserted " : "Updated ");
            return message.append(op)
                    .append(numRows)
                    .append((numRows > 1 ? " rows to ":" row to "))
                    .append(table.getFullName())
                    .toString();
        }

        private static class TablePutSubs extends TableCmdWithSubs {
            private static final
                List tablePutSubs =
                               Arrays.asList(new TableAddArrayValueSub(),
                                             new TableAddMapValueSub(),
                                             new TableAddRecordValueSub(),
                                             new TableAddValueSub(),
                                             new TableCancelSub(),
                                             new TableExitSub(),
                                             new TableShowSub());
            TablePutSubs() {
                super(tablePutSubs, "", 0, 2);
            }

            @Override
            protected String getCommandOverview() {
                return "Set field value.";
            }
        }

        @Override
        protected String getCommandSyntax() {
            return PUT_TABLE_SYNTAX;
        }

        @Override
        protected String getCommandDescription() {
            return PUT_TABLE_DESCRIPTION;
        }

        private static class PutArgs {
            private final String fieldName;
            private FieldValue fieldValue;
            private final Boolean ifAbsent;
            private final boolean isUpdate;

            PutArgs(String name, FieldValue value) {
                this(name, value, null, false);
            }

            PutArgs(FieldValue value, Boolean ifAbsent, boolean isUpdate) {
                this(null, value, ifAbsent, isUpdate);
            }

            PutArgs(String name, FieldValue value,
                    Boolean ifAbsent, boolean isUpdate) {
                this.fieldName = name;
                this.fieldValue = value;
                this.ifAbsent = ifAbsent;
                this.isUpdate = isUpdate;
            }

            FieldValue getFieldValue() {
                return this.fieldValue;
            }

            void setFieldValue(FieldValue value) {
                this.fieldValue = value;
            }

            Boolean isPutIfAbsent() {
                return this.ifAbsent;
            }

            String getFieldName() {
                return this.fieldName;
            }

            boolean isUpdate() {
                return this.isUpdate;
            }
        }

        static abstract class TableCmdWithSubs extends CommandWithSubs {
            private PutArgs putArgs = null;
            TableCmdWithSubs(List subCommands,
                             String name,
                             int prefixLength,
                             int minArgCount) {
                super(subCommands, name, prefixLength, minArgCount);
                initSubs(subCommands);
            }

            private void initSubs(List tblSubs) {
                for (SubCommand command: tblSubs) {
                    ((TableCmdSubCommand)command).setParentCommand(this);
                }
            }

            protected String execute(PutArgs opArgs,
                                     String[] args,
                                     Shell shell)
                throws ShellException {

                if (isHelpCommand(args[1], shell)) {
                    String[] argsEx = new String[args.length - 1];
                    argsEx[0] = args[0];
                    System.arraycopy(args, 2, argsEx, 1, args.length - 2);
                    return getHelp(argsEx, shell);
                }
                this.putArgs = opArgs;
                return execute(args, shell);
            }

            protected FieldValue getFieldValue() {
                return putArgs.getFieldValue();
            }

            protected void setFieldValue(FieldValue value) {
                putArgs.setFieldValue(value);
            }

            protected Boolean isPutIfAbsent() {
                return putArgs.isPutIfAbsent();
            }

            protected String getFieldName() {
                return putArgs.getFieldName();
            }

            protected boolean isUpdate() {
                return putArgs.isUpdate();
            }

            private boolean isHelpCommand(String commandName, Shell shell) {
                ShellCommand cmd = shell.findCommand(commandName);
                return (cmd instanceof Shell.HelpCommand);
            }
        }

        static abstract class TableCmdSubCommand extends SubCommand {
            CommandWithSubs parentCommand = null;

            protected TableCmdSubCommand(String name, int prefixMatchLength) {
                super(name, prefixMatchLength);
            }

            protected void setParentCommand(CommandWithSubs command) {
                parentCommand = command;
            }

            protected FieldValue getCurrentFieldValue() {
                return ((TableCmdWithSubs)parentCommand).getFieldValue();
            }

            protected void setCurrentFieldValue(FieldValue value) {
                ((TableCmdWithSubs)parentCommand).setFieldValue(value);
            }

            protected String getCurrentFieldName() {
                return ((TableCmdWithSubs)parentCommand).getFieldName();
            }

            protected Boolean isPutIfAbsent() {
                return ((TableCmdWithSubs)parentCommand).isPutIfAbsent();
            }

            protected boolean isPutUpdate() {
                return ((TableCmdWithSubs)parentCommand).isUpdate();
            }
        }

        static class TableAddValueSub extends TableCmdSubCommand {
            final static String COMMAND = "add-value";
            final static String FILE_BINARY_DESC = FILE_FLAG +
                " ";

            final static String COMMAND_DESCRIPTION =
                "Set field value." + eolt +
                "-file flag can be used to input binary value from " +
                "a file" + eolt + "for BINARY or FIXED_BINARY field.";

            private final boolean valueIsNullable;
            private final boolean fieldIsRequired;

            protected TableAddValueSub() {
                this(true, true);
            }

            protected TableAddValueSub(boolean fieldIsRequired,
                                       boolean valueIsNullable) {
                super(COMMAND, 5);
                this.fieldIsRequired = fieldIsRequired;
                this.valueIsNullable = valueIsNullable;
            }

            @Override
            public String execute(String[] args, Shell shell)
                throws ShellException {

                Shell.checkHelp(args, this);
                String fieldName = null;
                String sValue = null;
                boolean nullValue = false;
                boolean isFile = false;
                for (int i = 1; i < args.length; i++) {
                    String arg = args[i];
                    if (FIELD_FLAG.equals(arg)) {
                        fieldName = Shell.nextArg(args, i++, this);
                    } else if (VALUE_FLAG.equals(arg)) {
                        sValue = Shell.nextArg(args, i++, this);
                    } else if (valueIsNullable && NULL_VALUE_FLAG.equals(arg)) {
                        nullValue = true;
                    } else if (FILE_FLAG.equals(arg)) {
                        sValue = Shell.nextArg(args, i++, this);
                        isFile = true;
                    } else {
                        shell.unknownArgument(arg, this);
                    }
                }

                FieldValue currentVal = getCurrentFieldValue();
                if (fieldIsRequired && fieldName == null) {
                    shell.requiredArg(FIELD_FLAG, this);
                }
                if (nullValue) {
                    putNull(currentVal, fieldName);
                    return null;
                }

                FieldValue fdVal = null;
                if (sValue == null) {
                    /* Get fieldValue object from current command variable. */
                    ShellCommand cmd = shell.getCurrentCommand();
                    fdVal = (FieldValue)cmd.getVariable(fieldName);
                    if (fdVal == null) {
                        shell.requiredArg(VALUE_FLAG, this);
                    } else {
                        cmd.removeVariable(fieldName);
                    }
                } else {
                    FieldDef def =
                        CommandUtils.getFieldDef(currentVal, fieldName);
                    /* -file can only be used for binary or fixed field. */
                    if (isFile && (!def.isBinary() && !def.isFixedBinary())) {
                        invalidArgument(FILE_FLAG +
                            " can not be used for " + def.getType() + " field");
                    }
                    /**
                     * add-value command could not be used for complex type
                     * field: array, map and record.
                     */
                    if (def.isArray() || def.isMap() || def.isRecord()) {
                        String sType = def.getType().toString().toLowerCase();
                        throw new ShellException("Can't use " + COMMAND +
                            " for " + sType + " field, please run add-" +
                            sType + "-value to add value.");
                    }
                    fdVal = CommandUtils.createFieldValue(def, sValue, isFile);
                }
                putValue(currentVal, fieldName, fdVal);

                if (currentVal.isRow() && isPutUpdate()) {
                    TableAPI tableImpl =
                        ((CommandShell)shell).getStore().getTableAPI();
                    Row newRow = updateIfExists(tableImpl, currentVal.asRow());
                    if (newRow != null) {
                        setCurrentFieldValue(newRow);
                    }
                }
                return null;
            }

            private void putNull(FieldValue currentVal, String fieldName)
                throws ShellException {

                if (currentVal instanceof RecordValue) {
                    try {
                        currentVal.asRecord().putNull(fieldName);
                    } catch (IllegalArgumentException iae) {
                        throw new ShellException(iae.getMessage());
                    }
                } else {
                    throw new ShellException(
                        "Can not set null to the field: " + fieldName);
                }
            }

            private void putValue(FieldValue currentVal,
                                  String field, FieldValue value)
                throws ShellException {

                try {
                    if (currentVal.isRecord()) {
                        currentVal.asRecord().put(field, value);
                    } else if (currentVal.isArray()) {
                        currentVal.asArray().add(value);
                    } else if (currentVal.isMap()) {
                        currentVal.asMap().put(field, value);
                    }
                } catch (IllegalArgumentException iae) {
                    throw new ShellException(iae.getMessage());
                }
            }

            private Row updateIfExists(TableAPI tableImpl, Row row) {
                /* No need to update if all fields are filled in. */
                if (row.size() == row.getTable().getFields().size()) {
                    return null;
                }

                /* Check if all primary key fields' value are provided. */
                List pkFields = row.getTable().getPrimaryKey();
                for(String fname: pkFields) {
                    if (row.get(fname) == null) {
                        return null;
                    }
                }

                /* Check if row associated with the primary key exists. */
                PrimaryKey key = row.createPrimaryKey();
                Row retRow = tableImpl.get(key, null);
                if (retRow == null) {
                    return null;
                }

                /* Copy values from the current row. */
                retRow.copyFrom(row);
                return retRow;
            }

            @Override
            protected String getCommandDescription() {
                return COMMAND_DESCRIPTION;
            }

            @Override
            protected String getCommandSyntax() {
                String fieldDesc =
                    fieldIsRequired ? " " + FIELD_FLAG_DESC : "";
                String nullValueDesc =
                    valueIsNullable ? NULL_VALUE_FLAG + " | " + eolt : "";
                return COMMAND + fieldDesc + " [" + VALUE_FLAG_DESC +
                    " | " + nullValueDesc + FILE_BINARY_DESC + "]";
            }
        }

        abstract static class TableAddComplexValueSub
            extends TableCmdSubCommand {

            private static final String VAR_NAME = "currentVariable";
            private final boolean fieldIsRequired;

            TableAddComplexValueSub(String name, int prefixMatchLength) {
                this(name, prefixMatchLength, true);
            }

            TableAddComplexValueSub(String name, int prefixMatchLength,
                                    boolean fieldIsRequired) {
                super(name, prefixMatchLength);
                this.fieldIsRequired = fieldIsRequired;
            }

            abstract FieldValue createValue(FieldDef fieldDef)
                throws ShellException;

            abstract TableCmdWithSubs getCmdSubs();

            @Override
            public String execute(String[] args, Shell shell)
                throws ShellException {

                String varName = VAR_NAME;
                PutArgs putArgs = ((PutArgs)getVariable(varName));
                if (putArgs == null) {
                    Shell.checkHelp(args, this);
                    FieldValue currentVal = getCurrentFieldValue();
                    String fieldName = null;
                    for (int i = 1; i < args.length; i++) {
                        String arg = args[i];
                        if (FIELD_FLAG.equals(arg)) {
                            fieldName = Shell.nextArg(args, i++, this);
                        } else {
                            shell.unknownArgument(arg, this);
                        }
                    }

                    if (fieldIsRequired && fieldName == null) {
                        shell.requiredArg(FIELD_FLAG, this);
                    }
                    FieldDef fieldDef =
                        CommandUtils.getFieldDef(currentVal, fieldName);
                    FieldValue retVal = createValue(fieldDef);
                    ShellCommand cmd = this.clone();
                    if (fieldName == null) {
                        fieldName = "element";
                    }
                    cmd.addVariable(varName, new PutArgs(fieldName, retVal));
                    cmd.setPrompt(fieldName);
                    shell.pushCurrentCommand(cmd);
                    return null;
                }
                return getCmdSubs().execute(putArgs, args, shell);
            }

            @Override
            protected String getCommandSyntax() {
                return getCommandName() + " " +
                        (fieldIsRequired ?
                            FIELD_FLAG_DESC :
                            CommandParser.optional(FIELD_FLAG_DESC));
            }
        }

        /* sub command: add-map-value */
        static class TableAddMapValueSub extends TableAddComplexValueSub {
            final static String COMMAND = "add-map-value";
            final static String COMMAND_DESCRIPTION = "Set map field value.";
            private static TableCmdWithSubs cmdSubs = new MapValueSubs();

            private static class MapValueSubs extends TableCmdWithSubs {
                private static
                    List complexValueSubs =
                               Arrays.asList(new TableAddArrayValueSub(),
                                             new TableAddMapValueSub(),
                                             new TableAddRecordValueSub(),
                                             new TableAddValueSub(true, false),
                                             new TableCancelSub(),
                                             new TableExitSub(),
                                             new TableShowSub());
                MapValueSubs() {
                    super(complexValueSubs, "", 0, 2);
                }

                @Override
                public String getCommandOverview() {
                    return COMMAND_DESCRIPTION;
                }
            }

            TableAddMapValueSub() {
                super(COMMAND, 5);
            }

            TableAddMapValueSub(boolean fieldIsRequired) {
                super(COMMAND, 5, fieldIsRequired);
            }

            @Override
            TableCmdWithSubs getCmdSubs() {
                return cmdSubs;
            }

            @Override
            FieldValue createValue(FieldDef fieldDef)
                throws ShellException {

                try {
                    return fieldDef.createMap();
                } catch (ClassCastException cce) {
                    throw new ShellException(cce.getMessage());
                }
            }

            @Override
            protected String getCommandDescription() {
                return COMMAND_DESCRIPTION;
            }
        }

        /* sub command: add-array-value */
        static class TableAddArrayValueSub extends TableAddComplexValueSub {
            final static String COMMAND = "add-array-value";
            final static String COMMAND_DESCRIPTION = "Set array field value.";
            private static TableCmdWithSubs cmdSubs = new ArrayValueSubs();

            private static class ArrayValueSubs extends TableCmdWithSubs {
                private static
                    List complexValueSubs =
                               Arrays.asList(new TableAddArrayValueSub(false),
                                             new TableAddMapValueSub(false),
                                             new TableAddRecordValueSub(false),
                                             new TableAddValueSub(false, false),
                                             new TableCancelSub(),
                                             new TableExitSub(),
                                             new TableShowSub());
                ArrayValueSubs() {
                    super(complexValueSubs, "", 0, 2);
                }

                @Override
                public String getCommandOverview() {
                    return COMMAND_DESCRIPTION;
                }
            }

            TableAddArrayValueSub() {
                super(COMMAND, 5);
            }

            TableAddArrayValueSub(boolean fieldIsRequired) {
                super(COMMAND, 5, fieldIsRequired);
            }

            @Override
            TableCmdWithSubs getCmdSubs() {
                return cmdSubs;
            }

            @Override
            FieldValue createValue(FieldDef fieldDef)
                throws ShellException {

                try {
                    return fieldDef.createArray();
                } catch (ClassCastException cce) {
                    throw new ShellException(cce.getMessage());
                }
            }

            @Override
            protected String getCommandDescription() {
                return COMMAND_DESCRIPTION;
            }
        }

        /* sub command: add-record-value */
        static class TableAddRecordValueSub extends TableAddComplexValueSub {
            final static String COMMAND = "add-record-value";
            final static String COMMAND_DESCRIPTION = "Set record field value.";
            private static TableCmdWithSubs cmdSubs = new RecordValueSubs();

            private static class RecordValueSubs extends TableCmdWithSubs {
                private static
                    List complexValueSubs =
                               Arrays.asList(new TableAddArrayValueSub(),
                                             new TableAddMapValueSub(),
                                             new TableAddRecordValueSub(),
                                             new TableAddValueSub(),
                                             new TableCancelSub(),
                                             new TableExitSub(),
                                             new TableShowSub());
                RecordValueSubs() {
                    super(complexValueSubs, "", 0, 2);
                }

                @Override
                public String getCommandOverview() {
                    return COMMAND_DESCRIPTION;
                }
            }

            TableAddRecordValueSub() {
                super(COMMAND, 5);
            }

            TableAddRecordValueSub(boolean fieldIsRequired) {
                super(COMMAND, 5, fieldIsRequired);
            }

            @Override
            TableCmdWithSubs getCmdSubs() {
                return cmdSubs;
            }

            @Override
            FieldValue createValue(FieldDef fieldDef)
                throws ShellException {

                try {
                    return fieldDef.createRecord();
                } catch (ClassCastException cce) {
                    throw new ShellException(cce.getMessage());
                }
            }

            @Override
            protected String getCommandDescription() {
                return COMMAND_DESCRIPTION;
            }
        }

        /* sub command: show */
        static class TableShowSub extends TableCmdSubCommand {
            final static String COMMAND = "show";
            final static String COMMAND_DESCRIPTION = "Show field value.";

            protected TableShowSub() {
                super(COMMAND, 2);
            }

            @Override
            public String execute(String[] args, Shell shell)
                throws ShellException {

                Shell.checkHelp(args, this);
                if (args.length != 1) {
                    shell.badArgCount(this);
                }
                FieldValue value = getCurrentFieldValue();
                return value.toJsonString(true);
            }

            @Override
            protected String getCommandDescription() {
                return COMMAND_DESCRIPTION;
            }
        }

        /* sub command: cancel */
        static class TableCancelSub extends TableCmdSubCommand {
            final static String COMMAND = "cancel";
            final static String COMMAND_DESCRIPTION =
                "Cancel the current operation.";

            protected TableCancelSub() {
                super(COMMAND, 4);
            }

            @Override
            public String execute(String[] args, Shell shell)
                throws ShellException {

                Shell.checkHelp(args, this);
                if (args.length != 1) {
                    shell.badArgCount(this);
                }
                shell.popCurrentCommand();
                return null;
            }

            @Override
            protected String getCommandDescription() {
                return COMMAND_DESCRIPTION;
            }

        }

        /* sub command: exit */
        static class TableExitSub extends TableCmdSubCommand {
            final static String COMMAND = "exit";
            final static String COMMAND_DESCRIPTION =
                "Exit the current operation.";

            protected TableExitSub() {
                super(COMMAND, 4);
            }

            @Override
            public String execute(String[] args, Shell shell)
                throws ShellException {

                Shell.checkHelp(args, this);
                if (args.length != 1) {
                    shell.badArgCount(this);
                }

                String retString = null;
                final FieldValue fieldValue = getCurrentFieldValue();
                if (fieldValue.isRow()) {
                    final CommandShell cmdShell = (CommandShell)shell;
                    final TableAPI tableImpl =
                        cmdShell.getStore().getTableAPI();
                    final WriteOptions wro =
                        new WriteOptions(cmdShell.getStoreDurability(),
                                         cmdShell.getRequestTimeout(),
                                         TimeUnit.MILLISECONDS);

                    retString = doPutRow(tableImpl, fieldValue.asRow(),
                                         wro, isPutIfAbsent());
                    shell.popCurrentCommand();
                } else {
                    String fieldName = getCurrentFieldName();
                    shell.popCurrentCommand();
                    shell.getCurrentCommand()
                        .addVariable(fieldName, fieldValue);
                    shell.runLine("add-value -field \"" + fieldName + "\"");
                }
                return retString;
            }

            @Override
            protected String getCommandDescription() {
                return COMMAND_DESCRIPTION;
            }
        }

        private static String doPutRow(final TableAPI tableImpl,
                                       final Row row,
                                       final WriteOptions options,
                                       final Boolean ifAbsent)
            throws ShellException {

            final StringBuilder sb = new StringBuilder();
            new RunTableAPIOperation() {
                @Override
                void doOperation(){
                    PutResult putResult = executePutOp(tableImpl, row,
                                                       options, ifAbsent);
                    String ret = getReturnMessage(putResult, ifAbsent, row);
                    sb.append(ret);
                }
            }.run();
            if (sb.length() == 0) {
                return null;
            }
            return sb.toString();
        }

        /**
         * An enumeration of put result.
         */
        private enum PutResult {
            INSERTED,   /* Put record successfully using putIfAbsent() */
            UPDATED,    /* Put record successfully using putIfPresent() */
            NONE        /* No row is inserted or updated if -if-absent is specified
                           but a record was already present with the given ken or
                           if -if-present is specified but no existing record was
                           present with the given primary key. */
        }

        /**
         * Executes put operation, returns PutResult object.
         *
         *  @param tableImpl the TableAPI object
         *  @param row the row to be put to store
         *  @param options the WriteOptions used for put operation.
         *  @param ifAbsent a flag that indicates to use putIfAbsent() if true or
         *          use putIfPresent() if false or attempt to use putIfAbsent()
         *          firstly then putIfPresent() if it is null.
         *
         *  @return PutResult object.
         */
        private static PutResult executePutOp(TableAPI tableImpl,
                                              Row row,
                                              WriteOptions options,
                                              Boolean ifAbsent) {

            if (ifAbsent != null) {
                if (ifAbsent) {
                    if (tableImpl.putIfAbsent(row, null, options) != null) {
                        return PutResult.INSERTED;
                    }
                } else {
                    if (tableImpl.putIfPresent(row, null, options) != null) {
                        return PutResult.UPDATED;
                    }
                }
                return PutResult.NONE;
            }
            if (tableImpl.putIfAbsent(row, null, options) != null) {
                return PutResult.INSERTED;
            }
            tableImpl.putIfPresent(row, null, options);
            return PutResult.UPDATED;
        }

        /**
         * Returns the message of put result.
         */
        private static String getReturnMessage(PutResult putResult,
                                               Boolean ifAbsent,
                                               Row row) {

            final String keyString = row.createPrimaryKey().toJsonString(false);
            if (putResult == PutResult.NONE) {
                if (ifAbsent) {
                    return "Operation failed, A record was already present " +
                        "with the given primary key: " + keyString;
                }
                return "Operation failed, No existing record was present with " +
                    "the given primary key: " + keyString;
            }
            return "Operation successful, row " +
                ((putResult == PutResult.UPDATED)? "updated.":"inserted.");
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy