/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 com.datastax.driver.core.DataType;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Builds CQL3 query via a fluent API.
 *
 * <p>The queries built by this builder will provide a value for the {@link
 * com.datastax.driver.core.Statement#getRoutingKey} method only when a {@link
 * com.datastax.driver.core.TableMetadata} is provided to the builder. It is thus advised to do so
 * if a {@link com.datastax.driver.core.policies.TokenAwarePolicy} is in use.
 *
 * <p>The provider builders perform very little validation of the built query. There is thus no
 * guarantee that a built query is valid, and it is definitively possible to create invalid queries.
 *
 * <p>Note that it could be convenient to use an 'import static' to bring the static methods of this
 * class into scope.
 */
public final class QueryBuilder {

  private QueryBuilder() {}

  /**
   * Starts building a new {@code SELECT} query that selects the provided names.
   *
   * <p>Note that {@code select(c1, c2)} is just a shortcut for {@code
   * select().column(c1).column(c2)}.
   *
   * @param columns the columns names that should be selected by the query.
   * @return an in-construction {@code SELECT} query (you will need to provide at least a {@code
   *     FROM} clause to complete the query).
   */
  public static Select.Builder select(String... columns) {
    return select((Object[]) columns);
  }

  /**
   * Starts building a new {@code SELECT} query that selects the provided names.
   *
   * <p>Note that {@code select(c1, c2)} is just a shortcut for {@code
   * select().column(c1).column(c2)}.
   *
   * @param columns the columns names that should be selected by the query.
   * @return an in-construction {@code SELECT} query (you will need to provide at least a {@code
   *     FROM} clause to complete the query).
   */
  public static Select.Builder select(Object... columns) {
    return new Select.Builder(Arrays.asList(columns));
  }

  /**
   * Starts building a new {@code SELECT} query.
   *
   * @return an in-construction {@code SELECT} query (you will need to provide a column selection
   *     and at least a {@code FROM} clause to complete the query).
   */
  public static Select.Selection select() {
    // Note: the fact we return Select.Selection as return type is on purpose.
    return new Select.SelectionOrAlias();
  }

  /**
   * Starts building a new {@code INSERT} query.
   *
   * @param table the name of the table in which to insert.
   * @return an in-construction {@code INSERT} query.
   */
  public static Insert insertInto(String table) {
    return new Insert(null, table);
  }

  /**
   * Starts building a new {@code INSERT} query.
   *
   * @param keyspace the name of the keyspace to use.
   * @param table the name of the table to insert into.
   * @return an in-construction {@code INSERT} query.
   */
  public static Insert insertInto(String keyspace, String table) {
    return new Insert(keyspace, table);
  }

  /**
   * Starts building a new {@code INSERT} query.
   *
   * @param table the name of the table to insert into.
   * @return an in-construction {@code INSERT} query.
   */
  public static Insert insertInto(TableMetadata table) {
    return new Insert(table);
  }

  /**
   * Starts building a new {@code UPDATE} query.
   *
   * @param table the name of the table to update.
   * @return an in-construction {@code UPDATE} query (at least a {@code SET} and a {@code WHERE}
   *     clause needs to be provided to complete the query).
   */
  public static Update update(String table) {
    return new Update(null, table);
  }

  /**
   * Starts building a new {@code UPDATE} query.
   *
   * @param keyspace the name of the keyspace to use.
   * @param table the name of the table to update.
   * @return an in-construction {@code UPDATE} query (at least a {@code SET} and a {@code WHERE}
   *     clause needs to be provided to complete the query).
   */
  public static Update update(String keyspace, String table) {
    return new Update(keyspace, table);
  }

  /**
   * Starts building a new {@code UPDATE} query.
   *
   * @param table the name of the table to update.
   * @return an in-construction {@code UPDATE} query (at least a {@code SET} and a {@code WHERE}
   *     clause needs to be provided to complete the query).
   */
  public static Update update(TableMetadata table) {
    return new Update(table);
  }

  /**
   * Starts building a new {@code DELETE} query that deletes the provided names.
   *
   * @param columns the columns names that should be deleted by the query.
   * @return an in-construction {@code DELETE} query (At least a {@code FROM} and a {@code WHERE}
   *     clause needs to be provided to complete the query).
   */
  public static Delete.Builder delete(String... columns) {
    return new Delete.Builder(columns);
  }

  /**
   * Starts building a new {@code DELETE} query.
   *
   * @return an in-construction {@code DELETE} query (you will need to provide a column selection
   *     and at least a {@code FROM} and a {@code WHERE} clause to complete the query).
   */
  public static Delete.Selection delete() {
    return new Delete.Selection();
  }

  /**
   * Builds a new {@code BATCH} query on the provided statements.
   *
   * <p>This method will build a logged batch (this is the default in CQL3). To create unlogged
   * batches, use {@link #unloggedBatch}. Also note that for convenience, if the provided statements
   * are counter statements, this method will create a {@code COUNTER} batch even though COUNTER
   * batches are never logged (so for counters, using this method is effectively equivalent to using
   * {@link #unloggedBatch}).
   *
   * @param statements the statements to batch.
   * @return a new {@code RegularStatement} that batch {@code statements}.
   */
  public static Batch batch(RegularStatement... statements) {
    return new Batch(statements, true);
  }

  /**
   * Builds a new {@code UNLOGGED BATCH} query on the provided statements.
   *
   * <p>Compared to logged batches (the default), unlogged batch don't use the distributed batch log
   * server side and as such are not guaranteed to be atomic. In other words, if an unlogged batch
   * timeout, some of the batched statements may have been persisted while some have not. Unlogged
   * batch will however be slightly faster than logged batch.
   *
   * <p>If the statements added to the batch are counter statements, the resulting batch will be a
   * {@code COUNTER} one.
   *
   * @param statements the statements to batch.
   * @return a new {@code RegularStatement} that batch {@code statements} without using the batch
   *     log.
   */
  public static Batch unloggedBatch(RegularStatement... statements) {
    return new Batch(statements, false);
  }

  /**
   * Creates a new {@code TRUNCATE} query.
   *
   * @param table the name of the table to truncate.
   * @return the truncation query.
   */
  public static Truncate truncate(String table) {
    return new Truncate(null, table);
  }

  /**
   * Creates a new {@code TRUNCATE} query.
   *
   * @param keyspace the name of the keyspace to use.
   * @param table the name of the table to truncate.
   * @return the truncation query.
   */
  public static Truncate truncate(String keyspace, String table) {
    return new Truncate(keyspace, table);
  }

  /**
   * Creates a new {@code TRUNCATE} query.
   *
   * @param table the table to truncate.
   * @return the truncation query.
   */
  public static Truncate truncate(TableMetadata table) {
    return new Truncate(table);
  }

  /**
   * Quotes a column name to make it case sensitive.
   *
   * @param columnName the column name to quote.
   * @return the quoted column name.
   * @see Metadata#quote(String)
   */
  public static String quote(String columnName) {
    return Metadata.quote(columnName);
  }

  /**
   * The token of a column name.
   *
   * @param columnName the column name to take the token of.
   * @return {@code "token(" + columnName + ")"}.
   */
  public static String token(String columnName) {
    StringBuilder sb = new StringBuilder();
    sb.append("token(");
    Utils.appendName(columnName, sb);
    sb.append(')');
    return sb.toString();
  }

  /**
   * The token of column names.
   *
   * <p>This variant is most useful when the partition key is composite.
   *
   * @param columnNames the column names to take the token of.
   * @return a string representing the token of the provided column names.
   */
  public static String token(String... columnNames) {
    StringBuilder sb = new StringBuilder();
    sb.append("token(");
    Utils.joinAndAppendNames(sb, null, Arrays.asList(columnNames));
    sb.append(')');
    return sb.toString();
  }

  /**
   * Returns a generic {@code token} function call.
   *
   * @param values the arguments of the {@code token} function.
   * @return {@code token} function call.
   */
  public static Object token(Object... values) {
    return new Utils.FCall("token", values);
  }

  /**
   * Creates an "equal" {@code WHERE} clause stating the provided column must be equal to the
   * provided value.
   *
   * @param name the column name
   * @param value the value
   * @return the corresponding where clause.
   */
  public static Clause eq(String name, Object value) {
    return new Clause.SimpleClause(name, "=", value);
  }

  /**
   * Creates an "equal" {@code WHERE} clause for a group of clustering columns.
   *
   * <p>For instance, {@code eq(Arrays.asList("a", "b"), Arrays.asList(2, "test"))} will generate
   * the CQL {@code WHERE} clause {@code (a, b) = (2, 'test') }.
   *
   * <p>Please note that this variant is only supported starting with Cassandra 2.0.6.
   *
   * @param names the column names
   * @param values the values
   * @return the corresponding where clause.
   * @throws IllegalArgumentException if {@code names.size() != values.size()}.
   */
  public static Clause eq(Iterable<String> names, Iterable<?> values) {
    return new Clause.CompoundClause(names, "=", values);
  }

  /**
   * Creates a "not equal" {@code WHERE} clause stating the provided column must be different from
   * the provided value.
   *
   * @param name the column name
   * @param value the value
   * @return the corresponding where clause.
   */
  public static Clause ne(String name, Object value) {
    return new Clause.SimpleClause(name, "!=", value);
  }

  /**
   * Creates an "not equal" {@code WHERE} clause for a group of clustering columns.
   *
   * <p>For instance, {@code eq(Arrays.asList("a", "b"), Arrays.asList(2, "test"))} will generate
   * the CQL {@code WHERE} clause {@code (a, b) != (2, 'test') }.
   *
   * @param names the column names
   * @param values the values
   * @return the corresponding where clause.
   * @throws IllegalArgumentException if {@code names.size() != values.size()}.
   */
  public static Clause ne(Iterable<String> names, Iterable<?> values) {
    return new Clause.CompoundClause(names, "!=", values);
  }

  /**
   * Creates an "IS NOT NULL" {@code WHERE} clause for the provided column.
   *
   * @param name the column name
   * @return the corresponding where clause.
   */
  public static Clause notNull(String name) {
    return new Clause.IsNotNullClause(name);
  }

  /**
   * Creates a "like" {@code WHERE} clause stating that the provided column must be equal to the
   * provided value.
   *
   * @param name the column name.
   * @param value the value.
   * @return the corresponding where clause.
   */
  public static Clause like(String name, Object value) {
    return new Clause.SimpleClause(name, " LIKE ", value);
  }

  /**
   * Create an "in" {@code WHERE} clause stating the provided column must be equal to one of the
   * provided values.
   *
   * @param name the column name
   * @param values the values
   * @return the corresponding where clause.
   */
  public static Clause in(String name, Object... values) {
    return new Clause.InClause(name, Arrays.asList(values));
  }

  /**
   * Create an "in" {@code WHERE} clause stating the provided column must be equal to one of the
   * provided values.
   *
   * @param name the column name
   * @param values the values
   * @return the corresponding where clause.
   */
  public static Clause in(String name, Iterable<?> values) {
    return new Clause.InClause(name, values);
  }

  /**
   * Creates an "in" {@code WHERE} clause for a group of clustering columns (a.k.a. "multi-column IN
   * restriction").
   *
   * <p>For instance, {@code in(Arrays.asList("a", "b"), Arrays.asList(Arrays.asList(1, "foo"),
   * Arrays.asList(2, "bar")))} will generate the CQL {@code WHERE} clause {@code (a, b) IN ((1,
   * 'foo'), (2, 'bar'))}.
   *
   * <p>Each element in {@code values} must be either an {@link Iterable iterable} containing
   * exactly as many values as there are columns to match in {@code names}, or a {@link
   * #bindMarker() bind marker} – in which case, that marker is to be considered as a placeholder
   * for one whole tuple of values to match.
   *
   * <p>Please note that this variant is only supported starting with Cassandra 2.0.9.
   *
   * @param names the column names
   * @param values the values
   * @return the corresponding where clause.
   * @throws IllegalArgumentException if the size of any tuple in {@code values} is not equal to
   *     {@code names.size()}, or if {@code values} contains elements that are neither {@link List
   *     lists} nor {@link #bindMarker() bind markers}.
   */
  public static Clause in(Iterable<String> names, Iterable<?> values) {
    return new Clause.CompoundInClause(names, values);
  }

  /**
   * Creates a "contains" {@code WHERE} clause stating the provided column must contain the value
   * provided.
   *
   * @param name the column name
   * @param value the value
   * @return the corresponding where clause.
   */
  public static Clause contains(String name, Object value) {
    return new Clause.ContainsClause(name, value);
  }

  /**
   * Creates a "contains key" {@code WHERE} clause stating the provided column must contain the key
   * provided.
   *
   * @param name the column name
   * @param key the key
   * @return the corresponding where clause.
   */
  public static Clause containsKey(String name, Object key) {
    return new Clause.ContainsKeyClause(name, key);
  }

  /**
   * Creates a "lesser than" {@code WHERE} clause stating the provided column must be less than the
   * provided value.
   *
   * @param name the column name
   * @param value the value
   * @return the corresponding where clause.
   */
  public static Clause lt(String name, Object value) {
    return new Clause.SimpleClause(name, "<", value);
  }

  /**
   * Creates a "lesser than" {@code WHERE} clause for a group of clustering columns.
   *
   * <p>For instance, {@code lt(Arrays.asList("a", "b"), Arrays.asList(2, "test"))} will generate
   * the CQL {@code WHERE} clause {@code (a, b) &lt; (2, 'test') }.
   *
   * <p>Please note that this variant is only supported starting with Cassandra 2.0.6.
   *
   * @param names the column names
   * @param values the values
   * @return the corresponding where clause.
   * @throws IllegalArgumentException if {@code names.size() != values.size()}.
   */
  public static Clause lt(Iterable<String> names, Iterable<?> values) {
    return new Clause.CompoundClause(names, "<", values);
  }

  /**
   * Creates a "lesser than or equal" {@code WHERE} clause stating the provided column must be
   * lesser than or equal to the provided value.
   *
   * @param name the column name
   * @param value the value
   * @return the corresponding where clause.
   */
  public static Clause lte(String name, Object value) {
    return new Clause.SimpleClause(name, "<=", value);
  }

  /**
   * Creates a "lesser than or equal" {@code WHERE} clause for a group of clustering columns.
   *
   * <p>For instance, {@code lte(Arrays.asList("a", "b"), Arrays.asList(2, "test"))} will generate
   * the CQL {@code WHERE} clause {@code (a, b) &lte; (2, 'test') }.
   *
   * <p>Please note that this variant is only supported starting with Cassandra 2.0.6.
   *
   * @param names the column names
   * @param values the values
   * @return the corresponding where clause.
   * @throws IllegalArgumentException if {@code names.size() != values.size()}.
   */
  public static Clause lte(Iterable<String> names, Iterable<?> values) {
    return new Clause.CompoundClause(names, "<=", values);
  }

  /**
   * Creates a "greater than" {@code WHERE} clause stating the provided column must be greater to
   * the provided value.
   *
   * @param name the column name
   * @param value the value
   * @return the corresponding where clause.
   */
  public static Clause gt(String name, Object value) {
    return new Clause.SimpleClause(name, ">", value);
  }

  /**
   * Creates a "greater than" {@code WHERE} clause for a group of clustering columns.
   *
   * <p>For instance, {@code gt(Arrays.asList("a", "b"), Arrays.asList(2, "test"))} will generate
   * the CQL {@code WHERE} clause {@code (a, b) &gt; (2, 'test') }.
   *
   * <p>Please note that this variant is only supported starting with Cassandra 2.0.6.
   *
   * @param names the column names
   * @param values the values
   * @return the corresponding where clause.
   * @throws IllegalArgumentException if {@code names.size() != values.size()}.
   */
  public static Clause gt(Iterable<String> names, Iterable<?> values) {
    return new Clause.CompoundClause(names, ">", values);
  }

  /**
   * Creates a "greater than or equal" {@code WHERE} clause stating the provided column must be
   * greater than or equal to the provided value.
   *
   * @param name the column name
   * @param value the value
   * @return the corresponding where clause.
   */
  public static Clause gte(String name, Object value) {
    return new Clause.SimpleClause(name, ">=", value);
  }

  /**
   * Creates a "greater than or equal" {@code WHERE} clause for a group of clustering columns.
   *
   * <p>For instance, {@code gte(Arrays.asList("a", "b"), Arrays.asList(2, "test"))} will generate
   * the CQL {@code WHERE} clause {@code (a, b) &gte; (2, 'test') }.
   *
   * <p>Please note that this variant is only supported starting with Cassandra 2.0.6.
   *
   * @param names the column names
   * @param values the values
   * @return the corresponding where clause.
   * @throws IllegalArgumentException if {@code names.size() != values.size()}.
   */
  public static Clause gte(Iterable<String> names, Iterable<?> values) {
    return new Clause.CompoundClause(names, ">=", values);
  }

  /**
   * Ascending ordering for the provided column.
   *
   * @param columnName the column name
   * @return the corresponding ordering
   */
  public static Ordering asc(String columnName) {
    return new Ordering(columnName, false);
  }

  /**
   * Descending ordering for the provided column.
   *
   * @param columnName the column name
   * @return the corresponding ordering
   */
  public static Ordering desc(String columnName) {
    return new Ordering(columnName, true);
  }

  /**
   * Option to set the timestamp for a modification query (insert, update or delete).
   *
   * @param timestamp the timestamp (in microseconds) to use.
   * @return the corresponding option
   * @throws IllegalArgumentException if {@code timestamp &lt; 0}.
   */
  public static Using timestamp(long timestamp) {
    if (timestamp < 0) throw new IllegalArgumentException("Invalid timestamp, must be positive");

    return new Using.WithValue("TIMESTAMP", timestamp);
  }

  /**
   * Option to prepare the timestamp (in microseconds) for a modification query (insert, update or
   * delete).
   *
   * @param marker bind marker to use for the timestamp.
   * @return the corresponding option.
   */
  public static Using timestamp(BindMarker marker) {
    return new Using.WithMarker("TIMESTAMP", marker);
  }

  /**
   * Option to set the ttl for a modification query (insert, update or delete).
   *
   * @param ttl the ttl (in seconds) to use.
   * @return the corresponding option
   * @throws IllegalArgumentException if {@code ttl &lt; 0}.
   */
  public static Using ttl(int ttl) {
    if (ttl < 0) throw new IllegalArgumentException("Invalid ttl, must be positive");

    return new Using.WithValue("TTL", ttl);
  }

  /**
   * Option to prepare the ttl (in seconds) for a modification query (insert, update or delete).
   *
   * @param marker bind marker to use for the ttl.
   * @return the corresponding option
   */
  public static Using ttl(BindMarker marker) {
    return new Using.WithMarker("TTL", marker);
  }

  /**
   * Simple "set" assignment of a value to a column.
   *
   * <p>This will generate:
   *
   * <pre>
   * name = value
   * </pre>
   *
   * The column name will only be quoted if it contains special characters, as in:
   *
   * <pre>
   * "a name that contains spaces" = value
   * </pre>
   *
   * Otherwise, if you want to force case sensitivity, use {@link #quote(String)}:
   *
   * <pre>
   * set(quote("aCaseSensitiveName"), value)
   * </pre>
   *
   * This method won't work to set UDT fields; use {@link #set(Object, Object)} with a {@link
   * #path(String...) path} instead:
   *
   * <pre>
   * set(path("udt", "field"), value)
   * </pre>
   *
   * @param name the column name
   * @param value the value to assign
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment set(String name, Object value) {
    return new Assignment.SetAssignment(name, value);
  }

  /**
   * Advanced "set" assignment of a value to a column or a {@link com.datastax.driver.core.UserType
   * UDT} field.
   *
   * <p>This method is seldom preferable to {@link #set(String, Object)}; it is only useful:
   *
   * <ul>
   *   <li>when assigning values to individual fields of a UDT (see {@link #path(String...)}):
   *       <pre>
   * set(path("udt", "field"), value)
   * </pre>
   *   <li>if you wish to pass a "raw" string that will get appended as-is to the query (see {@link
   *       #raw(String)}). There is no practical usage for this the time of writing, but it will
   *       serve as a workaround if new features are added to Cassandra and you're using a older
   *       driver version that is not yet aware of them:
   *       <pre>
   * set(raw("some custom string"), value)
   * </pre>
   * </ul>
   *
   * If the runtime type of {@code name} is {@code String}, this method is equivalent to {@link
   * #set(String, Object)}.
   *
   * @param name the column or UDT field name
   * @param value the value to assign
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment set(Object name, Object value) {
    return new Assignment.SetAssignment(name, value);
  }

  /**
   * Incrementation of a counter column.
   *
   * <p>This will generate: {@code name = name + 1}.
   *
   * @param name the column name to increment
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment incr(String name) {
    return incr(name, 1L);
  }

  /**
   * Incrementation of a counter column by a provided value.
   *
   * <p>This will generate: {@code name = name + value}.
   *
   * @param name the column name to increment
   * @param value the value by which to increment
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment incr(String name, long value) {
    return new Assignment.CounterAssignment(name, value, true);
  }

  /**
   * Incrementation of a counter column by a provided value.
   *
   * <p>This will generate: {@code name = name + value}.
   *
   * @param name the column name to increment
   * @param value a bind marker representing the value by which to increment
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment incr(String name, BindMarker value) {
    return new Assignment.CounterAssignment(name, value, true);
  }

  /**
   * Decrementation of a counter column.
   *
   * <p>This will generate: {@code name = name - 1}.
   *
   * @param name the column name to decrement
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment decr(String name) {
    return decr(name, 1L);
  }

  /**
   * Decrementation of a counter column by a provided value.
   *
   * <p>This will generate: {@code name = name - value}.
   *
   * @param name the column name to decrement
   * @param value the value by which to decrement
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment decr(String name, long value) {
    return new Assignment.CounterAssignment(name, value, false);
  }

  /**
   * Decrementation of a counter column by a provided value.
   *
   * <p>This will generate: {@code name = name - value}.
   *
   * @param name the column name to decrement
   * @param value a bind marker representing the value by which to decrement
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment decr(String name, BindMarker value) {
    return new Assignment.CounterAssignment(name, value, false);
  }

  /**
   * Prepend a value to a list column.
   *
   * <p>This will generate: {@code name = [ value ] + name}.
   *
   * @param name the column name (must be of type list).
   * @param value the value to prepend. Using a BindMarker here is not supported. To use a
   *     BindMarker use {@code QueryBuilder#prependAll} with a singleton list.
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment prepend(String name, Object value) {
    if (value instanceof BindMarker) {
      throw new InvalidQueryException(
          "binding a value in prepend() is not supported, use prependAll() and bind a singleton list");
    }
    return prependAll(name, Collections.singletonList(value));
  }

  /**
   * Prepend a list of values to a list column.
   *
   * <p>This will generate: {@code name = list + name}.
   *
   * @param name the column name (must be of type list).
   * @param list the list of values to prepend.
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment prependAll(String name, List<?> list) {
    return new Assignment.ListPrependAssignment(name, list);
  }

  /**
   * Prepend a list of values to a list column.
   *
   * <p>This will generate: {@code name = list + name}.
   *
   * @param name the column name (must be of type list).
   * @param list a bind marker representing the list of values to prepend.
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment prependAll(String name, BindMarker list) {
    return new Assignment.ListPrependAssignment(name, list);
  }

  /**
   * Append a value to a list column.
   *
   * <p>This will generate: {@code name = name + [value]}.
   *
   * @param name the column name (must be of type list).
   * @param value the value to append. Using a BindMarker here is not supported. To use a BindMarker
   *     use {@code QueryBuilder#appendAll} with a singleton list.
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment append(String name, Object value) {
    if (value instanceof BindMarker) {
      throw new InvalidQueryException(
          "Binding a value in append() is not supported, use appendAll() and bind a singleton list");
    }
    return appendAll(name, Collections.singletonList(value));
  }

  /**
   * Append a list of values to a list column.
   *
   * <p>This will generate: {@code name = name + list}.
   *
   * @param name the column name (must be of type list).
   * @param list the list of values to append
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment appendAll(String name, List<?> list) {
    return new Assignment.CollectionAssignment(name, list, true, false);
  }

  /**
   * Append a list of values to a list column.
   *
   * <p>This will generate: {@code name = name + list}.
   *
   * @param name the column name (must be of type list).
   * @param list a bind marker representing the list of values to append
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment appendAll(String name, BindMarker list) {
    return new Assignment.CollectionAssignment(name, list, true, false);
  }

  /**
   * Discard a value from a list column.
   *
   * <p>This will generate: {@code name = name - [value]}.
   *
   * @param name the column name (must be of type list).
   * @param value the value to discard. Using a BindMarker here is not supported. To use a
   *     BindMarker use {@code QueryBuilder#discardAll} with a singleton list.
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment discard(String name, Object value) {
    if (value instanceof BindMarker) {
      throw new InvalidQueryException(
          "Binding a value in discard() is not supported, use discardAll() and bind a singleton list");
    }
    return discardAll(name, Collections.singletonList(value));
  }

  /**
   * Discard a list of values to a list column.
   *
   * <p>This will generate: {@code name = name - list}.
   *
   * @param name the column name (must be of type list).
   * @param list the list of values to discard
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment discardAll(String name, List<?> list) {
    return new Assignment.CollectionAssignment(name, list, false);
  }

  /**
   * Discard a list of values to a list column.
   *
   * <p>This will generate: {@code name = name - list}.
   *
   * @param name the column name (must be of type list).
   * @param list a bind marker representing the list of values to discard
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment discardAll(String name, BindMarker list) {
    return new Assignment.CollectionAssignment(name, list, false);
  }

  /**
   * Sets a list column value by index.
   *
   * <p>This will generate: {@code name[idx] = value}.
   *
   * @param name the column name (must be of type list).
   * @param idx the index to set
   * @param value the value to set
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment setIdx(String name, int idx, Object value) {
    return new Assignment.ListSetIdxAssignment(name, idx, value);
  }

  /**
   * Adds a value to a set column.
   *
   * <p>This will generate: {@code name = name + {value}}.
   *
   * @param name the column name (must be of type set).
   * @param value the value to add. Using a BindMarker here is not supported. To use a BindMarker
   *     use {@code QueryBuilder#addAll} with a singleton set.
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment add(String name, Object value) {
    if (value instanceof BindMarker) {
      throw new InvalidQueryException(
          "Binding a value in add() is not supported, use addAll() and bind a singleton list");
    }
    return addAll(name, Collections.singleton(value));
  }

  /**
   * Adds a set of values to a set column.
   *
   * <p>This will generate: {@code name = name + set}.
   *
   * @param name the column name (must be of type set).
   * @param set the set of values to append
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment addAll(String name, Set<?> set) {
    return new Assignment.CollectionAssignment(name, set, true);
  }

  /**
   * Adds a set of values to a set column.
   *
   * <p>This will generate: {@code name = name + set}.
   *
   * @param name the column name (must be of type set).
   * @param set a bind marker representing the set of values to append
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment addAll(String name, BindMarker set) {
    return new Assignment.CollectionAssignment(name, set, true);
  }

  /**
   * Remove a value from a set column.
   *
   * <p>This will generate: {@code name = name - {value}}.
   *
   * @param name the column name (must be of type set).
   * @param value the value to remove. Using a BindMarker here is not supported. To use a BindMarker
   *     use {@code QueryBuilder#removeAll} with a singleton set.
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment remove(String name, Object value) {
    if (value instanceof BindMarker) {
      throw new InvalidQueryException(
          "Binding a value in remove() is not supported, use removeAll() and bind a singleton set");
    }
    return removeAll(name, Collections.singleton(value));
  }

  /**
   * Remove a set of values from a set column.
   *
   * <p>This will generate: {@code name = name - set}.
   *
   * @param name the column name (must be of type set).
   * @param set the set of values to remove
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment removeAll(String name, Set<?> set) {
    return new Assignment.CollectionAssignment(name, set, false);
  }

  /**
   * Remove a set of values from a set column.
   *
   * <p>This will generate: {@code name = name - set}.
   *
   * @param name the column name (must be of type set).
   * @param set a bind marker representing the set of values to remove
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment removeAll(String name, BindMarker set) {
    return new Assignment.CollectionAssignment(name, set, false);
  }

  /**
   * Puts a new key/value pair to a map column.
   *
   * <p>This will generate: {@code name[key] = value}.
   *
   * @param name the column name (must be of type map).
   * @param key the key to put
   * @param value the value to put
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment put(String name, Object key, Object value) {
    return new Assignment.MapPutAssignment(name, key, value);
  }

  /**
   * Puts a map of new key/value pairs to a map column.
   *
   * <p>This will generate: {@code name = name + map}.
   *
   * @param name the column name (must be of type map).
   * @param map the map of key/value pairs to put
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment putAll(String name, Map<?, ?> map) {
    return new Assignment.CollectionAssignment(name, map, true);
  }

  /**
   * Puts a map of new key/value pairs to a map column.
   *
   * <p>This will generate: {@code name = name + map}.
   *
   * @param name the column name (must be of type map).
   * @param map a bind marker representing the map of key/value pairs to put
   * @return the correspond assignment (to use in an update query)
   */
  public static Assignment putAll(String name, BindMarker map) {
    return new Assignment.CollectionAssignment(name, map, true);
  }

  /**
   * An object representing an anonymous bind marker (a question mark).
   *
   * <p>This can be used wherever a value is expected. For instance, one can do:
   *
   * <pre>{@code
   * Insert i = QueryBuilder.insertInto("test").value("k", 0)
   *                                           .value("c", QueryBuilder.bindMarker());
   * PreparedStatement p = session.prepare(i.toString());
   * }</pre>
   *
   * @return a new bind marker.
   */
  public static BindMarker bindMarker() {
    return BindMarker.ANONYMOUS;
  }

  /**
   * An object representing a named bind marker.
   *
   * <p>This can be used wherever a value is expected. For instance, one can do:
   *
   * <pre>{@code
   * Insert i = QueryBuilder.insertInto("test").value("k", 0)
   *                                           .value("c", QueryBuilder.bindMarker("c_val"));
   * PreparedStatement p = session.prepare(i.toString());
   * }</pre>
   *
   * <p>Please note that named bind makers are only supported starting with Cassandra 2.0.1.
   *
   * @param name the name for the bind marker.
   * @return an object representing a bind marker named {@code name}.
   */
  public static BindMarker bindMarker(String name) {
    return new BindMarker(name);
  }

  /**
   * Protects a value from any interpretation by the query builder.
   *
   * <p>The following table exemplify the behavior of this function:
   *
   * <table border=1>
   * <caption>Examples of use</caption>
   * <tr><th>Code</th><th>Resulting query string</th></tr>
   * <tr><td>{@code select().from("t").where(eq("c", "C'est la vie!")); }</td><td>{@code "SELECT * FROM t WHERE c='C''est la vie!';"}</td></tr>
   * <tr><td>{@code select().from("t").where(eq("c", raw("C'est la vie!"))); }</td><td>{@code "SELECT * FROM t WHERE c=C'est la vie!;"}</td></tr>
   * <tr><td>{@code select().from("t").where(eq("c", raw("'C'est la vie!'"))); }</td><td>{@code "SELECT * FROM t WHERE c='C'est la vie!';"}</td></tr>
   * <tr><td>{@code select().from("t").where(eq("c", "now()")); }</td><td>{@code "SELECT * FROM t WHERE c='now()';"}</td></tr>
   * <tr><td>{@code select().from("t").where(eq("c", raw("now()"))); }</td><td>{@code "SELECT * FROM t WHERE c=now();"}</td></tr>
   * </table>
   *
   * <i>Note: the 2nd and 3rd examples in this table are not a valid CQL3 queries.</i>
   *
   * <p>The use of that method is generally discouraged since it lead to security risks. However, if
   * you know what you are doing, it allows to escape the interpretations done by the QueryBuilder.
   *
   * @param str the raw value to use as a string
   * @return the value but protected from being interpreted/escaped by the query builder.
   */
  public static Object raw(String str) {
    return new Utils.RawString(str);
  }

  /**
   * Creates a function call.
   *
   * @param name the name of the function to call.
   * @param parameters the parameters for the function.
   * @return the function call.
   */
  public static Object fcall(String name, Object... parameters) {
    return new Utils.FCall(name, parameters);
  }

  /**
   * Creates a Cast of a column using the given dataType.
   *
   * @param column the column to cast.
   * @param dataType the data type to cast to.
   * @return the casted column.
   */
  public static Object cast(Object column, DataType dataType) {
    return new Utils.Cast(column, dataType);
  }

  /**
   * Creates a {@code now()} function call.
   *
   * @return the function call.
   */
  public static Object now() {
    return new Utils.FCall("now");
  }

  /**
   * Creates a {@code uuid()} function call.
   *
   * @return the function call.
   */
  public static Object uuid() {
    return new Utils.FCall("uuid");
  }

  /**
   * Declares that the name in argument should be treated as a column name.
   *
   * <p>This mainly meant for use with {@link Select.Selection#fcall} when a function should apply
   * to a column name, not a string value.
   *
   * @param name the name of the column.
   * @return the name as a column name.
   */
  public static Object column(String name) {
    return new Utils.CName(name);
  }

  /**
   * Creates a path composed of the given path {@code segments}.
   *
   * <p>All provided path segments will be concatenated together with dots. If any segment contains
   * an identifier that needs quoting, caller code is expected to call {@link #quote(String)} prior
   * to invoking this method.
   *
   * <p>This method is currently only useful when accessing individual fields of a {@link
   * com.datastax.driver.core.UserType user-defined type} (UDT), which is only possible since
   * CASSANDRA-7423.
   *
   * <p>Note that currently nested UDT fields are not supported and will be rejected by the server
   * as a {@link com.datastax.driver.core.exceptions.SyntaxError syntax error}.
   *
   * @param segments the segments of the path to create.
   * @return the segments concatenated as a single path.
   * @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-7423">CASSANDRA-7423</a>
   */
  public static Object path(String... segments) {
    return new Utils.Path(segments);
  }

  /**
   * Creates a {@code fromJson()} function call.
   *
   * <p>Support for JSON functions has been added in Cassandra 2.2. The {@code fromJson()} function
   * is similar to {@code INSERT JSON} statements, but applies to a single column value instead of
   * the entire row, and converts a JSON object into the normal Cassandra column value.
   *
   * <p>It may be used in {@code INSERT} and {@code UPDATE} statements, but NOT in the selection
   * clause of a {@code SELECT} statement.
   *
   * <p>The provided object can be of the following types:
   *
   * <ol>
   *   <li>A raw string. In this case, it will be appended to the query string as is. <strong>It
   *       should NOT be surrounded by single quotes.</strong> 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.
   *   <li>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.
   *   <li>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
   *       com.datastax.driver.core.CodecRegistry CodecRegistry} in use. This allows the usage of
   *       JSON libraries, such as the <a href="https://jcp.org/en/jsr/detail?id=353">Java API for
   *       JSON processing</a>, the popular <a
   *       href="http://wiki.fasterxml.com/JacksonHome">Jackson</a> library, or Google's <a
   *       href="https://github.com/google/gson">Gson</a> library, for instance.
   * </ol>
   *
   * <p>When passing raw strings to this method, the following rules apply:
   *
   * <ol>
   *   <li>String values should be enclosed in double quotes.
   *   <li>Double quotes appearing inside strings 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"}.
   * </ol>
   *
   * @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 the function call.
   * @see <a href="http://cassandra.apache.org/doc/cql3/CQL-2.2.html#json">JSON Support for CQL</a>
   * @see <a href="http://www.datastax.com/dev/blog/whats-new-in-cassandra-2-2-json-support">JSON
   *     Support in Cassandra 2.2</a>
   */
  public static Object fromJson(Object json) {
    return fcall("fromJson", json);
  }

  /**
   * Creates a {@code toJson()} function call. This is a shortcut for {@code fcall("toJson",
   * QueryBuilder.column(name))}.
   *
   * <p>Support for JSON functions has been added in Cassandra 2.2. The {@code toJson()} function is
   * similar to {@code SELECT JSON} statements, but applies to a single column value instead of the
   * entire row, and produces a JSON-encoded string representing the normal Cassandra column value.
   *
   * <p>It may only be used in the selection clause of a {@code SELECT} statement.
   *
   * @param column the column to retrieve JSON from.
   * @return the function call.
   * @see <a href="http://cassandra.apache.org/doc/cql3/CQL-2.2.html#json">JSON Support for CQL</a>
   * @see <a href="http://www.datastax.com/dev/blog/whats-new-in-cassandra-2-2-json-support">JSON
   *     Support in Cassandra 2.2</a>
   */
  public static Object toJson(Object column) {
    // consider a String literal as a column name for user convenience,
    // as CQL literals are not allowed here.
    if (column instanceof String) column = column(((String) column));
    return new Utils.FCall("toJson", column);
  }

  /**
   * Creates an alias for a given column.
   *
   * <p>This is most useful when used with the method {@link #select(Object...)}.
   *
   * @param column The column to create an alias for.
   * @param alias The column alias.
   * @return a column alias.
   */
  public static Object alias(Object column, String alias) {
    return new Utils.Alias(column, alias);
  }

  /**
   * Creates a {@code count(x)} built-in function call.
   *
   * @return the function call.
   */
  public static Object count(Object column) {
    // consider a String literal as a column name for user convenience,
    // as CQL literals are not allowed here.
    if (column instanceof String) column = column(((String) column));
    return new Utils.FCall("count", column);
  }

  /**
   * Creates a {@code max(x)} built-in function call.
   *
   * @return the function call.
   */
  public static Object max(Object column) {
    // consider a String literal as a column name for user convenience,
    // as CQL literals are not allowed here.
    if (column instanceof String) column = column(((String) column));
    return new Utils.FCall("max", column);
  }

  /**
   * Creates a {@code min(x)} built-in function call.
   *
   * @return the function call.
   */
  public static Object min(Object column) {
    // consider a String literal as a column name for user convenience,
    // as CQL literals are not allowed here.
    if (column instanceof String) column = column(((String) column));
    return new Utils.FCall("min", column);
  }

  /**
   * Creates a {@code sum(x)} built-in function call.
   *
   * @return the function call.
   */
  public static Object sum(Object column) {
    // consider a String literal as a column name for user convenience,
    // as CQL literals are not allowed here.
    if (column instanceof String) column = column(((String) column));
    return new Utils.FCall("sum", column);
  }

  /**
   * Creates an {@code avg(x)} built-in function call.
   *
   * @return the function call.
   */
  public static Object avg(Object column) {
    // consider a String literal as a column name for user convenience,
    // as CQL literals are not allowed here.
    if (column instanceof String) column = column(((String) column));
    return new Utils.FCall("avg", column);
  }
}
