<|start_filename|>src/main/java/ai/heavy/jdbc/HeavyAIArray.java<|end_filename|>
package ai.heavy.jdbc;

import static java.lang.Math.toIntExact;

import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;

import ai.heavy.thrift.server.*;

public class HeavyAIArray implements java.sql.Array {
  private TDatumType type;
  private Object[] elements;

  public HeavyAIArray(TDatumType type, Object[] elements) throws SQLException {
    if (elements == null) {
      throw new SQLException("Elements[] cannot be null");
    }
    this.type = type;
    this.elements = elements;
    Class<?> elements_class = elements.getClass().getComponentType();
    switch (type) {
      case TINYINT:
        checkClass(elements_class, Byte.class);
        break;
      case SMALLINT:
        checkClass(elements_class, Short.class);
        break;
      case INT:
        checkClass(elements_class, Integer.class);
        break;
      case BIGINT:
        checkClass(elements_class, Long.class);
        break;
      case BOOL:
        checkClass(elements_class, Boolean.class);
        break;
      case TIME:
        checkClass(elements_class, java.sql.Time.class);
        break;
      case TIMESTAMP:
        checkClass(elements_class, java.sql.Timestamp.class);
        break;
      case DATE:
        checkClass(elements_class, java.sql.Date.class);
        break;
      case FLOAT:
        checkClass(elements_class, Float.class);
        break;
      case DECIMAL:
        checkClass(elements_class, BigDecimal.class);
        break;
      case DOUBLE:
        checkClass(elements_class, Double.class);
        break;
      case STR:
      case POINT:
      case LINESTRING:
      case POLYGON:
      case MULTIPOLYGON:
        checkClass(elements_class, String.class);
        break;
      default:
        throw new AssertionError(type.toString());
    }
  }

  @Override
  public String getBaseTypeName() throws SQLException {
    return type.name();
  }

  @Override
  public int getBaseType() throws SQLException {
    return HeavyAIType.toJava(type);
  }

  @Override
  public Object getArray() throws SQLException {
    return elements;
  }

  @Override
  public Object getArray(long start, int size) throws SQLException {
    checkSize(toIntExact(start), size);
    return Arrays.copyOfRange(elements, toIntExact(start), size);
  }

  @Override
  public ResultSet getResultSet() throws SQLException {
    return getResultSet(0, elements.length);
  }

  @Override
  public ResultSet getResultSet(long start, int size) throws SQLException {
    checkSize(toIntExact(start), size);

    ArrayList<TColumnType> columnTypes = new ArrayList<>(2);
    TTypeInfo idxType =
            new TTypeInfo(TDatumType.BIGINT, TEncodingType.NONE, false, false, 0, 0, 0);
    columnTypes.add(new TColumnType("INDEX", idxType, false, "", false, false, 0));
    // @NOTE(Max): The problem here is that it's hard to know precision and scale.
    // But it looks like we don't use those anywhere in ResultSet ???
    int precision = (type == TDatumType.TIMESTAMP || type == TDatumType.TIME
                            || type == TDatumType.DATE
                    ? 3
                    : 0);
    TTypeInfo valueType =
            new TTypeInfo(type, TEncodingType.NONE, true, false, precision, 0, 0);
    columnTypes.add(new TColumnType("VALUE", valueType, false, "", false, false, 1));

    Long[] indexes = new Long[size];
    // indexes in SQL arrays start from 1
    for (int i = 0; i < size; ++i) {
      indexes[i] = (long) (i + 1);
    }
    TColumnData idxData = new TColumnData(Arrays.asList(indexes), null, null, null);
    ArrayList<Boolean> idxNulls = new ArrayList<>(size);
    for (int i = 0; i < size; ++i) {
      idxNulls.add(Boolean.FALSE);
    }

    TColumnData valuesData;
    Long[] int_values = new Long[size];
    Double[] real_values = new Double[size];
    String[] string_values = new String[size];
    boolean is_real = false, is_string = false;
    ArrayList<Boolean> valueNulls = new ArrayList<>(size);
    for (int i = toIntExact(start); i < start + size; ++i) {
      if (elements[i] == null) {
        valueNulls.add(true);
      } else {
        valueNulls.add(false);
        switch (type) {
          case TINYINT:
            int_values[i] = ((Byte) elements[i]).longValue();
            break;
          case SMALLINT:
            int_values[i] = ((Short) elements[i]).longValue();
            break;
          case INT:
            int_values[i] = ((Integer) elements[i]).longValue();
            break;
          case BIGINT:
            int_values[i] = (Long) elements[i];
            break;
          case BOOL:
            int_values[i] = elements[i] == Boolean.TRUE ? 1l : 0l;
            break;
          case TIME:
            int_values[i] = ((Time) elements[i]).getTime();
            break;
          case TIMESTAMP:
            int_values[i] = ((Timestamp) elements[i]).getTime();
            break;
          case DATE:
            int_values[i] = ((Date) elements[i]).getTime();
            break;
          case FLOAT:
            is_real = true;
            real_values[i] = ((Float) elements[i]).doubleValue();
            break;
          case DECIMAL:
            is_real = true;
            real_values[i] = ((BigDecimal) elements[i]).doubleValue();
            break;
          case DOUBLE:
            is_real = true;
            real_values[i] = (Double) elements[i];
            break;
          case STR:
          case POINT:
          case LINESTRING:
          case POLYGON:
          case MULTIPOLYGON:
            is_string = true;
            string_values[i] = (String) elements[i];
            break;
          default:
            throw new AssertionError(type.toString());
        }
      }
    }
    if (is_real) {
      valuesData = new TColumnData(null, Arrays.asList(real_values), null, null);
    } else if (is_string) {
      valuesData = new TColumnData(null, null, Arrays.asList(string_values), null);
    } else {
      valuesData = new TColumnData(Arrays.asList(int_values), null, null, null);
    }

    ArrayList<TColumn> columns = new ArrayList<>(2);
    columns.add(new TColumn(idxData, idxNulls));
    columns.add(new TColumn(valuesData, valueNulls));
    TRowSet rowSet = new TRowSet(columnTypes, null, columns, true);
    TQueryResult result = new TQueryResult(rowSet, 0, 0, "", "", true, TQueryType.READ);
    return new HeavyAIResultSet(result, "");
  }

  @Override
  public void free() throws SQLException {
    elements = null;
  }

  @Override
  public String toString() {
    if (elements == null) {
      return "NULL";
    } else {
      switch (type) {
        case STR:
        case POINT:
        case LINESTRING:
        case POLYGON:
        case MULTIPOLYGON:
        case TIME:
        case TIMESTAMP:
        case DATE: {
          StringBuilder sb = new StringBuilder("{");
          for (Object e : elements) {
            if (e != null) {
              sb.append("'").append(e.toString()).append("', ");
            } else {
              sb.append("NULL").append(", ");
            }
          }
          if (elements.length > 0) {
            sb.delete(sb.length() - 2, sb.length());
          }
          sb.append("}");
          return sb.toString();
        }
        default: {
          String arr_str = Arrays.toString(elements);
          return "{" + arr_str.substring(1, arr_str.length() - 1) + "}";
        }
      }
    }
  }

  @Override
  public ResultSet getResultSet(long start, int size, Map<String, Class<?>> map)
          throws SQLException {
    throw new UnsupportedOperationException("Not supported yet,"
            + " line:" + new Throwable().getStackTrace()[0].getLineNumber()
            + " class:" + new Throwable().getStackTrace()[0].getClassName()
            + " method:" + new Throwable().getStackTrace()[0].getMethodName());
  }

  @Override
  public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet,"
            + " line:" + new Throwable().getStackTrace()[0].getLineNumber()
            + " class:" + new Throwable().getStackTrace()[0].getClassName()
            + " method:" + new Throwable().getStackTrace()[0].getMethodName());
  }

  @Override
  public Object getArray(long start, int size, Map<String, Class<?>> map)
          throws SQLException {
    throw new UnsupportedOperationException("Not supported yet,"
            + " line:" + new Throwable().getStackTrace()[0].getLineNumber()
            + " class:" + new Throwable().getStackTrace()[0].getClassName()
            + " method:" + new Throwable().getStackTrace()[0].getMethodName());
  }

  @Override
  public Object getArray(Map<String, Class<?>> map) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet,"
            + " line:" + new Throwable().getStackTrace()[0].getLineNumber()
            + " class:" + new Throwable().getStackTrace()[0].getClassName()
            + " method:" + new Throwable().getStackTrace()[0].getMethodName());
  }

  private void checkSize(int start, int size) throws SQLException {
    if (start < 0 || start >= elements.length || start + size > elements.length) {
      throw new SQLException("Array length = " + Integer.toString(elements.length)
              + ", slice start index = " + Integer.toString(start)
              + ", slice length = " + Integer.toString(size));
    }
  }

  private void checkClass(Class<?> given, Class<?> expected) throws SQLException {
    if (!expected.isAssignableFrom(given)) {
      throw new SQLException("For array of " + getBaseTypeName() + ", elements of type "
              + expected + " are expected. Got " + given + " instead");
    }
  }
}


<|start_filename|>src/test/java/ai/heavy/jdbc/HeavyAIArrayTest.java<|end_filename|>
package ai.heavy.jdbc;

import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.math.BigDecimal;
import java.sql.*;
import java.util.Properties;
import java.util.TimeZone;

// Create Array and validate
// Insert arrays and validate it is the same

public class HeavyAIArrayTest {
  static Properties PROPERTIES = new Property_loader("prepare_test.properties");
  static final String url = PROPERTIES.getProperty("default_db_connection_url");
  static final String user = PROPERTIES.getProperty("default_super_user");
  static final String password = PROPERTIES.getProperty("default_user_password");

  static final Integer[] ia = {100, null, 200, 300, 400, 500};
  static final Float[] fa = {null, null, 1.0001f, 12.541f, null};
  static final BigDecimal[] da = {BigDecimal.valueOf(1.100),
          null,
          BigDecimal.valueOf(2.15),
          BigDecimal.valueOf(3.5)};
  static final String[] sa = {"Hello", null, "World", "!"};
  static final Timestamp[] ta = {new Timestamp(System.currentTimeMillis()),
          new Timestamp(System.currentTimeMillis() + 1000000),
          null};

  private Connection m_conn = null;

  @Before
  public void setUp() throws Exception {
    Properties pt = new Properties();
    pt.setProperty("user", user);
    pt.setProperty("password", password);
    m_conn = DriverManager.getConnection(url, pt);
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
  }

  @After
  public void tearDown() throws Exception {
    m_conn.close();
  }

  // the test below makes sure that what we put into HeavyDBSciArray
  // is exactly what we get both from getArray() and getResultSet() methods
  @Test
  public void create_and_read_array_test() throws Exception {
    Array ints = m_conn.createArrayOf("INT", ia);
    Array floats = m_conn.createArrayOf("FLOAT", fa);
    Array decimals = m_conn.createArrayOf("DECIMAL", da);
    Array strings = m_conn.createArrayOf("STR", sa);
    Array timestamps = m_conn.createArrayOf("TIMESTAMP", ta);

    assertEquals(ints.getArray(), ia);
    assertEquals(floats.getArray(), fa);
    assertEquals(decimals.getArray(), da);
    assertEquals(strings.getArray(), sa);
    assertEquals(timestamps.getArray(), ta);

    ResultSet rs = ints.getResultSet();
    while (rs.next()) {
      Integer val = rs.getInt(2);
      if (rs.wasNull()) val = null;
      assertEquals(val, ia[rs.getInt(1) - 1]);
    }
    rs = floats.getResultSet();
    while (rs.next()) {
      Float val = rs.getFloat(2);
      if (rs.wasNull()) val = null;
      assertEquals(val, fa[rs.getInt(1) - 1]);
    }
    rs = decimals.getResultSet();
    while (rs.next()) {
      assertEquals(rs.getBigDecimal(2), da[rs.getInt(1) - 1]);
    }
    rs = strings.getResultSet();
    while (rs.next()) {
      assertEquals(rs.getString(2), sa[rs.getInt(1) - 1]);
    }
    rs = timestamps.getResultSet();
    while (rs.next()) {
      assertEquals(rs.getTimestamp(2), ta[rs.getInt(1) - 1]);
    }
  }

  @Test
  public void insert_select_test() throws Exception {
    Statement stmt = m_conn.createStatement();
    stmt.executeUpdate("DROP TABLE IF EXISTS arrays_tbl");
    stmt.executeUpdate("CREATE TABLE arrays_tbl ("
            + "i integer,"
            + "ia INTEGER[],"
            + "fa FLOAT[],"
            + "da DECIMAL(4,3)[],"
            + "sa TEXT[] ENCODING DICT,"
            + "ta TIMESTAMP(3)[])");
    PreparedStatement ps =
            m_conn.prepareStatement("INSERT INTO arrays_tbl VALUES(?, ?, ?, ?, ?, ?)");
    ps.setInt(1, 1);
    ps.setArray(2, m_conn.createArrayOf("INT", ia));
    ps.setArray(3, m_conn.createArrayOf("FLOAT", fa));
    ps.setArray(4, m_conn.createArrayOf("DECIMAL", da));
    ps.setArray(5, m_conn.createArrayOf("STR", sa));
    ps.setArray(6, m_conn.createArrayOf("TIMESTAMP", ta));
    ps.executeUpdate();

    ResultSet rs = stmt.executeQuery("SELECT i, ia, fa, da, sa, ta FROM arrays_tbl");
    assertTrue(rs.next());
    assertEquals(rs.getInt("i"), 1);
    assertArrayEquals((Integer[]) rs.getArray("ia").getArray(), ia);
    assertArrayEquals((Float[]) rs.getArray("fa").getArray(), fa);
    assertArrayEquals((BigDecimal[]) rs.getArray("da").getArray(), da);
    assertArrayEquals((String[]) rs.getArray("sa").getArray(), sa);
    assertArrayEquals((Timestamp[]) rs.getArray("ta").getArray(), ta);

    ps.close();
    stmt.executeUpdate("DROP TABLE arrays_tbl");
    stmt.close();
  }

  @Test
  public void illegal_arguments_test() throws Exception {
    try {
      m_conn.createArrayOf("BIGINT", ia);
      assertTrue("Should not be able to create BIGINT array from integers", false);
    } catch (SQLException e) {
    } catch (Exception ex) {
      assertTrue("Expected an SQLException", false);
    }

    try {
      m_conn.createArrayOf("not_a_type", ia);
      assertTrue("Should not be able to create an array without a valid type", false);
    } catch (SQLException e) {
    } catch (Exception ex) {
      assertTrue("Expected an SQLException", false);
    }

    try {
      m_conn.createArrayOf("BIGINT", null);
      assertTrue("Should not be able to create an array from null", false);
    } catch (SQLException e) {
    } catch (Exception ex) {
      assertTrue("Expected an SQLException", false);
    }

    Array ints = m_conn.createArrayOf("INT", ia);
    int sz = ia.length;
    try {
      ints.getArray(sz / 2, sz / 2 + 2);
      assertTrue("Should not be able to get array bigger than its initial size", false);
    } catch (SQLException e) {
    } catch (Exception ex) {
      assertTrue("Expected an SQLException", false);
    }
  }
}


<|start_filename|>src/main/java/ai/heavy/jdbc/HeavyAIEscapeFunctions.java<|end_filename|>
package ai.heavy.jdbc;

import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public final class HeavyAIEscapeFunctions {
  /**
   * storage for functions implementations
   */
  private static final ConcurrentMap<String, Method> FUNCTION_MAP =
          createFunctionMap("sql");

  private static ConcurrentMap<String, Method> createFunctionMap(String prefix) {
    Method[] methods = HeavyAIEscapeFunctions.class.getMethods();
    ConcurrentMap<String, Method> functionMap =
            new ConcurrentHashMap<String, Method>(methods.length * 2);
    for (Method method : methods) {
      if (method.getName().startsWith(prefix)) {
        functionMap.put(
                method.getName().substring(prefix.length()).toLowerCase(Locale.US),
                method);
      }
    }
    return functionMap;
  }

  /**
   * get Method object implementing the given function
   *
   * @param functionName name of the searched function
   * @return a Method object or null if not found
   */
  public static Method getFunction(String functionName) {
    Method method = FUNCTION_MAP.get(functionName);
    if (method != null) {
      return method;
    }
    // FIXME: this probably should not use the US locale
    String nameLower = functionName.toLowerCase(Locale.US);
    if (nameLower.equals(functionName)) {
      // Input name was in lower case, the function is not there
      return null;
    }
    method = FUNCTION_MAP.get(nameLower);
    if (method != null && FUNCTION_MAP.size() < 1000) {
      // Avoid OutOfMemoryError in case input function names are randomized
      // The number of methods is finite, however the number of upper-lower case
      // combinations is quite a few (e.g. substr, Substr, sUbstr, SUbstr, etc).
      FUNCTION_MAP.putIfAbsent(functionName, method);
    }
    return method;
  }

  // ** numeric functions translations **

  /**
   * ceiling to ceil translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqlceiling(StringBuilder buf,
          List<? extends CharSequence> parsedArgs) throws SQLException {
    singleArgumentFunctionCall(buf, "ceil(", "ceiling", parsedArgs);
  }

  /**
   * dayofmonth translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqldayofmonth(StringBuilder buf,
          List<? extends CharSequence> parsedArgs) throws SQLException {
    singleArgumentFunctionCall(buf, "extract(day from ", "dayofmonth", parsedArgs);
  }

  /**
   * dayofweek translation adding 1 to postgresql function since we expect values from 1
   * to 7
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqldayofweek(StringBuilder buf,
          List<? extends CharSequence> parsedArgs) throws SQLException {
    if (parsedArgs.size() != 1) {
      throw new RuntimeException(
              "Syntax error function 'dayofweek' takes one and only one argument.");
    }
    appendCall(buf, "extract(dow from ", ",", ")+1", parsedArgs);
  }

  /**
   * dayofyear translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqldayofyear(StringBuilder buf,
          List<? extends CharSequence> parsedArgs) throws SQLException {
    singleArgumentFunctionCall(buf, "extract(doy from ", "dayofyear", parsedArgs);
  }

  /**
   * hour translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqlhour(StringBuilder buf, List<? extends CharSequence> parsedArgs)
          throws SQLException {
    singleArgumentFunctionCall(buf, "extract(hour from ", "hour", parsedArgs);
  }

  /**
   * minute translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqlminute(StringBuilder buf, List<? extends CharSequence> parsedArgs)
          throws SQLException {
    singleArgumentFunctionCall(buf, "extract(minute from ", "minute", parsedArgs);
  }

  /**
   * month translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqlmonth(StringBuilder buf, List<? extends CharSequence> parsedArgs)
          throws SQLException {
    singleArgumentFunctionCall(buf, "extract(month from ", "month", parsedArgs);
  }

  /**
   * quarter translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqlquarter(StringBuilder buf,
          List<? extends CharSequence> parsedArgs) throws SQLException {
    singleArgumentFunctionCall(buf, "extract(quarter from ", "quarter", parsedArgs);
  }

  /**
   * second translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqlsecond(StringBuilder buf, List<? extends CharSequence> parsedArgs)
          throws SQLException {
    singleArgumentFunctionCall(buf, "extract(second from ", "second", parsedArgs);
  }

  /**
   * week translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqlweek(StringBuilder buf, List<? extends CharSequence> parsedArgs)
          throws SQLException {
    singleArgumentFunctionCall(buf, "extract(week from ", "week", parsedArgs);
  }

  /**
   * year translation
   *
   * @param buf The buffer to append into
   * @param parsedArgs arguments
   * @throws SQLException if something wrong happens
   */
  public static void sqlyear(StringBuilder buf, List<? extends CharSequence> parsedArgs)
          throws SQLException {
    singleArgumentFunctionCall(buf, "extract(year from ", "year", parsedArgs);
  }

  private static void singleArgumentFunctionCall(StringBuilder buf,
          String call,
          String functionName,
          List<? extends CharSequence> parsedArgs) {
    if (parsedArgs.size() != 1) {
      throw new RuntimeException(
              "Syntax error " + functionName + " takes one and only one argument.");
    }
    CharSequence arg0 = parsedArgs.get(0);
    buf.ensureCapacity(buf.length() + call.length() + arg0.length() + 1);
    buf.append(call).append(arg0).append(')');
  }

  /**
   * Appends {@code begin arg0 separator arg1 separator end} sequence to the input {@link
   * StringBuilder}
   * @param sb destination StringBuilder
   * @param begin begin string
   * @param separator separator string
   * @param end end string
   * @param args arguments
   */

  public static void appendCall(StringBuilder sb,
          String begin,
          String separator,
          String end,
          List<? extends CharSequence> args) {
    int size = begin.length();
    // Typically just-in-time compiler would eliminate Iterator in case foreach is used,
    // however the code below uses indexed iteration to keep the conde independent from
    // various JIT implementations (== avoid Iterator allocations even for not-so-smart
    // JITs) see https://bugs.openjdk.java.net/browse/JDK-8166840 see
    // http://2016.jpoint.ru/talks/cheremin/ (video and slides)
    int numberOfArguments = args.size();
    for (int i = 0; i < numberOfArguments; i++) {
      size += args.get(i).length();
    }
    size += separator.length() * (numberOfArguments - 1);
    sb.ensureCapacity(sb.length() + size + 1);
    sb.append(begin);
    for (int i = 0; i < numberOfArguments; i++) {
      if (i > 0) {
        sb.append(separator);
      }
      sb.append(args.get(i));
    }
    sb.append(end);
  }
}


<|start_filename|>src/main/java/ai/heavy/jdbc/HeavyAIEscapeParser.java<|end_filename|>
package ai.heavy.jdbc;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class HeavyAIEscapeParser {
  private static final char[] QUOTE_OR_ALPHABETIC_MARKER = {'\"', '0'};
  // private static final char[] QUOTE_OR_ALPHABETIC_MARKER_OR_PARENTHESIS = {'\"', '0',
  // '('};
  private static final char[] SINGLE_QUOTE = {'\''};

  private enum EscapeFunctions {
    ESC_FUNCTION("\\s*fn\\s+([^ ]*?)\\s*\\(", "\\(\\s*(.*)\\s*\\)", null),
    ESC_DATE("\\s*(d)\\s+", "('.*?')", "DATE "),
    ESC_TIME("\\s*(t)\\s+", "('.*?')", "TIME "),
    ESC_TIMESTAMP("\\s*(ts)\\s+", "('.*?')", "TIMESTAMP ");

    EscapeFunctions(String escapePattern, String argPattern, String replacementKeyword) {
      this.escapePattern = Pattern.compile(escapePattern);
      this.argPattern = Pattern.compile(argPattern);
      this.replacementKeyword = replacementKeyword;
    }

    private final Pattern escapePattern;
    private final Pattern argPattern;
    private final String replacementKeyword;

    private String call_escape_fn(String all_arguments, Method method) {
      try {
        StringBuilder sb = new StringBuilder();
        List<CharSequence> parseArgs = new ArrayList<CharSequence>(3);
        int bracket_cnt = 0;
        StringBuilder args_sb = new StringBuilder();
        for (int index = 0; index < all_arguments.length(); ++index) {
          if (all_arguments.charAt(index) == ',' && bracket_cnt == 0) {
            parseArgs.add(args_sb.toString());
            args_sb.delete(0, args_sb.length());
            continue;
          }
          if (all_arguments.charAt(index) == '(') {
            bracket_cnt++;
          } else if (all_arguments.charAt(index) == ')') {
            bracket_cnt--;
          }
          args_sb.append(all_arguments.charAt(index));
        }
        // add the last argument.
        parseArgs.add(args_sb.toString());
        method.invoke(null, sb, parseArgs);
        return sb.toString();
      } catch (InvocationTargetException e) {
        throw new RuntimeException("Invocation fn argument parse error" + e.getMessage());
      } catch (IllegalAccessException ilE) {
        throw new RuntimeException("Access  fn argument error" + ilE.getMessage());
      }
    }

    private String makeMatch(String sql) {
      Matcher matcher = escapePattern.matcher(sql);
      if (matcher.find()) {
        if (this == EscapeFunctions.ESC_DATE || this == EscapeFunctions.ESC_TIME
                || this == EscapeFunctions.ESC_TIMESTAMP) {
          matcher = argPattern.matcher(sql);
          if (matcher.find()) {
            String new_sql = this.replacementKeyword + matcher.group(1);
            return new_sql;
          }
        } else if (this == EscapeFunctions.ESC_FUNCTION) {
          String fn_name = matcher.group(1);
          Method method = HeavyAIEscapeFunctions.getFunction(fn_name);
          matcher = argPattern.matcher(sql);
          if (matcher.find()) {
            if (method == null) {
              String new_sql = fn_name + '(' + matcher.group(1) + ')';
              return new_sql;
            } else {
              return call_escape_fn(matcher.group(1), method);
            }
          }
        }
      }
      return null;
    }
    public static String simple(String arg) {
      return arg;
    }

    public static String function(String arg) {
      return arg;
    }
  }

  private static class Pair {
    public int start;
    public int end;

    public Pair(int s) {
      start = s;
    }
  }
  private static String process_sql(String sql, Pair index) {
    String value = sql.substring(index.start, index.end);

    boolean found_match = false;
    for (EscapeFunctions xx : EscapeFunctions.values()) {
      String newsql = xx.makeMatch(value);
      if (newsql != null) {
        sql = sql.substring(0, index.start) + newsql + " "
                + sql.substring(index.end + 1, sql.length());
        int x = newsql.length();
        index.end = index.start + newsql.length();
        found_match = true;
        break;
      }
    }
    if (!found_match) {
      // it's an array. Prepend '{' here, because parser will remove the original brace.
      sql = sql.substring(0, index.start) + "{" + sql.substring(index.start);
      index.end += 1;
    }
    return sql;
  }
  public static String parse(String sql) {
    Parser_return pR = parse(sql, 0);
    if (pR.bracket_cnt != 0) {
      throw new RuntimeException("Invalid java escape syntax - badly matched '}'");
    }
    return pR.sql_value;
  }
  static class Parser_return {
    public String sql_value;
    public int bracket_cnt;
    public int end_idx;
  }
  private static Parser_return parse(String sql, int bracket_cnt) {
    int index = 0;
    boolean in_quote = false;

    do {
      if (sql.charAt(index) == '\'') {
        in_quote = !in_quote;
      } else if (sql.charAt(index) == '{' && !in_quote) {
        if (index + 1 == sql.length()) {
          // What ever string we get should have had limit nnn
          // added to the end and this test is veru unlikely
          throw new RuntimeException("Invalid java escape syntax - badly matched '{'");
        }
        Parser_return pR = parse(sql.substring(index + 1), ++bracket_cnt);
        bracket_cnt = pR.bracket_cnt;
        String sql_snippet = pR.sql_value;
        sql = sql.substring(0, index) + " " + sql_snippet;
        index += pR.end_idx;
      } else if (sql.charAt(index) == '}' && !in_quote) {
        Pair ptr = new Pair(0);
        ptr.end = index;
        Parser_return pR = new Parser_return();
        pR.sql_value = process_sql(sql, ptr);
        pR.bracket_cnt = --bracket_cnt;
        pR.end_idx = ptr.end + 1;
        return pR;
      }
      index++;
    } while (index < sql.length());
    if (in_quote) {
      throw new RuntimeException("Invalid java escape syntax - badly matched '''");
    }

    Parser_return pR = new Parser_return();
    pR.sql_value = sql;
    pR.bracket_cnt = bracket_cnt;
    pR.end_idx = sql.length();
    return pR;
  }
}


<|start_filename|>src/test/java/ai/heavy/jdbc/HeavyAIStatementTest.java<|end_filename|>
package ai.heavy.jdbc;

import static org.junit.Assert.*;

import static java.lang.Math.ulp;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.sql.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Supplier;

public class HeavyAIStatementTest {
  static Properties PROPERTIES = new Property_loader("connection_test.properties");
  static final String url = PROPERTIES.getProperty("default_db_connection_url");
  static final String user = PROPERTIES.getProperty("default_super_user");
  static final String password = PROPERTIES.getProperty("default_user_password");

  private Connection m_conn = null;

  @Before
  public void setUp() throws Exception {
    Properties pt = new Properties();
    pt.setProperty("user", user);
    pt.setProperty("password", password);
    m_conn = DriverManager.getConnection(url, pt);
  }

  @After
  public void tearDown() throws Exception {
    m_conn.close();
  }

  static String sql_drop_tbl_tm = "drop table if exists test_jdbc_tm_tble";

  static String sql_create_tbl_tm = "CREATE table test_jdbc_tm_tble("
          + "m_timestamp TIMESTAMP,"
          + "m_timestamp_3 TIMESTAMP(3),"
          + "m_timestamp_6 TIMESTAMP(6),"
          + "m_timestamp_9 TIMESTAMP(9))";

  static String sql_insert_tm_1 =
          "insert into test_jdbc_tm_tble values ('1910-01-01 00:00:10', '1910-01-01 00:00:10.001', '1910-01-01 00:00:10.555556', '1910-01-01 00:00:10.999999999')";

  static String sql_insert_tm_2 =
          "insert into test_jdbc_tm_tble values ('1969-12-31 23:00:00', '1969-12-31 23:00:00.001', '1969-12-31 23:00:00.000001', '1969-12-31 23:00:00.000000001')";

  static String sql_insert_tm_3 =
          "insert into test_jdbc_tm_tble values ('1970-01-01 00:00:10', '1970-01-01 00:00:10.001', '1970-01-01 00:00:10.000001', '1970-01-01 00:00:10.000000001')";

  static String insert_prepare_tm =
          "insert into test_jdbc_tm_tble (m_timestamp, m_timestamp_3, m_timestamp_6, m_timestamp_9) values (?, ?, ?, ?)";

  // Note 2262-04-11 23:47:16.85 is very close to the limit for Timestamp(9)
  static String sql_insert_tm_4 =
          "insert into test_jdbc_tm_tble values ('2970-01-01 00:00:10', '2970-01-01 00:00:10.001', '2970-01-01 00:00:10.000001', '2262-04-11 23:47:16.850000001')";
  static String sql_select_tm = "select * from test_jdbc_tm_tble";

  @Test
  public void insert_times() throws Exception {
    Statement statement = m_conn.createStatement();
    statement.executeUpdate(sql_drop_tbl_tm);
    statement.executeUpdate(sql_create_tbl_tm);

    TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
    DateFormat date_format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");

    java.util.Date date_tm = date_format.parse("1918-11-11 11:11:00.000");
    Timestamp tm = new Timestamp(date_tm.getTime());

    date_tm = date_format.parse("1918-11-11 11:11:00.001");
    Timestamp tm_3 = new Timestamp(date_tm.getTime());

    date_tm = date_format.parse("1918-11-11 11:11:00.000");
    Timestamp tm_6 = new Timestamp(date_tm.getTime());
    tm_6.setNanos(999999000);

    date_tm = date_format.parse("1918-11-11 11:11:00.000");
    Timestamp tm_9 = new Timestamp(date_tm.getTime());
    tm_9.setNanos(123456789);

    PreparedStatement pr = m_conn.prepareStatement(insert_prepare_tm);
    pr.setTimestamp(1, tm);
    pr.setTimestamp(2, tm_3);
    pr.setTimestamp(3, tm_6);
    pr.setTimestamp(4, tm_9);

    pr.executeUpdate();

    ResultSet rs = statement.executeQuery(sql_select_tm);
    while (rs.next()) {
      Timestamp r_tm = rs.getTimestamp("m_timestamp");
      assertTrue(r_tm.equals(tm));
      Timestamp r_tm3 = rs.getTimestamp("m_timestamp_3");
      assertTrue(r_tm3.equals(tm_3));
      Timestamp r_tm6 = rs.getTimestamp("m_timestamp_6");
      assertTrue(r_tm6.equals(tm_6));
      Timestamp r_tm9 = rs.getTimestamp("m_timestamp_9");
      assertTrue(r_tm9.equals(tm_9));
    }

    statement.executeUpdate(sql_drop_tbl_tm);
  }

  @Test
  public void create_times() throws Exception {
    Statement statement = m_conn.createStatement();
    statement.executeUpdate(sql_drop_tbl_tm);
    statement.executeUpdate(sql_create_tbl_tm);

    statement.executeUpdate(sql_insert_tm_1);
    statement.executeUpdate(sql_insert_tm_2);
    statement.executeUpdate(sql_insert_tm_3);
    statement.executeUpdate(sql_insert_tm_4);
    ResultSet rs = statement.executeQuery(sql_select_tm);

    for (int i = 0; rs.next(); ++i) {
      TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
      Timestamp timestamp = rs.getTimestamp("m_timestamp");

      Timestamp timestamp_3 = rs.getTimestamp("m_timestamp_3");
      Timestamp timestamp_6 = rs.getTimestamp("m_timestamp_6");
      Timestamp timestamp_9 = rs.getTimestamp("m_timestamp_9");
      if (i == 0) {
        assertEquals("1910-01-01 00:00:10.0", timestamp.toString());
        assertEquals("1910-01-01 00:00:10.001", timestamp_3.toString());
        assertEquals("1910-01-01 00:00:10.555556", timestamp_6.toString());
        assertEquals("1910-01-01 00:00:10.999999999", timestamp_9.toString());
      }
      if (i == 1) {
        assertEquals("1969-12-31 23:00:00.0", timestamp.toString());
        assertEquals("1969-12-31 23:00:00.001", timestamp_3.toString());
        assertEquals("1969-12-31 23:00:00.000001", timestamp_6.toString());
        assertEquals("1969-12-31 23:00:00.000000001", timestamp_9.toString());
      }
      if (i == 2) {
        assertEquals("1970-01-01 00:00:10.0", timestamp.toString());
        assertEquals("1970-01-01 00:00:10.001", timestamp_3.toString());
        assertEquals("1970-01-01 00:00:10.000001", timestamp_6.toString());
        assertEquals("1970-01-01 00:00:10.000000001", timestamp_9.toString());
      }
      if (i == 3) {
        assertEquals("2970-01-01 00:00:10.0", timestamp.toString());
        assertEquals("2970-01-01 00:00:10.001", timestamp_3.toString());
        assertEquals("2970-01-01 00:00:10.000001", timestamp_6.toString());
        assertEquals("2262-04-11 23:47:16.850000001", timestamp_9.toString());
      }
    }

    statement.executeUpdate(sql_drop_tbl_tm);
  }

  static String sql_drop_tbl = "drop table if exists test_jdbc_types_tble";

  static String sql_create_tbl = "CREATE table test_jdbc_types_tble("
          + "m_decimal DECIMAL(8,3),"
          + "m_int int,"
          + "m_float float,"
          + "m_double double,"
          + "m_bigint BIGINT,"
          + "m_smallint SMALLINT,"
          + "m_tinyint TINYINT,"
          + "m_boolean BOOLEAN,"
          + "m_text_encoded TEXT ENCODING DICT,"
          + "m_text_encoded_none TEXT ENCODING NONE,"
          + "m_time TIME,"
          + "m_date DATE,"
          + "m_timestamp TIMESTAMP)";

  static String sql_insert = "insert into test_jdbc_types_tble values ("
          + "12345.123" + +Integer.MAX_VALUE + "," + Integer.MAX_VALUE + ","
          + Float.MAX_VALUE + "," + Double.MAX_VALUE + "," + Long.MAX_VALUE + ","
          + Short.MAX_VALUE + "," + Byte.MAX_VALUE + ","
          + "\'0\',"
          + "'String 1 - encoded', 'String 2 - not encoded', '00:00:00', '1970-01-01', '1970-01-01 00:00:00')";

  static String sql_select_all = "select * from test_jdbc_types_tble";

  @Test
  public void escape_function1() throws Exception {
    Statement statement = m_conn.createStatement();
    TimeZone.setDefault(TimeZone.getTimeZone("GMT"));

    // Test multiple nested fn {}
    String d_select =
            "select {fn cos(1)} as m_cos, {d '1999-01-09'} as m_date, {t '20:00:03'} as m_time, {ts '1990-01-09 20:00:03'} as m_timestamp, {fn week({d '2005-01-24'})} as m_week";
    ResultSet rs = statement.executeQuery(d_select);
    for (int i = 0; rs.next(); ++i) {
      Date r_date = rs.getDate("m_date");
      assertEquals("1999-01-09", r_date.toString());
      Time r_time = rs.getTime("m_time");
      assertEquals("20:00:03", r_time.toString());
      Timestamp ts_time = rs.getTimestamp("m_timestamp");
      assertEquals("1990-01-09 20:00:03.0", ts_time.toString());
      double m_cos = rs.getDouble("m_cos");
      assertEquals(Double.compare(m_cos, 0.5403023058681398), 0);
      int m_week = rs.getInt("m_week");
      assertEquals(m_week, 4);
    }

    d_select = "select  {fn FLOOR(-1 * {fn dayofmonth({d '1990-01-31'})})} as WWW";
    rs = statement.executeQuery(d_select);
    for (int i = 0; rs.next(); ++i) {
      int www = rs.getInt("WWW");
      assertEquals(-31, www);
    }
    d_select =
            "select  {fn FLOOR(-1 * {fn dayofmonth(cast('1990-01-31' as date))})} as XXX";
    rs = statement.executeQuery(d_select);
    for (int i = 0; rs.next(); ++i) {
      int xxx = rs.getInt("XXX");
      assertEquals(-31, xxx);
    }

    d_select = "select  {fn floor(1.005)} as YYY limit 1000 {";
    try {
      statement.executeQuery(d_select);
      assertTrue(false);
    } catch (RuntimeException rE) {
    }

    d_select = "select ' {fn floor(1.005)} as YYY limit 1000 {";
    try {
      statement.executeQuery(d_select);
      assertTrue(false);
    } catch (RuntimeException rE) {
    }

    d_select = "select ' {fn floor(1.005)} as YYY limit 1000 }";
    try {
      statement.executeQuery(d_select);
      assertTrue(false);
    } catch (RuntimeException rE) {
    }
  }
  @Test
  public void escape_function2() throws Exception {
    Statement statement = m_conn.createStatement();
    TimeZone.setDefault(TimeZone.getTimeZone("GMT"));

    String sql_drop_tester = "drop table if exists tester";

    statement.executeUpdate(sql_drop_tester);
    String sql_create_tester =
            "CREATE table tester(Connection_start TIMESTAMP, d_start DATE)";
    statement.executeUpdate(sql_create_tester);

    String sql_insert_tester1 =
            "insert into tester values ('2018-11-08 12:19:59', '2018-11-08')";
    statement.executeUpdate(sql_insert_tester1);

    // test multiple embedded {} with TIMESTAMPSIDD.
    String e_statement =
            "select COUNT( {fn TIMESTAMPDIFF(SQL_TSI_DAY, {fn TIMESTAMPADD(SQL_TSI_DAY,CAST((-1 * (EXTRACT(DAY FROM tester.d_start) - 1)) AS INTEGER),CAST( tester.d_start AS DATE))}, {fn TIMESTAMPADD(SQL_TSI_DAY,CAST((-1 * (EXTRACT(DAY FROM {fn TIMESTAMPADD(SQL_TSI_MONTH,1, tester.d_start)}) - 1)) AS INTEGER),CAST( {fn TIMESTAMPADD(SQL_TSI_MONTH,1, tester.d_start)} AS DATE)) }) }) AS TEMP from  tester";
    ResultSet rs = statement.executeQuery(e_statement);
    rs = statement.executeQuery(e_statement);
    for (int i = 0; rs.next(); ++i) {
      int r_count = rs.getInt("TEMP");
      assertEquals(r_count, 1);
    }
    // test TOP filter.
    String x_select =
            "SELECT TOP 1000 sum(1) AS sum_Number_of_Records_ok, {fn TIMESTAMPADD(SQL_TSI_HOUR, EXTRACT(HOUR FROM tester.Connection_Start), CAST(tester.Connection_Start as DATE))} AS thr_Connection_Start_ok FROM tester Where ((tester.Connection_Start >= {ts '2018-11-01 00:00:00'}) AND (tester.Connection_Start <= {ts '2018-11-08 23:59:59'})) GROUP BY 2";
    rs = statement.executeQuery(x_select);
    for (int i = 0; rs.next(); ++i) {
      int r_count = rs.getInt("sum_Number_of_Records_ok");
      assertEquals(r_count, 1);
      Timestamp ts_time = rs.getTimestamp("thr_Connection_Start_ok");
      assertEquals(ts_time.toString(), "2018-11-08 12:00:00.0");
    }

    // Test the simple date transformation in HeavyDBStatment.
    String d_simple_quarter = "select quarter(Connection_start) as m_quarter from tester";
    rs = statement.executeQuery(d_simple_quarter);
    for (int i = 0; rs.next(); ++i) {
      int r_quarter = rs.getInt("m_quarter");
      assertEquals(4, r_quarter);
    }
    d_simple_quarter =
            "select quarter(cast('2019-04-03' as date)) as m_quarter from tester";
    rs = statement.executeQuery(d_simple_quarter);
    for (int i = 0; rs.next(); ++i) {
      int r_quarter = rs.getInt("m_quarter");
      assertEquals(2, r_quarter);
    }
  }

  @Test
  public void escape_function3() throws Exception {
    Statement statement = m_conn.createStatement();
    TimeZone.setDefault(TimeZone.getTimeZone("GMT"));

    // ceil
    String d_ceiling = "select  {fn ceiling(1.005)} as YYY";
    ResultSet rs = statement.executeQuery(d_ceiling);
    for (int i = 0; rs.next(); ++i) {
      float yyy = rs.getFloat("YYY");
      assertEquals(Float.compare(2.0F, yyy), 0);
    }

    // floor
    String d_floor = "select  {fn floor(1.005)} as YYY";
    rs = statement.executeQuery(d_floor);
    for (int i = 0; rs.next(); ++i) {
      float yyy = rs.getFloat("YYY");
      assertEquals(Float.compare(1.0F, yyy), 0);
    }

    // ln
    String d_ln = "select {fn log(10)} as m_ln";
    rs = statement.executeQuery(d_ln);
    for (int i = 0; rs.next(); ++i) {
      double r_ln = rs.getDouble("m_ln");
      assertEquals(Double.compare(r_ln, 2.302585092994046), 0);
    }
    // ln
    d_ln = "select {fn ln(10)} as m_ln";
    rs = statement.executeQuery(d_ln);
    for (int i = 0; rs.next(); ++i) {
      double r_ln = rs.getDouble("m_ln");
      assertEquals(Double.compare(r_ln, 2.302585092994046), 0);
    }

    // log10
    String d_log10 = "select {fn log10(10)} as m_log10";
    rs = statement.executeQuery(d_log10);
    for (int i = 0; rs.next(); ++i) {
      double r_log10 = rs.getDouble("m_log10");
      assertEquals(Double.compare(r_log10, 1), 0);
    }

    // abs
    String d_abs = "select {fn abs(-1.45)} as m_abs";
    rs = statement.executeQuery(d_abs);
    for (int i = 0; rs.next(); ++i) {
      float r_abs = rs.getFloat("m_abs");
      assertEquals(Float.compare(r_abs, (float) Math.abs(-1.45)), 0);
    }

    // power
    String d_power = "select  {fn power(2,10)} as YYY";
    rs = statement.executeQuery(d_power);
    for (int i = 0; rs.next(); ++i) {
      float yyy = rs.getFloat("YYY");
      assertEquals(Float.compare((float) Math.pow(2, 10), yyy), 0);
    }
    // truncate
    String d_truncate = "select  {fn truncate(2.01,1)} as YYY";
    rs = statement.executeQuery(d_truncate);
    for (int i = 0; rs.next(); ++i) {
      float yyy = rs.getFloat("YYY");
      assertEquals(Float.compare(2.0F, yyy), 0);
    }

    // length
    String d_length = "select  {fn length('12345')} as YYY";
    rs = statement.executeQuery(d_length);
    for (int i = 0; rs.next(); ++i) {
      int yyy = rs.getInt("YYY");
      assertEquals(5, yyy);
    }

    // quarter
    String d_quarter = "select {fn quarter({d '2005-01-24'})} as m_quarter";
    rs = statement.executeQuery(d_quarter);
    for (int i = 0; rs.next(); ++i) {
      int r_quarter = rs.getInt("m_quarter");
      assertEquals(1, r_quarter);
    }

    // day of year
    String d_dayofyear = "select {fn DAYOFYEAR({d '2005-01-24'})} as m_dayofyear";
    rs = statement.executeQuery(d_dayofyear);
    for (int i = 0; rs.next(); ++i) {
      int r_dayofyear = rs.getInt("m_dayofyear");
      assertEquals(24, r_dayofyear);
    }

    // day of week
    String d_dayofweek = "select {fn dayofweek({d '2005-01-24'})} as m_dayofweek";
    rs = statement.executeQuery(d_dayofweek);
    for (int i = 0; rs.next(); ++i) {
      int r_dayofweek = rs.getInt("m_dayofweek");
      assertEquals(2, r_dayofweek);
    }
    // day of month
    String d_dayofmonth = "select {fn dayofmonth({d '2005-01-24'})} as m_dayofmonth";
    rs = statement.executeQuery(d_dayofmonth);
    for (int i = 0; rs.next(); ++i) {
      int r_dayofmonth = rs.getInt("m_dayofmonth");
      assertEquals(24, r_dayofmonth);
    }
    // hour
    String d_hour = "select {fn hour({ts '2005-01-24 10:11:12'})} as m_hour";
    rs = statement.executeQuery(d_hour);
    for (int i = 0; rs.next(); ++i) {
      int r_hour = rs.getInt("m_hour");
      assertEquals(10, r_hour);
    }

    // current date
    String d_c =
            "select {fn TIMESTAMPADD(SQL_TSI_DAY, 1, CURRENT_DATE())} m, TIMESTAMPADD(SQL_TSI_WEEK, 1, CURRENT_DATE) y";
    ResultSet rx = statement.executeQuery(d_c);
    for (int i = 0; rx.next(); ++i) {
      Date r_m = rx.getDate("m");
      Date r_y = rx.getDate("y");
    }

    // current date
    d_c = "select TIMESTAMPADD(SQL_TSI_DAY, 1, CURRENT_DATE) as m_zzz";
    rs = statement.executeQuery(d_c);
    for (int i = 0; rs.next(); ++i) {
      Timestamp r_zzz = rs.getTimestamp("m_zzz");
    }
    // current date
    d_c = "select TIMESTAMPADD(SQL_TSI_DAY, 1, CURRENT_DATE_TIME) as m_zzz";
    try {
      rs = statement.executeQuery(d_c);
      assertTrue(false);
    } catch (SQLException sE) {
      assertTrue(sE.toString().contains("CURRENT_DATE_TIME' not found in any table"));
    } catch (Exception ex) {
      assertTrue(false);
    }
  }

  @Test
  public void create_types() throws Exception {
    Statement statement = m_conn.createStatement();
    statement.executeQuery(sql_drop_tbl);
    statement.executeUpdate(sql_create_tbl);
    statement.executeUpdate(sql_insert);
    statement.executeUpdate(sql_insert);
    ResultSet rs = statement.executeQuery(sql_select_all);

    int i = 0;
    for (; rs.next(); ++i) {
      int r_int = rs.getInt("m_int");
      assertEquals(Integer.MAX_VALUE, r_int);
      float r_float = rs.getFloat("m_float");
      float delta_f = ulp(Float.MAX_VALUE);
      assertEquals(Float.MAX_VALUE, r_float, delta_f);

      double r_double = rs.getDouble("m_double");
      double delta_d = ulp(Double.MAX_VALUE);
      assertEquals(Double.MAX_VALUE, r_double, delta_d);

      long r_long = rs.getLong("m_bigint");
      assertEquals(Long.MAX_VALUE, r_long);

      short r_short = rs.getShort("m_smallint");
      assertEquals(Short.MAX_VALUE, r_short);

      byte r_byte = (byte) rs.getShort("m_tinyint");
      assertEquals(Byte.MAX_VALUE, r_byte);

      String decimal_str = rs.getString("m_decimal");
      assertEquals("12345.123", decimal_str);

      // byte r_boolean = rs.getByte("m_boolean"); Not supported!
      byte r_boolean = (byte) rs.getShort("m_boolean");
      assertEquals(0, r_boolean);

      String r_text_encoded = rs.getString("m_text_encoded");
      assertEquals("String 1 - encoded", r_text_encoded);

      String r_text_encoded_none = rs.getString("m_text_encoded_none");
      assertEquals("String 2 - not encoded", r_text_encoded_none);

      // Set the tz to GMT to help with compares
      TimeZone.setDefault(TimeZone.getTimeZone("GMT"));

      Timestamp r_timestamp = rs.getTimestamp("m_timestamp");
      assertEquals("1970-01-01 00:00:00.0", r_timestamp.toString());

      Date r_date = rs.getDate("m_date");
      assertEquals("1970-01-01", r_date.toString());

      Time r_time = rs.getTime("m_time");
      assertEquals("00:00:00", r_time.toString());
    }

    assertEquals(2, i);

    statement.executeUpdate(sql_drop_tbl);
  }

  @Test
  public void cancelTest1() throws Exception {
    Statement statement = m_conn.createStatement();
    statement.executeUpdate(sql_drop_tbl);
    statement.executeUpdate(sql_create_tbl);
    statement.executeUpdate(sql_insert);

    Supplier<Boolean> B = () -> {
      try {
        Thread.sleep(1);
        statement.cancel();
      } catch (SQLException | InterruptedException e) {
        e.printStackTrace();
        return false;
      }
      return true;
    };
    Future<Boolean> result = CompletableFuture.supplyAsync(B);
    ResultSet rs = statement.executeQuery(sql_select_all);
    int size = 0;
    while (rs.next()) {
      size++;
    }
    assertEquals(size, 1);
    // To verify the main connection is still functioning
    // after the cancel command.
    statement.executeUpdate(sql_insert);
    rs = statement.executeQuery(sql_select_all);

    size = 0;
    while (rs.next()) {
      size++;
    }
    assertEquals(size, 2);
    statement.executeUpdate(sql_drop_tbl);
  }

  private int resultSetSize(ResultSet rs) throws SQLException {
    int sz = 0;
    while (rs.next()) {
      sz++;
    }
    return sz;
  }

  @Test
  public void maxRowsTest() throws Exception {
    Statement statement = m_conn.createStatement();
    statement.executeUpdate("DROP TABLE IF EXISTS max_rows_tbl");
    statement.executeUpdate("CREATE TABLE max_rows_tbl (i INTEGER)");
    statement.executeUpdate("INSERT INTO max_rows_tbl VALUES (1)");
    statement.executeUpdate("INSERT INTO max_rows_tbl VALUES (2)");
    statement.executeUpdate("INSERT INTO max_rows_tbl VALUES (3)");
    ResultSet rs = statement.executeQuery("SELECT * FROM max_rows_tbl");
    assertEquals(3, resultSetSize(rs));
    statement.setMaxRows(1);
    rs = statement.executeQuery("SELECT * FROM max_rows_tbl");
    assertEquals(1, resultSetSize(rs));
    statement.close();

    Properties pt = new Properties();
    pt.setProperty("user", user);
    pt.setProperty("password", password);
    pt.setProperty("max_rows", "2");
    Connection limited_conn = DriverManager.getConnection(url, pt);
    statement = limited_conn.createStatement();
    rs = statement.executeQuery("SELECT * FROM max_rows_tbl");
    assertEquals(2, resultSetSize(rs));
    statement.setMaxRows(1);
    rs = statement.executeQuery("SELECT * FROM max_rows_tbl");
    assertEquals(1, resultSetSize(rs));
    statement.close();
    limited_conn.close();
  }
}
