From 0af2eaa0cdc80c706cda167e9172934664157c6a Mon Sep 17 00:00:00 2001 From: Hemanth Umashankar Date: Wed, 8 Jul 2026 16:12:17 +0530 Subject: [PATCH 1/2] HIVE-26441: Expand GenericJdbcDatabaseAccessor unit test coverage --- .../dao/TestGenericJdbcDatabaseAccessor.java | 266 ++++++++++++++++++ .../src/test/resources/test_script.sql | 7 + 2 files changed, 273 insertions(+) diff --git a/jdbc-handler/src/test/java/org/apache/hive/storage/jdbc/dao/TestGenericJdbcDatabaseAccessor.java b/jdbc-handler/src/test/java/org/apache/hive/storage/jdbc/dao/TestGenericJdbcDatabaseAccessor.java index 98888ecd045e..6b7661cfaacb 100644 --- a/jdbc-handler/src/test/java/org/apache/hive/storage/jdbc/dao/TestGenericJdbcDatabaseAccessor.java +++ b/jdbc-handler/src/test/java/org/apache/hive/storage/jdbc/dao/TestGenericJdbcDatabaseAccessor.java @@ -14,16 +14,24 @@ */ package org.apache.hive.storage.jdbc.dao; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.serde.serdeConstants; import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo; import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory; +import org.apache.hadoop.mapreduce.RecordWriter; +import org.apache.hadoop.mapreduce.TaskAttemptContext; +import org.apache.hive.storage.jdbc.DBRecordWritable; import org.apache.hive.storage.jdbc.conf.JdbcStorageConfig; import org.apache.hive.storage.jdbc.exception.HiveJdbcDatabaseAccessException; import org.junit.Assert; import org.junit.Test; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -31,8 +39,11 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TestGenericJdbcDatabaseAccessor { @@ -212,6 +223,29 @@ public void testGetRecordIterator_offsets() throws HiveJdbcDatabaseAccessExcepti } + @Test + public void testGetRecordIterator_limitOffsetLastPartition() throws HiveJdbcDatabaseAccessException { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + JdbcRecordIterator iterator = accessor.getRecordIterator(conf, null, null, null, 2, 4); + + assertThat(iterator, is(notNullValue())); + + int count = 0; + while (iterator.hasNext()) { + Map record = iterator.next(); + count++; + + assertThat(record, is(notNullValue())); + assertThat(record.size(), is(equalTo(7))); + assertThat(record.get("strategy_id"), is(equalTo(5))); + } + + assertThat(count, is(equalTo(1))); + iterator.close(); + } + + @Test public void testGetRecordIterator_emptyResultSet() throws HiveJdbcDatabaseAccessException { Configuration conf = buildConfiguration(); @@ -225,6 +259,91 @@ public void testGetRecordIterator_emptyResultSet() throws HiveJdbcDatabaseAccess } + @Test + public void testGetRecordIterator_partitionFirst() throws HiveJdbcDatabaseAccessException { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + JdbcRecordIterator iterator = + accessor.getRecordIterator(conf, getStrategyIdColumnName(conf, accessor), null, "3", -1, 0); + + assertThat(iterator, is(notNullValue())); + + int count = 0; + while (iterator.hasNext()) { + Map record = iterator.next(); + count++; + + assertThat(record, is(notNullValue())); + assertThat(record.size(), is(equalTo(7))); + assertThat(record.get("strategy_id"), is(equalTo(count))); + } + + assertThat(count, is(equalTo(2))); + iterator.close(); + } + + + @Test + public void testGetRecordIterator_partitionMiddle() throws HiveJdbcDatabaseAccessException { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + JdbcRecordIterator iterator = + accessor.getRecordIterator(conf, getStrategyIdColumnName(conf, accessor), "3", "5", -1, 0); + + assertThat(iterator, is(notNullValue())); + + int count = 0; + while (iterator.hasNext()) { + Map record = iterator.next(); + count++; + + assertThat(record, is(notNullValue())); + assertThat(record.size(), is(equalTo(7))); + assertThat(record.get("strategy_id"), is(equalTo(count + 2))); + } + + assertThat(count, is(equalTo(2))); + iterator.close(); + } + + + @Test + public void testGetRecordIterator_partitionLast() throws HiveJdbcDatabaseAccessException { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + JdbcRecordIterator iterator = + accessor.getRecordIterator(conf, getStrategyIdColumnName(conf, accessor), "5", null, -1, 0); + + assertThat(iterator, is(notNullValue())); + + int count = 0; + while (iterator.hasNext()) { + Map record = iterator.next(); + count++; + + assertThat(record, is(notNullValue())); + assertThat(record.size(), is(equalTo(7))); + assertThat(record.get("strategy_id"), is(equalTo(5))); + } + + assertThat(count, is(equalTo(1))); + iterator.close(); + } + + + @Test + public void testGetRecordIterator_partitionEmptyResultSet() throws HiveJdbcDatabaseAccessException { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + JdbcRecordIterator iterator = + accessor.getRecordIterator(conf, getStrategyIdColumnName(conf, accessor), "6", "7", -1, 0); + + assertThat(iterator, is(notNullValue())); + assertThat(iterator.hasNext(), is(false)); + iterator.close(); + } + + @Test public void testGetRecordIterator_largeOffset() throws HiveJdbcDatabaseAccessException { Configuration conf = buildConfiguration(); @@ -246,6 +365,153 @@ public void testGetRecordIterator_invalidQuery() throws HiveJdbcDatabaseAccessEx JdbcRecordIterator iterator = accessor.getRecordIterator(conf, null, null, null, 0, 2); } + @Test + public void testGetBounds_minAndMax() throws HiveJdbcDatabaseAccessException { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + Pair bounds = accessor.getBounds(conf, getStrategyIdColumnName(conf, accessor), true, true); + + assertThat(bounds.getLeft(), is(equalTo("1"))); + assertThat(bounds.getRight(), is(equalTo("5"))); + } + + @Test + public void testGetBounds_minOnly() throws HiveJdbcDatabaseAccessException { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + Pair bounds = accessor.getBounds(conf, getStrategyIdColumnName(conf, accessor), true, false); + + assertThat(bounds.getLeft(), is(equalTo("1"))); + assertThat(bounds.getRight(), is(nullValue())); + } + + @Test + public void testGetBounds_maxOnly() throws HiveJdbcDatabaseAccessException { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + Pair bounds = accessor.getBounds(conf, getStrategyIdColumnName(conf, accessor), false, true); + + assertThat(bounds.getLeft(), is(nullValue())); + assertThat(bounds.getRight(), is(equalTo("5"))); + } + + @Test + public void testNeedColumnQuote_genericAccessor() { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + + assertThat(accessor.needColumnQuote(), is(true)); + } + + @Test + public void testNeedColumnQuote_mysqlAccessor() { + Configuration conf = buildConfiguration(); + conf.set(JdbcStorageConfig.DATABASE_TYPE.getPropertyName(), "MYSQL"); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + + assertThat(accessor.needColumnQuote(), is(false)); + } + + @Test + public void testClose_afterInitializedAccessor() throws Exception { + Configuration conf = buildConfiguration(); + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + + assertThat(accessor.getColumnNames(conf), is(notNullValue())); + accessor.close(); + accessor.close(); + } + + @Test + public void testGetRecordWriter_writesRows() throws Exception { + Configuration conf = buildConfiguration(); + conf.set(JdbcStorageConfig.JDBC_URL.getPropertyName(), + conf.get(JdbcStorageConfig.JDBC_URL.getPropertyName()) + .replace("jdbc:h2:mem:test;MODE=MySQL;INIT", + "jdbc:h2:mem:test_writer;MODE=MySQL;DB_CLOSE_DELAY=-1;INIT")); + conf.set(JdbcStorageConfig.TABLE.getPropertyName(), "test_writer"); + conf.set(serdeConstants.LIST_COLUMNS, "id,name"); + conf.set(serdeConstants.LIST_COLUMN_TYPES, "int,string"); + + TaskAttemptContext context = mock(TaskAttemptContext.class); + when(context.getConfiguration()).thenReturn(conf); + + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + @SuppressWarnings("rawtypes") + RecordWriter writer = accessor.getRecordWriter(context); + DBRecordWritable record = new DBRecordWritable(2); + record.set(0, 10); + record.set(1, "written"); + + writer.write(record, null); + writer.close(context); + + String readBackUrl = conf.get(JdbcStorageConfig.JDBC_URL.getPropertyName()).replaceAll(";INIT=.*", ""); + try (Connection conn = DriverManager.getConnection(readBackUrl); + Statement statement = conn.createStatement()) { + try (ResultSet rs = statement.executeQuery("select id, name from test_writer")) { + assertThat(rs.next(), is(true)); + assertThat(rs.getInt("id"), is(equalTo(10))); + assertThat(rs.getString("name"), is(equalTo("written"))); + assertThat(rs.next(), is(false)); + } + statement.execute("SHUTDOWN"); + } + } + + @Test + public void testGetRecordWriter_writesMultipleRows() throws Exception { + Configuration conf = buildConfiguration(); + conf.set(JdbcStorageConfig.JDBC_URL.getPropertyName(), + conf.get(JdbcStorageConfig.JDBC_URL.getPropertyName()) + .replace("jdbc:h2:mem:test;MODE=MySQL;INIT", + "jdbc:h2:mem:test_writer_multi;MODE=MySQL;DB_CLOSE_DELAY=-1;INIT")); + conf.set(JdbcStorageConfig.TABLE.getPropertyName(), "test_writer"); + conf.set(serdeConstants.LIST_COLUMNS, "id,name"); + conf.set(serdeConstants.LIST_COLUMN_TYPES, "int,string"); + + TaskAttemptContext context = mock(TaskAttemptContext.class); + when(context.getConfiguration()).thenReturn(conf); + + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(conf); + @SuppressWarnings("rawtypes") + RecordWriter writer = accessor.getRecordWriter(context); + + DBRecordWritable firstRecord = new DBRecordWritable(2); + firstRecord.set(0, 11); + firstRecord.set(1, "first"); + writer.write(firstRecord, null); + + DBRecordWritable secondRecord = new DBRecordWritable(2); + secondRecord.set(0, 12); + secondRecord.set(1, "second"); + writer.write(secondRecord, null); + + writer.close(context); + + String readBackUrl = conf.get(JdbcStorageConfig.JDBC_URL.getPropertyName()).replaceAll(";INIT=.*", ""); + try (Connection conn = DriverManager.getConnection(readBackUrl); + Statement statement = conn.createStatement()) { + try (ResultSet rs = statement.executeQuery("select id, name from test_writer order by id")) { + assertThat(rs.next(), is(true)); + assertThat(rs.getInt("id"), is(equalTo(11))); + assertThat(rs.getString("name"), is(equalTo("first"))); + + assertThat(rs.next(), is(true)); + assertThat(rs.getInt("id"), is(equalTo(12))); + assertThat(rs.getString("name"), is(equalTo("second"))); + + assertThat(rs.next(), is(false)); + } + statement.execute("SHUTDOWN"); + } + } + + private String getStrategyIdColumnName(Configuration conf, DatabaseAccessor accessor) + throws HiveJdbcDatabaseAccessException { + return accessor.getColumnNames(conf).get(0); + } + private Configuration buildConfiguration() { String scriptPath = diff --git a/jdbc-handler/src/test/resources/test_script.sql b/jdbc-handler/src/test/resources/test_script.sql index 29a2db7402f7..60e44574958d 100644 --- a/jdbc-handler/src/test/resources/test_script.sql +++ b/jdbc-handler/src/test/resources/test_script.sql @@ -1,4 +1,5 @@ DROP TABLE IF EXISTS test_strategy; +DROP TABLE IF EXISTS test_writer; CREATE TABLE IF NOT EXISTS test_strategy ( strategy_id int(11) NOT NULL, @@ -18,6 +19,12 @@ INSERT INTO test_strategy (strategy_id, name, referrer, landing, priority, imple INSERT INTO test_strategy (strategy_id, name, referrer, landing, priority, implementation, last_modified) VALUES (4,'S4','ddd','jkl',980,NULL,'2012-05-08 15:01:15'); INSERT INTO test_strategy (strategy_id, name, referrer, landing, priority, implementation, last_modified) VALUES (5,'S5','eee',NULL,NULL,NULL,'2012-05-08 15:01:15'); +CREATE TABLE IF NOT EXISTS test_writer ( + id int(11) NOT NULL, + name varchar(50) DEFAULT NULL, + PRIMARY KEY (id) +); + CREATE TABLE IF NOT EXISTS all_types_table ( char_col CHAR, char_with_len_col CHAR(20), From c813e9c013c58587857849930cedd7b75fae0cb9 Mon Sep 17 00:00:00 2001 From: Hemanth Umashankar Date: Fri, 10 Jul 2026 16:08:54 +0530 Subject: [PATCH 2/2] HIVE-26441: Consolidate JDBC database accessor dialect tests into TestDatabaseAccessorByDatabaseType --- .../TestDatabaseAccessorByDatabaseType.java | 400 ++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 jdbc-handler/src/test/java/org/apache/hive/storage/jdbc/dao/TestDatabaseAccessorByDatabaseType.java diff --git a/jdbc-handler/src/test/java/org/apache/hive/storage/jdbc/dao/TestDatabaseAccessorByDatabaseType.java b/jdbc-handler/src/test/java/org/apache/hive/storage/jdbc/dao/TestDatabaseAccessorByDatabaseType.java new file mode 100644 index 000000000000..b0396b60eb1a --- /dev/null +++ b/jdbc-handler/src/test/java/org/apache/hive/storage/jdbc/dao/TestDatabaseAccessorByDatabaseType.java @@ -0,0 +1,400 @@ +/* + * + * Licensed 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.hive.storage.jdbc.dao; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hive.storage.jdbc.conf.DatabaseType; +import org.apache.hive.storage.jdbc.conf.JdbcStorageConfig; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Function; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +/** Tests database-specific accessor behavior; generic JDBC behavior is covered separately. */ +@RunWith(Enclosed.class) +public class TestDatabaseAccessorByDatabaseType { + + private static final String BASE = "select * from test_strategy"; + private static final String H2_DRIVER = "org.h2.Driver"; + private static final String R = OracleDatabaseAccessor.ROW_NUM_COLUMN_NAME; + + @RunWith(Parameterized.class) + public static class AddLimitToQueryWithLimit { + private final DatabaseType databaseType; + private final String expectedQuery; + + public AddLimitToQueryWithLimit(DatabaseType databaseType, String expectedQuery) { + this.databaseType = databaseType; + this.expectedQuery = expectedQuery; + } + + @Parameters(name = "{0}") + public static Collection data() { + return queryRows(database -> database.limit2); + } + + @Test + public void testAddLimitToQuery_withLimit() { + assertThat(accessor(databaseType).addLimitToQuery(BASE, 2), is(equalTo(expectedQuery))); + } + } + + @RunWith(Parameterized.class) + public static class AddLimitToQueryNoLimit { + private final DatabaseType databaseType; + private final String expectedQuery; + + public AddLimitToQueryNoLimit(DatabaseType databaseType, String expectedQuery) { + this.databaseType = databaseType; + this.expectedQuery = expectedQuery; + } + + @Parameters(name = "{0}") + public static Collection data() { + return queryRows(database -> database.noLimit); + } + + @Test + public void testAddLimitToQuery_noLimit() { + assertThat(accessor(databaseType).addLimitToQuery(BASE, -1), is(equalTo(expectedQuery))); + } + } + + @RunWith(Parameterized.class) + public static class AddLimitAndOffsetToQueryLimitAndOffset { + private final DatabaseType databaseType; + private final String expectedQuery; + + public AddLimitAndOffsetToQueryLimitAndOffset(DatabaseType databaseType, String expectedQuery) { + this.databaseType = databaseType; + this.expectedQuery = expectedQuery; + } + + @Parameters(name = "{0}") + public static Collection data() { + return queryRows(database -> database.limitAndOffset); + } + + @Test + public void testAddLimitAndOffsetToQuery_limitAndOffset() { + assertThat(accessor(databaseType).addLimitAndOffsetToQuery(BASE, 2, 1), is(equalTo(expectedQuery))); + } + } + + @RunWith(Parameterized.class) + public static class AddLimitAndOffsetToQueryOffsetOnly { + private final DatabaseType databaseType; + private final String expectedQuery; + + public AddLimitAndOffsetToQueryOffsetOnly(DatabaseType databaseType, String expectedQuery) { + this.databaseType = databaseType; + this.expectedQuery = expectedQuery; + } + + @Parameters(name = "{0}") + public static Collection data() { + return queryRows(database -> database.offsetOnly); + } + + @Test + public void testAddLimitAndOffsetToQuery_offsetOnly() { + assertThat(accessor(databaseType).addLimitAndOffsetToQuery(BASE, -1, 2), is(equalTo(expectedQuery))); + } + } + + @RunWith(Parameterized.class) + public static class AddLimitAndOffsetToQueryOffsetZero { + private final DatabaseType databaseType; + private final String expectedQuery; + + public AddLimitAndOffsetToQueryOffsetZero(DatabaseType databaseType, String expectedQuery) { + this.databaseType = databaseType; + this.expectedQuery = expectedQuery; + } + + @Parameters(name = "{0}") + public static Collection data() { + return queryRows(database -> database.offsetZero); + } + + @Test + public void testAddLimitAndOffsetToQuery_offsetZero() { + assertThat(accessor(databaseType).addLimitAndOffsetToQuery(BASE, 2, 0), is(equalTo(expectedQuery))); + } + } + + @RunWith(Parameterized.class) + public static class NeedColumnQuote { + private final DatabaseType databaseType; + private final boolean expectedNeedColumnQuote; + + public NeedColumnQuote(DatabaseType databaseType, boolean expectedNeedColumnQuote) { + this.databaseType = databaseType; + this.expectedNeedColumnQuote = expectedNeedColumnQuote; + } + + @Parameters(name = "{0}") + public static Collection data() { + return rowsFor(database -> database.needColumnQuote); + } + + @Test + public void testNeedColumnQuote() { + assertThat(accessor(databaseType).needColumnQuote(), is(expectedNeedColumnQuote)); + } + } + + @RunWith(Parameterized.class) + public static class ConstructQuery { + private final DatabaseType databaseType; + private final String expectedQuery; + + public ConstructQuery(DatabaseType databaseType, String expectedQuery) { + this.databaseType = databaseType; + this.expectedQuery = expectedQuery; + } + + @Parameters(name = "{0}") + public static Collection data() { + return queryRows(database -> database.constructQuery); + } + + @Test + public void testConstructQuery() { + assertThat(accessor(databaseType).constructQuery("t", new String[]{"a", "b", "c"}), + is(equalTo(expectedQuery))); + } + } + + @RunWith(Parameterized.class) + public static class GetMetaDataQuery { + private final DatabaseType databaseType; + private final String expectedQuery; + + public GetMetaDataQuery(DatabaseType databaseType, String expectedQuery) { + this.databaseType = databaseType; + this.expectedQuery = expectedQuery; + } + + @Parameters(name = "{0}") + public static Collection data() { + return queryRows(database -> database.metaDataQuery); + } + + @Test + public void testGetMetaDataQuery() { + assertThat(accessor(databaseType).getMetaDataQuery(BASE), is(equalTo(expectedQuery))); + } + } + + @RunWith(Parameterized.class) + public static class AccessorType { + private final DatabaseType databaseType; + + public AccessorType(DatabaseType databaseType) { + this.databaseType = databaseType; + } + + @Parameters(name = "{0}") + public static Collection data() { + return rowsFor(database -> null); + } + + @Test + public void testAccessorType() { + assertThat(DatabaseAccessorFactory.getAccessor(buildConfiguration(databaseType)), + instanceOf(accessorCase(databaseType).expectedType)); + } + } + + private static final AccessorCase[] CASES = { + db(DatabaseType.MYSQL, MySqlDatabaseAccessor.class, + BASE + " LIMIT 2", + BASE, + BASE + " LIMIT 1,2", + BASE, + BASE + " LIMIT 2", + false, + "INSERT INTO t VALUES (?,?,?);", + BASE + " LIMIT 1"), + db(DatabaseType.POSTGRES, PostgresDatabaseAccessor.class, + BASE + " LIMIT 2", + BASE, + BASE + " LIMIT 2 OFFSET 1", + BASE, + BASE + " LIMIT 2", + true, + "INSERT INTO t VALUES (?,?,?);", + BASE + " LIMIT 1"), + db(DatabaseType.DB2, DB2DatabaseAccessor.class, + BASE + " LIMIT 2", + BASE, + BASE + " LIMIT 2 OFFSET 1", + BASE, + BASE + " LIMIT 2", + true, + "INSERT INTO t VALUES (?,?,?)", + BASE + " LIMIT 1"), + db(DatabaseType.DERBY, DerbyDatabaseAccessor.class, + BASE + " {LIMIT 2}", + BASE, + BASE + " {LIMIT 2 OFFSET 1}", + BASE + " {OFFSET 2}", + BASE + " {LIMIT 2}", + true, + "INSERT INTO t VALUES (?,?,?)", + BASE + " {LIMIT 1}"), + db(DatabaseType.MSSQL, MsSqlDatabaseAccessor.class, + BASE + " {LIMIT 2}", + BASE, + BASE + " ORDER BY 1 OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY", + BASE, + BASE + " {LIMIT 2}", + true, + "INSERT INTO t VALUES (?,?,?);", + BASE + " {LIMIT 1}"), + db(DatabaseType.ORACLE, OracleDatabaseAccessor.class, + "SELECT * FROM (" + BASE + ") WHERE ROWNUM <= 2", + BASE, + "SELECT * FROM (SELECT t.*, ROWNUM AS " + R + " FROM (" + BASE + ") t) WHERE " + + R + " >1 AND " + R + " <=3", + BASE, + "SELECT * FROM (" + BASE + ") WHERE ROWNUM <= 2", + true, + "INSERT INTO t VALUES (?,?,?)", + "SELECT * FROM (" + BASE + ") WHERE ROWNUM <= 1"), + db(DatabaseType.HIVE, HiveDatabaseAccessor.class, + BASE + " LIMIT 2", + BASE, + BASE + " LIMIT 2 OFFSET 1", + BASE, + BASE + " LIMIT 2", + true, + "INSERT INTO t VALUES (?,?,?);", + BASE + " LIMIT 0"), + db(DatabaseType.JETHRO_DATA, JethroDatabaseAccessor.class, + "Select * from (" + BASE + ") as \"tmp\" limit 2", + "Select * from (" + BASE + ") as \"tmp\" limit -1", + BASE + " LIMIT 1,2", + BASE + " LIMIT 2,-1", + "Select * from (" + BASE + ") as \"tmp\" limit 2", + true, + "INSERT INTO t VALUES (?,?,?);", + "Select * from (" + BASE + ") as \"tmp\" limit 0") + }; + + private static Object[] row(Object... values) { + return values; + } + + private static Collection rowsFor(Function expectedValue) { + Object[][] rows = new Object[CASES.length][]; + for (int i = 0; i < CASES.length; i++) { + AccessorCase database = CASES[i]; + Object expected = expectedValue.apply(database); + rows[i] = expected == null ? row(database.databaseType) : row(database.databaseType, expected); + } + return Arrays.asList(rows); + } + + private static Collection queryRows(Function expectedQuery) { + return rowsFor(database -> expectedQuery.apply(database)); + } + + private static AccessorCase db( + DatabaseType databaseType, + Class expectedType, + String limit2, + String noLimit, + String limitAndOffset, + String offsetOnly, + String offsetZero, + boolean needColumnQuote, + String constructQuery, + String metaDataQuery) { + return new AccessorCase(databaseType, expectedType, limit2, noLimit, limitAndOffset, + offsetOnly, offsetZero, needColumnQuote, constructQuery, metaDataQuery); + } + + private static AccessorCase accessorCase(DatabaseType databaseType) { + for (AccessorCase accessorCase : CASES) { + if (accessorCase.databaseType == databaseType) { + return accessorCase; + } + } + throw new IllegalArgumentException("Unexpected database type: " + databaseType); + } + + private static GenericJdbcDatabaseAccessor accessor(DatabaseType databaseType) { + DatabaseAccessor accessor = DatabaseAccessorFactory.getAccessor(buildConfiguration(databaseType)); + assertThat(accessor, instanceOf(accessorCase(databaseType).expectedType)); + return (GenericJdbcDatabaseAccessor) accessor; + } + + private static Configuration buildConfiguration(DatabaseType databaseType) { + Configuration config = new Configuration(); + config.set(JdbcStorageConfig.DATABASE_TYPE.getPropertyName(), databaseType.name()); + config.set(JdbcStorageConfig.JDBC_DRIVER_CLASS.getPropertyName(), H2_DRIVER); + return config; + } + + private static class AccessorCase { + private final DatabaseType databaseType; + private final Class expectedType; + private final String limit2; + private final String noLimit; + private final String limitAndOffset; + private final String offsetOnly; + private final String offsetZero; + private final boolean needColumnQuote; + private final String constructQuery; + private final String metaDataQuery; + + private AccessorCase( + DatabaseType databaseType, + Class expectedType, + String limit2, + String noLimit, + String limitAndOffset, + String offsetOnly, + String offsetZero, + boolean needColumnQuote, + String constructQuery, + String metaDataQuery) { + this.databaseType = databaseType; + this.expectedType = expectedType; + this.limit2 = limit2; + this.noLimit = noLimit; + this.limitAndOffset = limitAndOffset; + this.offsetOnly = offsetOnly; + this.offsetZero = offsetZero; + this.needColumnQuote = needColumnQuote; + this.constructQuery = constructQuery; + this.metaDataQuery = metaDataQuery; + } + } + +}