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

com.datastax.driver.core.querybuilder.Insert Maven / Gradle / Ivy

/*
 * Copyright DataStax, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datastax.driver.core.querybuilder;

import static com.google.common.base.Preconditions.checkState;

import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.ColumnMetadata;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.TableMetadata;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** A built {@code INSERT} statement. */
public class Insert extends BuiltStatement {

  private enum JsonDefault {
    NULL,
    UNSET
  }

  private final String table;
  private final List names = new ArrayList();
  private final List values = new ArrayList();
  private final Options usings;
  private boolean ifNotExists;
  private Object json;
  private JsonDefault jsonDefault;

  Insert(String keyspace, String table) {
    this(keyspace, table, null, null);
  }

  Insert(TableMetadata table) {
    this(
        Metadata.quoteIfNecessary(table.getKeyspace().getName()),
        Metadata.quoteIfNecessary(table.getName()),
        Arrays.asList(new Object[table.getPartitionKey().size()]),
        table.getPartitionKey());
  }

  Insert(
      String keyspace,
      String table,
      List routingKeyValues,
      List partitionKey) {
    super(keyspace, partitionKey, routingKeyValues);
    this.table = table;
    this.usings = new Options(this);
  }

  @Override
  StringBuilder buildQueryString(List variables, CodecRegistry codecRegistry) {
    StringBuilder builder = new StringBuilder();

    builder.append("INSERT INTO ");
    if (keyspace != null) Utils.appendName(keyspace, builder).append('.');
    Utils.appendName(table, builder);

    builder.append(" ");
    if (json != null) {
      builder.append("JSON ");
      Utils.appendValue(json, codecRegistry, builder, variables);
      if (jsonDefault == JsonDefault.UNSET) builder.append(" DEFAULT UNSET");
      else if (jsonDefault == JsonDefault.NULL) builder.append(" DEFAULT NULL");
    } else {
      builder.append("(");
      Utils.joinAndAppendNames(builder, codecRegistry, names);
      builder.append(") VALUES (");
      Utils.joinAndAppendValues(builder, codecRegistry, values, variables);
      builder.append(')');
    }

    if (ifNotExists) builder.append(" IF NOT EXISTS");

    if (!usings.usings.isEmpty()) {
      builder.append(" USING ");
      Utils.joinAndAppend(builder, codecRegistry, " AND ", usings.usings, variables);
    }
    return builder;
  }

  /**
   * Adds a column/value pair to the values inserted by this {@code INSERT} statement.
   *
   * @param name the name of the column to insert/update.
   * @param value the value to insert/update for {@code name}.
   * @return this {@code INSERT} statement.
   * @throws IllegalStateException if this method is called and the {@link #json(Object)} method has
   *     been called before, because it's not possible to mix {@code INSERT JSON} syntax with
   *     regular {@code INSERT} syntax.
   */
  public Insert value(String name, Object value) {
    checkState(
        json == null && jsonDefault == null,
        "Cannot mix INSERT JSON syntax with regular INSERT syntax");
    names.add(name);
    values.add(value);
    checkForBindMarkers(value);
    if (!hasNonIdempotentOps() && !Utils.isIdempotent(value)) this.setNonIdempotentOps();
    maybeAddRoutingKey(name, value);
    return this;
  }

  /**
   * Adds multiple column/value pairs to the values inserted by this INSERT statement.
   *
   * @param names a list of column names to insert/update.
   * @param values a list of values to insert/update. The {@code i}th value in {@code values} will
   *     be inserted for the {@code i}th column in {@code names}.
   * @return this INSERT statement.
   * @throws IllegalArgumentException if {@code names.length != values.length}.
   * @throws IllegalStateException if this method is called and the {@link #json(Object)} method has
   *     been called before, because it's not possible to mix {@code INSERT JSON} syntax with
   *     regular {@code INSERT} syntax.
   */
  public Insert values(String[] names, Object[] values) {
    return values(Arrays.asList(names), Arrays.asList(values));
  }

  /**
   * Adds multiple column/value pairs to the values inserted by this INSERT statement.
   *
   * @param names a list of column names to insert/update.
   * @param values a list of values to insert/update. The {@code i}th value in {@code values} will
   *     be inserted for the {@code i}th column in {@code names}.
   * @return this INSERT statement.
   * @throws IllegalArgumentException if {@code names.size() != values.size()}.
   * @throws IllegalStateException if this method is called and the {@link #json(Object)} method has
   *     been called before, because it's not possible to mix {@code INSERT JSON} syntax with
   *     regular {@code INSERT} syntax.
   */
  public Insert values(List names, List values) {
    if (names.size() != values.size())
      throw new IllegalArgumentException(
          String.format("Got %d names but %d values", names.size(), values.size()));
    checkState(
        json == null && jsonDefault == null,
        "Cannot mix INSERT JSON syntax with regular INSERT syntax");
    this.names.addAll(names);
    this.values.addAll(values);
    for (int i = 0; i < names.size(); i++) {
      Object value = values.get(i);
      checkForBindMarkers(value);
      maybeAddRoutingKey(names.get(i), value);
      if (!hasNonIdempotentOps() && !Utils.isIdempotent(value)) this.setNonIdempotentOps();
    }
    return this;
  }

  /**
   * Inserts the provided object, using the {@code INSERT INTO ... JSON} syntax introduced in
   * Cassandra 2.2.
   *
   * 

With INSERT statements, the new {@code JSON} keyword can be used to enable inserting a JSON * structure as a single row. * *

The provided object can be of the following types: * *

    *
  1. A raw string. In this case, it will be appended to the query string as is. It * should NOT be surrounded by single quotes. Its format should generally match * that returned by a {@code SELECT JSON} statement on the same table. Note that it is not * possible to insert function calls nor bind markers in a JSON string. *
  2. A {@link QueryBuilder#bindMarker() bind marker}. In this case, the statement is meant to * be prepared and no JSON string will be appended to the query string, only a bind marker * for the whole JSON parameter. *
  3. Any object that can be serialized to JSON. Such objects can be used provided that a * matching {@link com.datastax.driver.core.TypeCodec codec} is registered with the {@link * CodecRegistry} in use. This allows the usage of JSON libraries, such as the Java API for JSON processing, the popular * Jackson library, or Google's Gson library, for instance. *
* *

Case-sensitive column names

* * When passing raw strings to this method, users are required to handle case-sensitive column * names by surrounding them with double quotes. * *

For example, to insert into a table with two columns named “myKey” and “value”, you would do * the following: * *

   * insertInto("mytable").json("{\"\\\"myKey\\\"\": 0, \"value\": 0}");
   * 
* * This will produce the following CQL: * *
   * INSERT INTO mytable JSON '{"\"myKey\"": 0, "value": 0}';
   * 
* *

Escaping quotes in column values

* * When passing raw strings to this method, double quotes should be escaped with a backslash, but * single quotes should be escaped in the CQL manner, i.e. by another single quote. For example, * the column value {@code foo"'bar} should be inserted in the JSON string as {@code * "foo\"''bar"}. * *

* *

Null values and tombstones

* * Any columns which are omitted from the JSON string will be defaulted to a {@code NULL} value * (which will result in a tombstone being created). * * @param json the JSON string, or a bind marker, or a JSON object handled by a specific {@link * com.datastax.driver.core.TypeCodec codec}. * @return this INSERT statement. * @throws IllegalStateException if this method is called and any of the {@code value} or {@code * values} methods have been called before, because it's not possible to mix {@code INSERT * JSON} syntax with regular {@code INSERT} syntax. * @see JSON Support for CQL * @see JSON * Support in Cassandra 2.2 * @see Inserting * JSON data */ public Insert json(Object json) { checkState( values.isEmpty() && names.isEmpty(), "Cannot mix INSERT JSON syntax with regular INSERT syntax"); this.json = json; return this; } /** * Appends a {@code DEFAULT UNSET} clause to this {@code INSERT INTO ... JSON} statement. * *

Support for {@code DEFAULT UNSET} has been introduced in Cassandra 3.10. * * @return this {@code INSERT} statement. * @throws IllegalStateException if this method is called and any of the {@code value} or {@code * values} methods have been called before, because it's not possible to mix {@code INSERT * JSON} syntax with regular {@code INSERT} syntax. */ public Insert defaultUnset() { checkState( values.isEmpty() && names.isEmpty(), "Cannot mix INSERT JSON syntax with regular INSERT syntax"); this.jsonDefault = JsonDefault.UNSET; return this; } /** * Appends a {@code DEFAULT NULL} clause to this {@code INSERT INTO ... JSON} statement. * *

Support for {@code DEFAULT NULL} has been introduced in Cassandra 3.10. * * @return this {@code INSERT} statement. * @throws IllegalStateException if this method is called and any of the {@code value} or {@code * values} methods have been called before, because it's not possible to mix {@code INSERT * JSON} syntax with regular {@code INSERT} syntax. */ public Insert defaultNull() { checkState( values.isEmpty() && names.isEmpty(), "Cannot mix INSERT JSON syntax with regular INSERT syntax"); this.jsonDefault = JsonDefault.NULL; return this; } /** * Adds a new options for this {@code INSERT} statement. * * @param using the option to add. * @return the options of this {@code INSERT} statement. */ public Options using(Using using) { return usings.and(using); } /** * Returns the options for this {@code INSERT} statement. * *

Chain this with {@link Options#and(Using)} to add options. * * @return the options of this {@code INSERT} statement. */ public Options using() { return usings; } /** * Sets the 'IF NOT EXISTS' option for this {@code INSERT} statement. * *

An insert with that option will not succeed unless the row does not exist at the time the * insertion is executed. The existence check and insertions are done transactionally in the sense * that if multiple clients attempt to create a given row with this option, then at most one may * succeed. * *

Please keep in mind that using this option has a non negligible performance impact and * should be avoided when possible. * *

This will configure the statement as non-idempotent, see {@link * com.datastax.driver.core.Statement#isIdempotent()} for more information. * * @return this {@code INSERT} statement. */ public Insert ifNotExists() { this.setNonIdempotentOps(); this.ifNotExists = true; return this; } /** The options of an {@code INSERT} statement. */ public static class Options extends BuiltStatement.ForwardingStatement { private final List usings = new ArrayList(); Options(Insert st) { super(st); } /** * Adds the provided option. * * @param using an {@code INSERT} option. * @return this {@code Options} object. */ public Options and(Using using) { usings.add(using); checkForBindMarkers(using); return this; } /** * Adds a column/value pair to the values inserted by this {@code INSERT} statement. * * @param name the name of the column to insert/update. * @param value the value to insert/update for {@code name}. * @return the {@code INSERT} statement those options are part of. */ public Insert value(String name, Object value) { return statement.value(name, value); } /** * Adds multiple column/value pairs to the values inserted by this {@code INSERT} statement. * * @param names a list of column names to insert/update. * @param values a list of values to insert/update. The {@code i}th value in {@code values} will * be inserted for the {@code i}th column in {@code names}. * @return the {@code INSERT} statement those options are part of. * @throws IllegalArgumentException if {@code names.length != values.length}. */ public Insert values(String[] names, Object[] values) { return statement.values(names, values); } } }