SqlParser.java

/*
 * 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 org.apache.calcite.sql.parser;

import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.Quoting;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.config.CharLiteralStyle;
import org.apache.calcite.config.Lex;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rel.type.TimeFrameSet;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.parser.impl.SqlParserImpl;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlDelegatingConformance;
import org.apache.calcite.util.SourceStringReader;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

import org.immutables.value.Value;

import java.io.Reader;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A <code>SqlParser</code> parses a SQL statement.
 */
@Value.Enclosing
@SuppressWarnings("deprecation")
public class SqlParser {
  public static final int DEFAULT_IDENTIFIER_MAX_LENGTH = 128;

  /** Default value of {@link Config#timeUnitCodes()}.
   * The map is empty, which means that there are no abbreviations other than
   * the time unit names ("YEAR", "SECOND", etc.) */
  @Deprecated // to be removed before 2.0
  public static final ImmutableMap<String, TimeUnit> DEFAULT_IDENTIFIER_TIMEUNIT_MAP =
      ImmutableMap.of();

  @Deprecated // to be removed before 2.0
  public static final boolean DEFAULT_ALLOW_BANG_EQUAL =
      SqlConformanceEnum.DEFAULT.isBangEqualAllowed();

  //~ Instance fields --------------------------------------------------------
  private final SqlAbstractParserImpl parser;

  //~ Constructors -----------------------------------------------------------
  private SqlParser(SqlAbstractParserImpl parser,
      Config config) {
    this.parser = parser;
    parser.setTabSize(1);
    parser.setQuotedCasing(config.quotedCasing());
    parser.setUnquotedCasing(config.unquotedCasing());
    parser.setIdentifierMaxLength(config.identifierMaxLength());
    parser.setTimeUnitCodes(config.timeUnitCodes());
    parser.setConformance(config.conformance());
    parser.switchTo(SqlAbstractParserImpl.LexicalState.forConfig(config));
  }

  //~ Methods ----------------------------------------------------------------

  /**
   * Creates a <code>SqlParser</code> to parse the given string using
   * Calcite's parser implementation.
   *
   * <p>The default lexical policy is similar to Oracle.
   *
   * @see Lex#ORACLE
   *
   * @param s An SQL statement or expression to parse.
   * @return A parser
   */
  public static SqlParser create(String s) {
    return create(s, config());
  }

  /**
   * Creates a <code>SqlParser</code> to parse the given string using the
   * parser implementation created from given {@link SqlParserImplFactory}
   * with given quoting syntax and casing policies for identifiers.
   *
   * @param sql A SQL statement or expression to parse
   * @param config The parser configuration (identifier max length, etc.)
   * @return A parser
   */
  public static SqlParser create(String sql, Config config) {
    return create(new SourceStringReader(sql), config);
  }

  /**
   * Creates a <code>SqlParser</code> to parse the given string using the
   * parser implementation created from given {@link SqlParserImplFactory}
   * with given quoting syntax and casing policies for identifiers.
   *
   * <p>Unlike
   * {@link #create(java.lang.String, org.apache.calcite.sql.parser.SqlParser.Config)},
   * the parser is not able to return the original query string, but will
   * instead return "?".
   *
   * @param reader The source for the SQL statement or expression to parse
   * @param config The parser configuration (identifier max length, etc.)
   * @return A parser
   */
  public static SqlParser create(Reader reader, Config config) {
    SqlAbstractParserImpl parser =
        config.parserFactory().getParser(reader);

    return new SqlParser(parser, config);
  }

  /**
   * Parses a SQL expression.
   *
   * @throws SqlParseException if there is a parse error
   */
  public SqlNode parseExpression() throws SqlParseException {
    try {
      return parser.parseSqlExpressionEof();
    } catch (Throwable ex) {
      if (ex instanceof CalciteContextException) {
        final String originalSql = parser.getOriginalSql();
        if (originalSql != null) {
          ((CalciteContextException) ex).setOriginalStatement(originalSql);
        }
      }
      throw parser.normalizeException(ex);
    }
  }

  /** Normalizes a SQL exception. */
  private SqlParseException handleException(Throwable ex) {
    if (ex instanceof CalciteContextException) {
      final String originalSql = parser.getOriginalSql();
      if (originalSql != null) {
        ((CalciteContextException) ex).setOriginalStatement(originalSql);
      }
    }
    return parser.normalizeException(ex);
  }

  /**
   * Parses a <code>SELECT</code> statement.
   *
   * @return A {@link org.apache.calcite.sql.SqlSelect} for a regular <code>
   * SELECT</code> statement; a {@link org.apache.calcite.sql.SqlBinaryOperator}
   * for a <code>UNION</code>, <code>INTERSECT</code>, or <code>EXCEPT</code>.
   * @throws SqlParseException if there is a parse error
   */
  public SqlNode parseQuery() throws SqlParseException {
    try {
      return parser.parseSqlStmtEof();
    } catch (Throwable ex) {
      throw handleException(ex);
    }
  }

  /**
   * Parses a <code>SELECT</code> statement and reuses parser.
   *
   * @param sql sql to parse
   * @return A {@link org.apache.calcite.sql.SqlSelect} for a regular <code>
   * SELECT</code> statement; a {@link org.apache.calcite.sql.SqlBinaryOperator}
   * for a <code>UNION</code>, <code>INTERSECT</code>, or <code>EXCEPT</code>.
   * @throws SqlParseException if there is a parse error
   */
  public SqlNode parseQuery(String sql) throws SqlParseException {
    parser.ReInit(new StringReader(sql));
    return parseQuery();
  }

  /**
   * Parses an SQL statement.
   *
   * @return top-level SqlNode representing stmt
   * @throws SqlParseException if there is a parse error
   */
  public SqlNode parseStmt() throws SqlParseException {
    return parseQuery();
  }

  /**
   * Parses a list of SQL statements separated by semicolon.
   * The semicolon is required between statements, but is
   * optional at the end.
   *
   * @return list of SqlNodeList representing the list of SQL statements
   * @throws SqlParseException if there is a parse error
   */
  public SqlNodeList parseStmtList() throws SqlParseException {
    try {
      return parser.parseSqlStmtList();
    } catch (Throwable ex) {
      throw handleException(ex);
    }
  }

  /**
   * Get the parser metadata.
   *
   * @return {@link SqlAbstractParserImpl.Metadata} implementation of
   *     underlying parser.
   */
  public SqlAbstractParserImpl.Metadata getMetadata() {
    return parser.getMetadata();
  }

  /**
   * Returns the warnings that were generated by the previous invocation
   * of the parser.
   */
  public List<CalciteContextException> getWarnings() {
    return parser.warnings;
  }

  /** Returns a default {@link Config}. */
  public static Config config() {
    return Config.DEFAULT;
  }

  /**
   * Builder for a {@link Config}.
   *
   * @deprecated Use {@link #config()}
   */
  @Deprecated // to be removed before 2.0
  public static ConfigBuilder configBuilder() {
    return new ConfigBuilder();
  }

  /**
   * Builder for a {@link Config} that starts with an existing {@code Config}.
   *
   * @deprecated Use {@code config}, and modify it using its mutator methods
   */
  @Deprecated // to be removed before 2.0
  public static ConfigBuilder configBuilder(Config config) {
    return new ConfigBuilder().setConfig(config);
  }

  /**
   * Interface to define the configuration for a SQL parser.
   */
  @Value.Immutable
  @SuppressWarnings("deprecation")
  public interface Config {
    /** Default configuration. */
    Config DEFAULT = ImmutableSqlParser.Config.of();

    @Value.Default default int identifierMaxLength() {
      return DEFAULT_IDENTIFIER_MAX_LENGTH;
    }

    /** Sets {@link #identifierMaxLength()}. */
    Config withIdentifierMaxLength(int identifierMaxLength);

    @Value.Default default Casing quotedCasing() {
      return Casing.UNCHANGED;
    }

    /** Sets {@link #quotedCasing()}. */
    Config withQuotedCasing(Casing casing);

    @Value.Default default Casing unquotedCasing() {
      return Casing.TO_UPPER;
    }

    /** Sets {@link #unquotedCasing()}. */
    Config withUnquotedCasing(Casing casing);

    @Value.Default default Quoting quoting() {
      return Quoting.DOUBLE_QUOTE;
    }

    /** Sets {@link #quoting()}. */
    Config withQuoting(Quoting quoting);

    @Value.Default default boolean caseSensitive() {
      return true;
    }

    /** Sets {@link #caseSensitive()}. */
    Config withCaseSensitive(boolean caseSensitive);

    @Value.Default default SqlConformance conformance() {
      return SqlConformanceEnum.DEFAULT;
    }

    /** Sets {@link #conformance()}. */
    Config withConformance(SqlConformance conformance);

    @Deprecated // to be removed before 2.0
    @SuppressWarnings("deprecation")
    @Value.Default default boolean allowBangEqual() {
      return DEFAULT_ALLOW_BANG_EQUAL;
    }

    /** Returns which character literal styles are supported. */
    @Value.Default default Set<CharLiteralStyle> charLiteralStyles() {
      return ImmutableSet.of(CharLiteralStyle.STANDARD);
    }

    /** Sets {@link #charLiteralStyles()}. */
    Config withCharLiteralStyles(Iterable<CharLiteralStyle> charLiteralStyles);

    /** Returns a mapping from abbreviations to time units.
     *
     * <p>For example, if the map contains the entry
     * ("Y", {@link TimeUnit#YEAR}) then you can write
     * "{@code EXTRACT(S FROM orderDate)}".
     *
     * @deprecated This property is deprecated, and has no effect. All
     * non-standard time units are now parsed as identifiers, and resolved in
     * the validator. You can define custom time frames using
     * {@link RelDataTypeSystem#deriveTimeFrameSet(TimeFrameSet)}. To alias a
     * time frame, use {@link TimeFrameSet.Builder#addAlias(String, String)}. */
    @Deprecated // to be removed before 2.0
    @Value.Default default Map<String, TimeUnit> timeUnitCodes() {
      return DEFAULT_IDENTIFIER_TIMEUNIT_MAP;
    }

    /** Sets {@link #timeUnitCodes()}. */
    Config withTimeUnitCodes(Map<String, ? extends TimeUnit> timeUnitCodes);

    @Value.Default default SqlParserImplFactory parserFactory() {
      return SqlParserImpl.FACTORY;
    }

    /** Sets {@link #parserFactory()}. */
    Config withParserFactory(SqlParserImplFactory factory);

    default Config withLex(Lex lex) {
      return withCaseSensitive(lex.caseSensitive)
          .withUnquotedCasing(lex.unquotedCasing)
          .withQuotedCasing(lex.quotedCasing)
          .withQuoting(lex.quoting)
          .withCharLiteralStyles(lex.charLiteralStyles);
    }
  }

  /** Builder for a {@link Config}. */
  @Deprecated // to be removed before 2.0
  public static class ConfigBuilder {
    private Config config = Config.DEFAULT;

    private ConfigBuilder() {}

    /** Sets configuration to a given {@link Config}. */
    public ConfigBuilder setConfig(Config config) {
      this.config = config;
      return this;
    }

    public ConfigBuilder setQuotedCasing(Casing quotedCasing) {
      return setConfig(config.withQuotedCasing(quotedCasing));
    }

    public ConfigBuilder setUnquotedCasing(Casing unquotedCasing) {
      return setConfig(config.withUnquotedCasing(unquotedCasing));
    }

    public ConfigBuilder setQuoting(Quoting quoting) {
      return setConfig(config.withQuoting(quoting));
    }

    public ConfigBuilder setCaseSensitive(boolean caseSensitive) {
      return setConfig(config.withCaseSensitive(caseSensitive));
    }

    public ConfigBuilder setIdentifierMaxLength(int identifierMaxLength) {
      return setConfig(config.withIdentifierMaxLength(identifierMaxLength));
    }

    public ConfigBuilder setIdentifierTimeUnitMap(
        ImmutableMap<String, TimeUnit> identifierTimeUnitMap) {
      return setConfig(config.withTimeUnitCodes(identifierTimeUnitMap));
    }

    @SuppressWarnings("unused")
    @Deprecated // to be removed before 2.0
    public ConfigBuilder setAllowBangEqual(final boolean allowBangEqual) {
      if (allowBangEqual != config.conformance().isBangEqualAllowed()) {
        return setConformance(
            new SqlDelegatingConformance(config.conformance()) {
              @Override public boolean isBangEqualAllowed() {
                return allowBangEqual;
              }
            });
      }
      return this;
    }

    public ConfigBuilder setConformance(SqlConformance conformance) {
      return setConfig(config.withConformance(conformance));
    }

    public ConfigBuilder setCharLiteralStyles(
        Set<CharLiteralStyle> charLiteralStyles) {
      return setConfig(config.withCharLiteralStyles(charLiteralStyles));
    }

    public ConfigBuilder setParserFactory(SqlParserImplFactory factory) {
      return setConfig(config.withParserFactory(factory));
    }

    public ConfigBuilder setLex(Lex lex) {
      return setConfig(config.withLex(lex));
    }

    /** Builds a {@link Config}. */
    public Config build() {
      return config;
    }
  }
}