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

com.datastax.driver.core.FunctionMetadata Maven / Gradle / Ivy

Go to download

A driver for DataStax Enterprise (DSE) and Apache Cassandra 1.2+ clusters that works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's binary protocol, supporting DSE-specific features such as geospatial types, DSE Graph and DSE authentication.

There is a newer version: 2.4.0
Show newest version
/*
 * Copyright DataStax, Inc.
 *
 * This software can be used solely with DataStax Enterprise. Please consult the license at
 * http://www.datastax.com/terms/datastax-dse-driver-license-terms
 */
package com.datastax.driver.core;

import com.datastax.driver.core.utils.MoreObjects;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Describes a CQL function (created with {@code CREATE FUNCTION...}). */
public class FunctionMetadata {
  private static final Logger logger = LoggerFactory.getLogger(FunctionMetadata.class);

  private final KeyspaceMetadata keyspace;
  private final String simpleName;
  private final Map arguments;
  private final String body;
  private final boolean calledOnNullInput;
  private final String language;
  private final DataType returnType;
  private final boolean deterministic;
  private final boolean monotonic;
  private final List monotonicOn;

  private FunctionMetadata(
      KeyspaceMetadata keyspace,
      String simpleName,
      Map arguments,
      String body,
      boolean calledOnNullInput,
      String language,
      DataType returnType,
      boolean deterministic,
      boolean monotonic,
      List monotonicOn) {
    this.keyspace = keyspace;
    this.simpleName = simpleName;
    this.arguments = arguments;
    this.body = body;
    this.calledOnNullInput = calledOnNullInput;
    this.language = language;
    this.returnType = returnType;
    this.deterministic = deterministic;
    this.monotonic = monotonic;
    this.monotonicOn = monotonicOn;
  }

  // Cassandra < 3.0:
  // CREATE TABLE system.schema_functions (
  //     keyspace_name text,
  //     function_name text,
  //     signature frozen>,
  //     argument_names list,
  //     argument_types list,
  //     body text,
  //     called_on_null_input boolean,
  //     language text,
  //     return_type text,
  //     PRIMARY KEY (keyspace_name, function_name, signature)
  // ) WITH CLUSTERING ORDER BY (function_name ASC, signature ASC)
  //
  // Cassandra >= 3.0:
  // CREATE TABLE system_schema.functions (
  //     keyspace_name text,
  //     function_name text,
  //     argument_names frozen>,
  //     argument_types frozen>,
  //     body text,
  //     called_on_null_input boolean,
  //     language text,
  //     return_type text,
  //     PRIMARY KEY (keyspace_name, function_name, argument_types)
  // ) WITH CLUSTERING ORDER BY (function_name ASC, argument_types ASC)
  //

  static FunctionMetadata build(
      KeyspaceMetadata ksm, Row row, VersionNumber version, Cluster cluster) {
    CodecRegistry codecRegistry = cluster.getConfiguration().getCodecRegistry();
    ProtocolVersion protocolVersion =
        cluster.getConfiguration().getProtocolOptions().getProtocolVersion();
    String simpleName = row.getString("function_name");
    List argumentNames = row.getList("argument_names", String.class);
    // this will be a list of C* types in 2.2 and a list of CQL types in 3.0
    List argumentTypes = row.getList("argument_types", String.class);
    Map arguments =
        buildArguments(ksm, argumentNames, argumentTypes, version, cluster);
    if (argumentNames.size() != argumentTypes.size()) {
      String fullName = Metadata.fullFunctionName(simpleName, arguments.values());
      logger.error(
          String.format(
              "Error parsing definition of function %1$s.%2$s: the number of argument names and types don't match."
                  + "Cluster.getMetadata().getKeyspace(\"%1$s\").getFunction(\"%2$s\") will be missing.",
              ksm.getName(), fullName));
      return null;
    }
    String body = row.getString("body");
    boolean calledOnNullInput = row.getBool("called_on_null_input");
    String language = row.getString("language");
    DataType returnType;
    if (version.getMajor() >= 3.0) {
      returnType =
          DataTypeCqlNameParser.parse(
              row.getString("return_type"),
              cluster,
              ksm.getName(),
              ksm.userTypes,
              null,
              false,
              false);
    } else {
      returnType =
          DataTypeClassNameParser.parseOne(
              row.getString("return_type"), protocolVersion, codecRegistry);
    }
    boolean deterministic = false;
    if (row.getColumnDefinitions().contains("deterministic")) {
      deterministic = row.getBool("deterministic");
    }
    boolean monotonic = false;
    if (row.getColumnDefinitions().contains("monotonic")) {
      monotonic = row.getBool("monotonic");
    }
    List monotonicOn = Collections.emptyList();
    if (row.getColumnDefinitions().contains("monotonic_on")) {
      monotonicOn = row.getList("monotonic_on", String.class);
    }
    return new FunctionMetadata(
        ksm,
        simpleName,
        arguments,
        body,
        calledOnNullInput,
        language,
        returnType,
        deterministic,
        monotonic,
        monotonicOn);
  }

  // Note: the caller ensures that names and types have the same size
  private static Map buildArguments(
      KeyspaceMetadata ksm,
      List names,
      List types,
      VersionNumber version,
      Cluster cluster) {
    if (names.isEmpty()) return Collections.emptyMap();
    ImmutableMap.Builder builder = ImmutableMap.builder();
    CodecRegistry codecRegistry = cluster.getConfiguration().getCodecRegistry();
    ProtocolVersion protocolVersion =
        cluster.getConfiguration().getProtocolOptions().getProtocolVersion();
    Iterator iterTypes = types.iterator();
    for (String name : names) {
      DataType type;
      if (version.getMajor() >= 3) {
        type =
            DataTypeCqlNameParser.parse(
                iterTypes.next(), cluster, ksm.getName(), ksm.userTypes, null, false, false);
      } else {
        type = DataTypeClassNameParser.parseOne(iterTypes.next(), protocolVersion, codecRegistry);
      }
      builder.put(name, type);
    }
    return builder.build();
  }

  /**
   * Returns a CQL query representing this function in human readable form.
   *
   * 

This method is equivalent to {@link #asCQLQuery} but the output is formatted. * * @return the CQL query representing this function. */ public String exportAsString() { return asCQLQuery(true); } /** * Returns a CQL query representing this function. * *

This method returns a single 'CREATE FUNCTION' query corresponding to this function * definition. * * @return the 'CREATE FUNCTION' query corresponding to this function. */ public String asCQLQuery() { return asCQLQuery(false); } @Override public String toString() { return asCQLQuery(false); } private String asCQLQuery(boolean formatted) { StringBuilder sb = new StringBuilder("CREATE FUNCTION "); sb.append(Metadata.quoteIfNecessary(keyspace.getName())) .append('.') .append(Metadata.quoteIfNecessary(simpleName)) .append('('); boolean first = true; for (Map.Entry entry : arguments.entrySet()) { if (first) first = false; else sb.append(','); String name = entry.getKey(); DataType type = entry.getValue(); sb.append(Metadata.quoteIfNecessary(name)) .append(' ') .append(type.asFunctionParameterString()); } sb.append(')'); TableMetadata.spaceOrNewLine(sb, formatted) .append(calledOnNullInput ? "CALLED ON NULL INPUT" : "RETURNS NULL ON NULL INPUT"); TableMetadata.spaceOrNewLine(sb, formatted) .append("RETURNS ") .append(returnType.asFunctionParameterString()); if (deterministic) { TableMetadata.spaceOrNewLine(sb, formatted).append("DETERMINISTIC"); } if (monotonic) { TableMetadata.spaceOrNewLine(sb, formatted).append("MONOTONIC"); } else if (!monotonicOn.isEmpty()) { // 'monotonic_on' column includes all arguments if 'monotonic' is true, therefore // only include when not monotonic. // Only 1 argument can be present when partially monotonic. String colName = monotonicOn.get(0); TableMetadata.spaceOrNewLine(sb, formatted) .append("MONOTONIC ON") .append(' ') .append(Metadata.quoteIfNecessary(colName)); } TableMetadata.spaceOrNewLine(sb, formatted).append("LANGUAGE ").append(language); TableMetadata.spaceOrNewLine(sb, formatted).append("AS '").append(body).append("';"); return sb.toString(); } /** * Returns the keyspace this function belongs to. * * @return the keyspace metadata of the keyspace this function belongs to. */ public KeyspaceMetadata getKeyspace() { return keyspace; } /** * Returns the CQL signature of this function. * *

This is the name of the function, followed by the names of the argument types between * parentheses, for example {@code sum(int,int)}. * *

Note that the returned signature is not qualified with the keyspace name. * * @return the signature of this function. */ public String getSignature() { StringBuilder sb = new StringBuilder(); sb.append(Metadata.quoteIfNecessary(simpleName)).append('('); boolean first = true; for (DataType type : arguments.values()) { if (first) first = false; else sb.append(','); sb.append(type.asFunctionParameterString()); } sb.append(')'); return sb.toString(); } /** * Returns the simple name of this function. * *

This is the name of the function, without arguments. Note that functions can be overloaded * with different argument lists, therefore the simple name may not be unique. For example, {@code * sum(int,int)} and {@code sum(int,int,int)} both have the simple name {@code sum}. * * @return the simple name of this function. * @see #getSignature() */ public String getSimpleName() { return simpleName; } /** * Returns the names and types of this function's arguments. * * @return a map from argument name to argument type. */ public Map getArguments() { return arguments; } /** * Returns the body of this function. * * @return the body. */ public String getBody() { return body; } /** * Indicates whether this function's body gets called on null input. * *

This is {@code true} if the function was created with {@code CALLED ON NULL INPUT}, and * {@code false} if it was created with {@code RETURNS NULL ON NULL INPUT}. * * @return whether this function's body gets called on null input. */ public boolean isCalledOnNullInput() { return calledOnNullInput; } /** * Returns the programming language in which this function's body is written. * * @return the language. */ public String getLanguage() { return language; } /** * Returns the return type of this function. * * @return the return type. */ public DataType getReturnType() { return returnType; } /** * @return Whether or not this function is deterministic. This means that given a particular * input, the function will always produce the same output. */ public boolean isDeterministic() { return deterministic; } /** * Indicates whether or not this function is monotonic on all of its arguments. This means that it * is either entirely non-increasing or non-decreasing. Even if the function is not monotonic on * all its arguments, it's possible to specify that it is monotonic on one of its arguments, * meaning that partial applications of the function over that argument will be monotonic. * *

Monotonicity is required to use the function in a GROUP BY clause. * * @return whether or not this function is monotonic on all of its arguments. */ public boolean isMonotonic() { return monotonic; } /** * Returns the argument names that the function is monotonic on. * *

If {@link #isMonotonic()} returns true, this will return all argument names. Otherwise this * will return either one argument or an empty list. */ public List getMonotonicOn() { return monotonicOn; } @Override public boolean equals(Object other) { if (other == this) return true; if (other instanceof FunctionMetadata) { FunctionMetadata that = (FunctionMetadata) other; return this.keyspace.getName().equals(that.keyspace.getName()) && this.arguments.equals(that.arguments) && this.body.equals(that.body) && this.calledOnNullInput == that.calledOnNullInput && this.language.equals(that.language) && this.returnType.equals(that.returnType) && this.deterministic == that.deterministic && this.monotonic == that.monotonic && this.monotonicOn.equals(that.monotonicOn); } return false; } @Override public int hashCode() { return MoreObjects.hashCode( keyspace.getName(), arguments, body, calledOnNullInput, language, returnType, deterministic, monotonic, monotonicOn); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy