diff --git a/src/test/java/com/alipay/oceanbase/rpc/ObTableClientTest.java b/src/test/java/com/alipay/oceanbase/rpc/ObTableClientTest.java index 58f36fab..d3bfcea0 100644 --- a/src/test/java/com/alipay/oceanbase/rpc/ObTableClientTest.java +++ b/src/test/java/com/alipay/oceanbase/rpc/ObTableClientTest.java @@ -30,6 +30,7 @@ import com.alipay.oceanbase.rpc.property.Property; import com.alipay.oceanbase.rpc.protocol.payload.ObPayload; import com.alipay.oceanbase.rpc.protocol.payload.ResultCodes; +import com.alipay.oceanbase.rpc.protocol.payload.impl.ObObj; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.aggregation.ObTableAggregation; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.aggregation.ObTableAggregationResult; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.mutate.ObTableQueryAndMutateRequest; @@ -2525,4 +2526,51 @@ public void testQueryWithEmptyTable() throws Exception { Assert.assertEquals("table name is null", ((IllegalArgumentException) e).getMessage()); } } + + @Test + public void testQueryWithScanOrder() throws Exception { + String tableName = "test_query_scan_order"; + ((ObTableClient) client).addRowKeyElement(tableName, new String[]{"c1"}); + try { + client.insert(tableName, new Object[] { 0, 1 }, new String[] { "c3" }, + new Object[] { 2 }); + client.insert(tableName, new Object[] { 0, 2 }, new String[] { "c3" }, + new Object[] { 1 }); + // Forward + Object[] start = {0, ObObj.getMin()}; + Object[] end = {1, ObObj.getMax()}; + QueryResultSet resultSet = client.query(tableName).indexName("idx") + .setScanRangeColumns("c1", "c3") + .addScanRange(start, end) + .scanOrder(true) + .select("c1", "c2", "c3") + .execute(); + Assert.assertEquals(2, resultSet.cacheSize()); + int pre_value = 0; + while(resultSet.next()) { + Map valueMap = resultSet.getRow(); + Assert.assertTrue(pre_value < (int)valueMap.get("c3") ); + pre_value = (int)valueMap.get("c3"); + } + // Reverse + QueryResultSet resultSet2 = client.query(tableName).indexName("idx") + .setScanRangeColumns("c1", "c3") + .addScanRange(start, end) + .scanOrder(false) + .select("c1", "c2", "c3") + .execute(); + Assert.assertEquals(2, resultSet2.cacheSize()); + pre_value = 3; + while(resultSet2.next()) { + Map valueMap = resultSet2.getRow(); + Assert.assertTrue(pre_value > (int)valueMap.get("c3") ); + pre_value = (int)valueMap.get("c3"); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + client.delete(tableName, new Object[] { 0, 1 }); + client.delete(tableName, new Object[] { 0, 2 }); + } + } } diff --git a/src/test/java/com/alipay/oceanbase/rpc/ObTableIndexWithCalcColumn.java b/src/test/java/com/alipay/oceanbase/rpc/ObTableIndexWithCalcColumn.java new file mode 100644 index 00000000..dabbed04 --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/rpc/ObTableIndexWithCalcColumn.java @@ -0,0 +1,411 @@ +/*- + * #%L + * com.oceanbase:obkv-table-client + * %% + * Copyright (C) 2021 - 2022 OceanBase + * %% + * OBKV Table Client Framework is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * #L% + */ + +package com.alipay.oceanbase.rpc; + +import com.alipay.oceanbase.rpc.ObTableClient; +import com.alipay.oceanbase.rpc.mutation.Row; +import com.alipay.oceanbase.rpc.stream.QueryResultSet; +import com.alipay.oceanbase.rpc.util.ObTableClientTestUtil; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.sql.*; +import java.util.Map; + +import static com.alipay.oceanbase.rpc.mutation.MutationFactory.colVal; +import static com.alipay.oceanbase.rpc.mutation.MutationFactory.row; + +public class ObTableIndexWithCalcColumn { + + String CreateTableStatement = "CREATE TABLE `index_has_current_timestamp` (\n" + + " `id` bigint(20) NOT NULL AUTO_INCREMENT,\n" + + " `adiu` varchar(512) NOT NULL DEFAULT '',\n" + + " `mode` varchar(512) NOT NULL DEFAULT '',\n" + + " `time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" + + " `tag` varchar(512) DEFAULT '',\n" + + " `content` varchar(412) DEFAULT '',\n" + + " PRIMARY KEY (`id`, `adiu`),\n" + + " KEY `idx_adiu_time_mode_tag` (`id`, `adiu`, `time`, `mode`) BLOCK_SIZE 16384 LOCAL,\n" + + " KEY `g_idx_time_tag_mode` (`time`, `tag`, `mode`) BLOCK_SIZE 16384 GLOBAL\n" + + " ) TTL(time + INTERVAL 300 second) partition by key(adiu) partitions 8;"; + + String TableName = "index_has_current_timestamp"; + Long TableId; + String LocalIndexTableName; + String GlobalIndexTableName; + String StringFormat = "%s_%d"; + String[] AllColumns = {"id", "adiu", "mode", "time", "tag", "content"}; + int recordCount = 10; + ObTableClient client; + + @Before + public void setup() throws Exception { + setEnableIndexDirectSelect(); + createTable(); + final ObTableClient obTableClient = ObTableClientTestUtil.newTestClient(); + obTableClient.init(); + this.client = obTableClient; + this.client.addRowKeyElement(TableName, new String[]{"id", "adiu"}); + } + + @After + public void teardown() throws Exception { + dropTable(); + } + + + public void setEnableIndexDirectSelect() throws Exception { + Connection connection = ObTableClientTestUtil.getConnection(); + Statement statement = connection.createStatement(); + statement.execute("set global ob_enable_index_direct_select = on"); + } + + public void createTable() throws Exception { + Connection connection = ObTableClientTestUtil.getConnection(); + Statement statement = connection.createStatement(); + statement.execute(CreateTableStatement); + ResultSet rs = statement.executeQuery("select table_id from oceanbase.__all_table where table_name = '" + TableName + "'"); + if (rs.next()) { + TableId = rs.getLong(1); + LocalIndexTableName = "__idx_" + TableId + "_idx_adiu_time_mode_tag"; + GlobalIndexTableName = "__idx_" + TableId + "_g_idx_time_tag_mode"; + } + statement.close(); + } + + public void dropTable() throws Exception { + Connection connection = ObTableClientTestUtil.getConnection(); + Statement statement = connection.createStatement(); + statement.execute("drop table " + TableName); + } + + public void deleteTable() throws Exception { + Connection connection = ObTableClientTestUtil.getConnection(); + Statement statement = connection.createStatement(); + statement.execute("delete from " + TableName); + } + + public void removeTTLAttribute() throws Exception { + Connection connection = ObTableClientTestUtil.getConnection(); + Statement statement = connection.createStatement(); + statement.execute("alter table " + TableName + " remove TTL"); + } + + public void addTTLAttribute(int expire_secord) throws Exception { + Connection connection = ObTableClientTestUtil.getConnection(); + Statement statement = connection.createStatement(); + statement.execute("alter table " + TableName + " TTL (time + INTERVAL " + expire_secord + " SECOND)"); + } + + private void checkIndexData(long count) throws Exception { + String sql = "select count(1) as cnt from " + LocalIndexTableName; + Connection connection = ObTableClientTestUtil.getConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(sql); + if (resultSet.next()) { + long total = resultSet.getLong(1); + Assert.assertEquals(count, total); + } else { + Assert.fail("there is no data for " + LocalIndexTableName); + } + sql = "select count(1) as cnt from " + GlobalIndexTableName; + resultSet = statement.executeQuery(sql); + if (resultSet.next()) { + long total = resultSet.getLong(1); + Assert.assertEquals(count, total); + } else { + Assert.fail("there is no data for " + GlobalIndexTableName); + } + statement.close(); + } + + @Test + public void test_without_ttl_attributes() throws Exception { + recordCount = 10; + removeTTLAttribute(); + test_insert(); + test_update(); + test_insert_up(); + test_replace(); + test_delete(); + test_query(); + } + + + @Test + public void test_with_ttl_attribute() throws Exception { + recordCount = 10; + // test rows has been not expired + addTTLAttribute(300); + test_insert(); + test_update(); + test_insert_up(); + test_replace(); + test_delete(); + test_query(); + // test rows has been expired + addTTLAttribute(5); + test_insert_with_expired_row(); + test_query_with_expired_row(); + } + + public void test_insert_with_expired_row() throws Exception { + try { + insert("insert", recordCount, true); + Thread.sleep(5000); + update("insertOrupdate", recordCount, true); + checkIndexData(recordCount); + } catch (Exception e) { + e.printStackTrace(); + } finally { + deleteTable(); + } + } + + + public void insert(String op_type, int count, boolean fill_autoinc) throws Exception { + for (int i = 1; i <= count; i++) { + String adiu = String.format(StringFormat, "adiu", i); + String mode = String.format(StringFormat, "mode", i); + String tag = String.format(StringFormat, "tag", i); + String content = String.format(StringFormat, "content", i); + Object autoincObj = fill_autoinc ? Long.valueOf(i) : null; + Row rowKey = row(colVal("id", autoincObj), colVal("adiu", adiu)); + Row row = row(); + row.add(colVal("mode", mode)); + row.add(colVal("tag", tag)); + row.add(colVal("content", content)); + if ("insert".equalsIgnoreCase(op_type)) { + client.insert(TableName).setRowKey(rowKey).addMutateRow(row).execute(); + } + if ("insertOrUpdate".equalsIgnoreCase(op_type)) { + client.insertOrUpdate(TableName).setRowKey(rowKey).addMutateRow(row).execute(); + } + if ("replace".equalsIgnoreCase(op_type)) { + client.replace(TableName).setRowKey(rowKey).addMutateRow(row).execute(); + } + } + } + + public void update(String op_type, int count, boolean is_expired) throws Exception { + for (int i = 1; i <= count; i++) { + Long id = Long.valueOf(i); + String adiu = String.format(StringFormat, "adiu", i); + Map valueMap = client.get(TableName, new Object[]{id, adiu}, AllColumns); + Timestamp time1 = new Timestamp(System.currentTimeMillis() + 100000); + if (is_expired) { + Assert.assertTrue(valueMap.isEmpty()); + } else { + Assert.assertEquals(id, valueMap.get("id")); + Assert.assertEquals(String.format(StringFormat, "adiu", id), valueMap.get("adiu")); + time1 = (Timestamp) valueMap.get("time"); + } + + // do update + Row rowKey = row(colVal("id", id), colVal("adiu", adiu)); + Row row = row(); + String update_mode = String.format(StringFormat, "mode_update", i); + String update_tag = String.format(StringFormat, "mode_tag", i); + String update_content = String.format(StringFormat, "mode_content", i); + row.add(colVal("mode", update_mode)); + row.add(colVal("tag", update_tag)); + row.add(colVal("content", update_content)); + if ("update".equalsIgnoreCase(op_type)) { + client.update(TableName).setRowKey(rowKey).addMutateRow(row).execute(); + } + if ("insertOrUpdate".equalsIgnoreCase(op_type)) { + client.insertOrUpdate(TableName).setRowKey(rowKey).addMutateRow(row).execute(); + } + if ("replace".equalsIgnoreCase(op_type)) { + client.replace(TableName).setRowKey(rowKey).addMutateRow(row).execute(); + } + // get again + Map valueMap_2 = client.get(TableName, new Object[]{id, adiu}, AllColumns); + Assert.assertEquals(id, valueMap_2.get("id")); + Assert.assertEquals(String.format(StringFormat, "adiu", id), valueMap_2.get("adiu")); + Assert.assertEquals(update_mode, valueMap_2.get("mode")); + Assert.assertEquals(update_tag, valueMap_2.get("tag")); + Assert.assertEquals(update_content, valueMap_2.get("content")); + if (is_expired) { + Assert.assertTrue(time1.after((Timestamp) valueMap_2.get("time"))); + } else { + Assert.assertTrue(time1.before((Timestamp) valueMap_2.get("time"))); + } + } + } + + public void test_insert() throws Exception { + try { + insert("insert", recordCount, false); + for (int i = 1; i <= recordCount; i++) { + Long id = Long.valueOf(i); + String adiu = String.format(StringFormat, "adiu", i); + Map valueMap = client.get(TableName, new Object[]{id, adiu}, AllColumns); + Assert.assertEquals(id, valueMap.get("id")); + Assert.assertEquals(String.format(StringFormat, "adiu", id), valueMap.get("adiu")); + } + checkIndexData(recordCount); + } catch (Exception e) { + e.printStackTrace(); + } finally { + deleteTable(); + } + } + + public void test_update() throws Exception { + try { + insert("insert", recordCount, true); + Thread.sleep(1000); + update("update", recordCount, false); + checkIndexData(recordCount); + } catch (Exception e) { + e.printStackTrace(); + } finally { + deleteTable(); + } + + } + + public void test_insert_up() throws Exception { + try { + insert("insertOrUpdate", recordCount, true); + Thread.sleep(1000); + update("insertOrUpdate", recordCount, false); + checkIndexData(recordCount); + } catch (Exception e) { + e.printStackTrace(); + } finally { + deleteTable(); + } + } + + public void test_replace() throws Exception { + try { + insert("replace", recordCount, true); + Thread.sleep(1000); + update("replace", recordCount, false); + checkIndexData(recordCount); + } catch (Exception e) { + e.printStackTrace(); + } finally { + deleteTable(); + } + } + + public void test_delete() throws Exception { + try { + insert("insert", recordCount, true); + checkIndexData(recordCount); + for (int i = 1; i <= recordCount; i++) { + Long id = Long.valueOf(i); + String adiu = String.format(StringFormat, "adiu", i); + Map valueMap = client.get(TableName, new Object[]{id, adiu}, AllColumns); + Assert.assertEquals(id, valueMap.get("id")); + Assert.assertEquals(String.format(StringFormat, "adiu", id), valueMap.get("adiu")); + Row rowKey = row(colVal("id", id), colVal("adiu", adiu)); + client.delete(TableName).setRowKey(rowKey).execute(); + } + checkIndexData(0); + } catch (Exception e) { + e.printStackTrace(); + } finally { + deleteTable(); + } + } + + public void test_query() throws Exception { + try { + insert("insert", recordCount, true); + test_query(false); + } catch (Exception e) { + e.printStackTrace(); + } finally { + deleteTable(); + } + } + + public void test_query_with_expired_row() throws Exception { + try { + insert("insert", recordCount, true); + Thread.sleep(5000); + test_query(true); + } catch (Exception e) { + e.printStackTrace(); + } finally { + deleteTable(); + } + } + + public void test_query(boolean is_expire) throws Exception { + // query with primary index + String start_adiu = String.format(StringFormat, "adiu", 1); + Object[] start = {0L, start_adiu}; + String end_adiu = String.format(StringFormat, "adiu", recordCount); + Object[] end = {Long.valueOf(recordCount), end_adiu}; + QueryResultSet resultSet = client.query(TableName).addScanRange(start, end) + .execute(); + if (is_expire) { + Assert.assertEquals(resultSet.cacheSize(), 0); + } else { + Assert.assertEquals(resultSet.cacheSize(), recordCount); + } + + // query with local index + Timestamp start_time1 = new Timestamp(System.currentTimeMillis() - 100000); + start_time1.setNanos(0); + String start_adiu1 = String.format(StringFormat, "adiu", 1); + String start_mode1 = String.format(StringFormat, "mode", 1); + Object[] start1 = {0L, start_adiu1, start_time1, start_mode1}; + Timestamp end_time1 = new Timestamp(System.currentTimeMillis() + 100000); + end_time1.setNanos(0); + String end_adiu1 = String.format(StringFormat, "adiu", recordCount); + String end_mode1 = String.format(StringFormat, "mode", recordCount); + Object[] end1 = {Long.valueOf(recordCount), end_adiu1, end_time1, end_mode1}; + QueryResultSet resultSet1 = client.query(TableName).indexName("idx_adiu_time_mode_tag") + .setScanRangeColumns("id", "time", "tag", "mode") + .addScanRange(start1, end1) + .execute(); + if (is_expire) { + Assert.assertEquals(resultSet1.cacheSize(), 0); + } else { + Assert.assertEquals(resultSet1.cacheSize(), recordCount); + } + // query with global index + Timestamp startTime2 = new Timestamp(System.currentTimeMillis() - 100000); + startTime2.setNanos(0); + String start_tag = String.format(StringFormat, "tag", 1); + String start_mode = String.format(StringFormat, "mode", 1); + Object[] start2 = {startTime2, start_tag, start_mode}; + Timestamp endTime2 = new Timestamp(System.currentTimeMillis() + 100000); + endTime2.setNanos(0); + String end_tag = String.format(StringFormat, "tag", recordCount); + String end_mode = String.format(StringFormat, "mode", recordCount); + Object[] end2 = {endTime2, end_tag, end_mode}; + QueryResultSet resultSet2 = client.query(TableName).indexName("g_idx_time_tag_mode") + .setScanRangeColumns("time", "tag", "mode") + .addScanRange(start2, end2) + .execute(); + if (is_expire) { + Assert.assertEquals(resultSet2.cacheSize(), 0); + } else { + Assert.assertEquals(resultSet2.cacheSize(), recordCount); + } + } +} diff --git a/src/test/resources/ci.sql b/src/test/resources/ci.sql index 6503988c..707fb100 100644 --- a/src/test/resources/ci.sql +++ b/src/test/resources/ci.sql @@ -455,6 +455,17 @@ CREATE TABLE IF NOT EXISTS `test_table_object` ( `c19` longblob DEFAULT NULL ); +CREATE TABLE `test_query_scan_order` ( + `c1` int(12) NOT NULL, + `c2` int(11) NOT NULL, + `c3` int(11) NOT NULL, + PRIMARY KEY (`c1`, `c2`), + KEY `idx` (`c1`, `c3`) BLOCK_SIZE 16384 LOCAL +) partition by hash(c1) +(partition `p0`, +partition `p1`, +partition `p2`); + CREATE TABLE IF NOT EXISTS `test_put` ( `id` varchar(20) NOT NULL, `c1` bigint DEFAULT NULL,