gt:                     quote(buf, value, datatype);
context: /*
  // This software is subject to the terms of the Eclipse Public License v1.0
  // Agreement, available at the following URL:
  // http://www.eclipse.org/legal/epl-v10.html.
  // You must accept the terms of that agreement to use this software.
  //
  // Copyright (c) 2002-2016 Pentaho Corporation..  All rights reserved.
  */
  package mondrian.spi.impl;
  
  import mondrian.olap.MondrianProperties;
  import mondrian.olap.Util;
  import mondrian.rolap.SqlStatement;
  import mondrian.spi.Dialect;
  import mondrian.spi.Dialect.DatabaseProduct;
  import mondrian.spi.StatisticsProvider;
  import mondrian.util.ClassResolver;
  
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  import java.sql.*;
  import java.sql.Date;
  import java.util.*;
  import java.util.regex.Pattern;
  
  /**
   * Implementation of {@link Dialect} based on a JDBC connection and metadata.
   *
   * <p>If you are writing a class for a specific database dialect, we recommend
   * that you use this as a base class, so your dialect class will be
   * forwards-compatible. If methods are added to {@link Dialect} in future
   * revisions, default implementations of those methods will be added to this
   * class.</p>
   *
   * <p>Mondrian uses JdbcDialectImpl as a fallback if it cannot find a more
   * specific dialect. JdbcDialectImpl reads properties from the JDBC driver's
   * metadata, so can deduce some of the dialect's behavior.</p>
   *
   * @author jhyde
   * @since Oct 10, 2008
   */
  public class JdbcDialectImpl implements Dialect {
      private static final Log LOGGER = LogFactory.getLog(JdbcDialectImpl.class);
  
      /**
       * String used to quote identifiers.
       */
      private final String quoteIdentifierString;
  
      /**
       * Product name per JDBC driver.
       */
      private final String productName;
  
      /**
       * Product version per JDBC driver.
       */
      protected final String productVersion;
  
      /**
       * Supported result set types.
       */
      private final Set<List<Integer>> supportedResultSetTypes;
  
      /**
       * Whether database is read-only
       */
      private final boolean readOnly;
  
      /**
       * Maximum column name length
       */
      private final int maxColumnNameLength;
  
      /**
       * Indicates whether the database allows selection of columns
       * not listed in the group by clause.
       */
      protected boolean permitsSelectNotInGroupBy;
  
      /**
       * Major database product (or null if product is not a common one)
       */
      protected final DatabaseProduct databaseProduct;
  
      /**
       * List of statistics providers.
       */
      private final List<StatisticsProvider> statisticsProviders;
  
      private static final int[] RESULT_SET_TYPE_VALUES = {
          ResultSet.TYPE_FORWARD_ONLY,
          ResultSet.TYPE_SCROLL_INSENSITIVE,
          ResultSet.TYPE_SCROLL_SENSITIVE};
  
      private static final int[] CONCURRENCY_VALUES = {
          ResultSet.CONCUR_READ_ONLY,
          ResultSet.CONCUR_UPDATABLE};
  
      /**
       * The size required to add quotes around a string - this ought to be
       * large enough to prevent a reallocation.
       */
      private static final int SINGLE_QUOTE_SIZE = 10;
      /**
       * Two strings are quoted and the character '.' is placed between them.
       */
      private static final int DOUBLE_QUOTE_SIZE = 2 * SINGLE_QUOTE_SIZE + 1;
  
      /**
       * The default mapping of java.sql.Types to SqlStatement.Type
       */
      private static final Map<Types, SqlStatement.Type> DEFAULT_TYPE_MAP;
      static {
          Map typeMapInitial = new HashMap<Types, SqlStatement.Type>();
          typeMapInitial.put(Types.SMALLINT, SqlStatement.Type.INT);
          typeMapInitial.put(Types.INTEGER, SqlStatement.Type.INT);
          typeMapInitial.put(Types.BOOLEAN, SqlStatement.Type.INT);
          typeMapInitial.put(Types.DOUBLE, SqlStatement.Type.DOUBLE);
          typeMapInitial.put(Types.FLOAT, SqlStatement.Type.DOUBLE);
          typeMapInitial.put(Types.BIGINT, SqlStatement.Type.DOUBLE);
  
          DEFAULT_TYPE_MAP = Collections.unmodifiableMap(typeMapInitial);
      }
  
      /**
       * Creates a JdbcDialectImpl.
       *
       * <p>To prevent connection leaks, this constructor does not hold a
       * reference to the connection after the call returns. It makes a copy of
       * everything useful during the call.  Derived classes must do the
       * same.</p>
       *
       * @param connection Connection
       *
       * @throws java.sql.SQLException on error
       */
      public JdbcDialectImpl(
          Connection connection)
          throws SQLException
      {
          final DatabaseMetaData metaData = connection.getMetaData();
          this.quoteIdentifierString = deduceIdentifierQuoteString(metaData);
          this.productName = deduceProductName(metaData);
          this.productVersion = deduceProductVersion(metaData);
          this.supportedResultSetTypes = deduceSupportedResultSetStyles(metaData);
          this.readOnly = deduceReadOnly(metaData);
          this.maxColumnNameLength = deduceMaxColumnNameLength(metaData);
          this.databaseProduct =
              getProduct(this.productName, this.productVersion);
          this.permitsSelectNotInGroupBy =
              deduceSupportsSelectNotInGroupBy(connection);
          this.statisticsProviders = computeStatisticsProviders();
      }
  
      public JdbcDialectImpl() {
          quoteIdentifierString = "";
          productName = "";
          productVersion = "";
          supportedResultSetTypes = null;
          readOnly = true;
          maxColumnNameLength = 0;
          databaseProduct = null;
          permitsSelectNotInGroupBy = true;
          statisticsProviders = null;
      }
  
      public DatabaseProduct getDatabaseProduct() {
          return databaseProduct;
      }
  
      public void appendHintsAfterFromClause(
          StringBuilder buf,
          Map<String, String> hints)
      {
          // Hints are always dialect-specific, so the default is a no-op
      }
  
      public boolean allowsDialectSharing() {
          return true;
      }
  
      protected int deduceMaxColumnNameLength(DatabaseMetaData databaseMetaData) {
          try {
              return databaseMetaData.getMaxColumnNameLength();
          } catch (SQLException e) {
              throw Util.newInternal(
                  e,
                  "while detecting maxColumnNameLength");
          }
      }
  
      protected boolean deduceReadOnly(DatabaseMetaData databaseMetaData) {
          try {
              return databaseMetaData.isReadOnly();
          } catch (SQLException e) {
              throw Util.newInternal(
                  e,
                  "while detecting isReadOnly");
          }
      }
  
      protected String deduceProductName(DatabaseMetaData databaseMetaData) {
          try {
              return databaseMetaData.getDatabaseProductName();
          } catch (SQLException e) {
              throw Util.newInternal(e, "while detecting database product");
          }
      }
  
      protected String deduceIdentifierQuoteString(
          DatabaseMetaData databaseMetaData)
      {
          try {
              final String quoteIdentifierString =
                  databaseMetaData.getIdentifierQuoteString();
              return "".equals(quoteIdentifierString)
                  // quoting not supported
                  ? null
                  : quoteIdentifierString;
          } catch (SQLException e) {
              throw Util.newInternal(e, "while quoting identifier");
          }
      }
  
      protected String deduceProductVersion(DatabaseMetaData databaseMetaData) {
          String productVersion;
          try {
              productVersion = databaseMetaData.getDatabaseProductVersion();
          } catch (SQLException e11) {
              throw Util.newInternal(
                  e11,
                  "while detecting database product version");
          }
          return productVersion;
      }
  
      protected Set<List<Integer>> deduceSupportedResultSetStyles(
          DatabaseMetaData databaseMetaData)
      {
          Set<List<Integer>> supports = new HashSet<List<Integer>>();
          for (int type : RESULT_SET_TYPE_VALUES) {
              for (int concurrency : CONCURRENCY_VALUES) {
                  try {
                      if (databaseMetaData.supportsResultSetConcurrency(
                              type, concurrency))
                      {
                          String driverName =
                              databaseMetaData.getDriverName();
                          if (type != ResultSet.TYPE_FORWARD_ONLY
                              && driverName.equals(
                                  "JDBC-ODBC Bridge (odbcjt32.dll)"))
                          {
                              // In JDK 1.6, the Jdbc-Odbc bridge announces
                              // that it can handle TYPE_SCROLL_INSENSITIVE
                              // but it does so by generating a 'COUNT(*)'
                              // query, and this query is invalid if the query
                              // contains a single-quote. So, override the
                              // driver.
                              continue;
                          }
                          supports.add(
                              new ArrayList<Integer>(
                                  Arrays.asList(type, concurrency)));
                      }
                  } catch (SQLException e) {
                      // DB2 throws "com.ibm.db2.jcc.b.SqlException: Unknown type
                      // or Concurrency" for certain values of type/concurrency.
                      // No harm in interpreting all such exceptions as 'this
                      // database does not support this type/concurrency
                      // combination'.
                      Util.discard(e);
                  }
              }
          }
          return supports;
      }
  
       /**
        * <p>Detects whether the database is configured to permit queries
        * that include columns in the SELECT that are not also in the GROUP BY.
        * MySQL is an example of one that does, though this is configurable.</p>
        *
        * <p>The expectation is that this will not change while Mondrian is
        * running, though some databases (MySQL) allow changing it on the fly.</p>
        *
        * @param conn The database connection
        * @return Whether the feature is enabled.
        * @throws SQLException on error
        */
      protected boolean deduceSupportsSelectNotInGroupBy(Connection conn)
          throws SQLException
      {
          // Most simply don't support it
          return false;
      }
  
      public String toUpper(String expr) {
          return "UPPER(" + expr + ")";
      }
  
      public String caseWhenElse(String cond, String thenExpr, String elseExpr) {
          return "CASE WHEN " + cond + " THEN " + thenExpr + " ELSE " + elseExpr
              + " END";
      }
  
      public String quoteIdentifier(final String val) {
          int size = val.length() + SINGLE_QUOTE_SIZE;
          StringBuilder buf = new StringBuilder(size);
  
          quoteIdentifier(val, buf);
  
          return buf.toString();
      }
  
      public void quoteIdentifier(final String val, final StringBuilder buf) {
          String q = getQuoteIdentifierString();
          if (q == null) {
              // quoting is not supported
              buf.append(val);
              return;
          }
          // if the value is already quoted, do nothing
          //  if not, then check for a dot qualified expression
          //  like "owner.table".
          //  In that case, prefix the single parts separately.
          if (val.startsWith(q) && val.endsWith(q)) {
              // already quoted - nothing to do
              buf.append(val);
              return;
          }
  
          String val2 = Util.replace(val, q, q + q);
          buf.append(q);
          buf.append(val2);
          buf.append(q);
      }
  
      public String quoteIdentifier(final String qual, final String name) {
          // We know if the qalifier is null, then only the name is going
          // to be quoted.
          int size = name.length()
              + ((qual == null)
                  ? SINGLE_QUOTE_SIZE
                  : (qual.length() + DOUBLE_QUOTE_SIZE));
          StringBuilder buf = new StringBuilder(size);
  
          quoteIdentifier(buf, qual, name);
  
          return buf.toString();
      }
  
      public void quoteIdentifier(
          final StringBuilder buf,
          final String... names)
      {
          int nonNullNameCount = 0;
          for (String name : names) {
              if (name == null) {
                  continue;
              }
              if (nonNullNameCount > 0) {
                  buf.append('.');
              }
              assert name.length() > 0
                  : "name should probably be null, not empty";
              quoteIdentifier(name, buf);
              ++nonNullNameCount;
          }
      }
  
      public String getQuoteIdentifierString() {
          return quoteIdentifierString;
      }
  
      public void quoteStringLiteral(
          StringBuilder buf,
          String s)
      {
          Util.singleQuoteString(s, buf);
      }
  
      public void quoteNumericLiteral(
          StringBuilder buf,
          String value)
      {
          buf.append(value);
      }
  
      public void quoteBooleanLiteral(StringBuilder buf, String value) {
          // NOTE jvs 1-Jan-2007:  See quoteDateLiteral for explanation.
          // In addition, note that we leave out UNKNOWN (even though
          // it is a valid SQL:2003 literal) because it's really
          // NULL in disguise, and NULL is always treated specially.
          if (!value.equalsIgnoreCase("TRUE")
              && !(value.equalsIgnoreCase("FALSE")))
          {
              throw new NumberFormatException(
                  "Illegal BOOLEAN literal:  " + value);
          }
          buf.append(value);
      }
  
      public void quoteDateLiteral(StringBuilder buf, String value) {
          // NOTE jvs 1-Jan-2007: Check that the supplied literal is in valid
          // SQL:2003 date format.  A hack in
          // RolapSchemaReader.lookupMemberChildByName looks for
          // NumberFormatException to suppress it, so that is why
          // we convert the exception here.
          Date date;
          try {
                date = Date.valueOf(value);
          } catch (IllegalArgumentException ex) {
              // MONDRIAN-2038
              try {
                  date = new Date(Timestamp.valueOf(value).getTime());
              } catch (IllegalArgumentException ex2) {
                  throw new NumberFormatException(
                      "Illegal DATE literal:  " + value);
              }
          }
          quoteDateLiteral(buf, date.toString(), date);
      }
  
      /**
       * Helper method for {@link #quoteDateLiteral(StringBuilder, String)}.
       *
       * @param buf Buffer to append to
       * @param value Value as string
       * @param date Value as date
       */
      protected void quoteDateLiteral(
          StringBuilder buf,
          String value,
          Date date)
      {
          // SQL:2003 date format: DATE '2008-01-23'.
          buf.append("DATE ");
          Util.singleQuoteString(value, buf);
      }
  
      public void quoteTimeLiteral(StringBuilder buf, String value) {
          // NOTE jvs 1-Jan-2007:  See quoteDateLiteral for explanation.
          try {
              Time.valueOf(value);
          } catch (IllegalArgumentException ex) {
              throw new NumberFormatException(
                  "Illegal TIME literal:  " + value);
          }
          buf.append("TIME ");
          Util.singleQuoteString(value, buf);
      }
  
      public void quoteTimestampLiteral(
          StringBuilder buf,
          String value)
      {
          // NOTE jvs 1-Jan-2007:  See quoteTimestampLiteral for explanation.
          try {
              Timestamp.valueOf(value);
          } catch (IllegalArgumentException ex) {
              throw new NumberFormatException(
                  "Illegal TIMESTAMP literal:  " + value);
          }
          buf.append("TIMESTAMP ");
          Util.singleQuoteString(value, buf);
      }
  
      public boolean requiresAliasForFromQuery() {
          return false;
      }
  
      public boolean allowsAs() {
          return true;
      }
  
      public boolean allowsFromQuery() {
          return true;
      }
  
      public boolean allowsCompoundCountDistinct() {
          return false;
      }
  
      public boolean allowsCountDistinct() {
          return true;
      }
  
      public boolean allowsMultipleCountDistinct() {
          return allowsCountDistinct();
      }
  
      public boolean allowsMultipleDistinctSqlMeasures() {
          return allowsMultipleCountDistinct();
      }
  
      public boolean allowsCountDistinctWithOtherAggs() {
        return allowsCountDistinct();
      }
  
      public String generateInline(
          List<String> columnNames,
          List<String> columnTypes,
          List<String[]> valueList)
      {
          return generateInlineForAnsi(
              "t", columnNames, columnTypes, valueList, false);
      }
  
      /**
       * Generic algorithm to generate inline values list,
       * using an optional FROM clause, specified by the caller of this
       * method, appropriate to the dialect of SQL.
       *
       * @param columnNames Column names
       * @param columnTypes Column types
       * @param valueList List rows
       * @param fromClause FROM clause, or null
       * @param cast Whether to cast the values in the first row
       * @return Expression that returns the given values
       */
      protected String generateInlineGeneric(
          List<String> columnNames,
          List<String> columnTypes,
          List<String[]> valueList,
          String fromClause,
          boolean cast)
      {
          final StringBuilder buf = new StringBuilder();
          int columnCount = columnNames.size();
          assert columnTypes.size() == columnCount;
  
          // Some databases, e.g. Teradata, derives datatype from value of column
          // in first row, and truncates subsequent rows. Therefore, we need to
          // cast every value to the correct length. Figure out the maximum length
          // now.
          Integer[] maxLengths = new Integer[columnCount];
          if (cast) {
              for (int i = 0; i < columnTypes.size(); i++) {
                  String columnType = columnTypes.get(i);
                  Datatype datatype = Datatype.valueOf(columnType);
                  if (datatype == Datatype.String) {
                      int maxLen = -1;
                      for (String[] strings : valueList) {
                          if (strings[i] != null
                              && strings[i].length() > maxLen)
                          {
                              maxLen = strings[i].length();
                          }
                      }
                      maxLengths[i] = maxLen;
                  }
              }
          }
  
          for (int i = 0; i < valueList.size(); i++) {
              if (i > 0) {
                  buf.append(" union all ");
              }
              String[] values = valueList.get(i);
              buf.append("select ");
              for (int j = 0; j < values.length; j++) {
                  String value = values[j];
                  if (j > 0) {
                      buf.append(", ");
                  }
                  final String columnType = columnTypes.get(j);
                  final String columnName = columnNames.get(j);
                  Datatype datatype = Datatype.valueOf(columnType);
                  final Integer maxLength = maxLengths[j];
                  if (maxLength != null) {
                      // Generate CAST for Teradata.
                      buf.append("CAST(");
                      quote(buf, value, datatype);
                      buf.append(" AS VARCHAR(").append(maxLength).append("))");
                  } else {
  