From bba72169f970454a0c16bffa516f23890a6c966c Mon Sep 17 00:00:00 2001 From: terrymanu Date: Mon, 18 Jan 2016 21:33:03 +0800 Subject: [PATCH 1/7] init version --- .gitignore | 38 +- README.md | 91 + pom.xml | 650 ++ sharding-jdbc-core/pom.xml | 82 + .../rdb/sharding/api/DatabaseType.java | 44 + .../rdb/sharding/api/ShardingDataSource.java | 94 + .../rdb/sharding/api/ShardingValue.java | 88 + .../api/config/ShardingConfiguration.java | 74 + .../config/ShardingConfigurationConstant.java | 53 + .../sharding/api/rule/BindingTableRule.java | 90 + .../rdb/sharding/api/rule/DataNode.java | 39 + .../rdb/sharding/api/rule/DataSourceRule.java | 69 + .../rdb/sharding/api/rule/ShardingRule.java | 213 + .../rdb/sharding/api/rule/TableRule.java | 134 + .../common/MultipleKeysShardingAlgorithm.java | 39 + .../strategy/common/ShardingAlgorithm.java | 26 + .../api/strategy/common/ShardingStrategy.java | 76 + .../common/SingleKeyShardingAlgorithm.java | 59 + .../database/DatabaseShardingAlgorithm.java | 28 + .../database/DatabaseShardingStrategy.java | 38 + ...MultipleKeysDatabaseShardingAlgorithm.java | 28 + .../NoneDatabaseShardingAlgorithm.java | 50 + .../SingleKeyDatabaseShardingAlgorithm.java | 28 + .../MultipleKeysTableShardingAlgorithm.java | 28 + .../table/NoneTableShardingAlgorithm.java | 50 + .../SingleKeyTableShardingAlgorithm.java | 28 + .../table/TableShardingAlgorithm.java | 28 + .../strategy/table/TableShardingStrategy.java | 38 + .../DatabaseTypeUnsupportedException.java | 34 + .../exception/SQLParserException.java | 32 + .../exception/ShardingJdbcException.java | 36 + .../rdb/sharding/executor/ExecuteUnit.java | 35 + .../rdb/sharding/executor/ExecutorEngine.java | 116 + .../rdb/sharding/executor/MergeUnit.java | 36 + .../executor/PreparedStatementExecutor.java | 125 + .../sharding/executor/StatementExecutor.java | 237 + .../jdbc/AbstractShardingResultSet.java | 77 + .../rdb/sharding/jdbc/ShardingConnection.java | 129 + .../jdbc/ShardingPreparedStatement.java | 186 + .../rdb/sharding/jdbc/ShardingStatement.java | 194 + .../adapter/AbstractConnectionAdapter.java | 142 + .../adapter/AbstractDataSourceAdapter.java | 53 + .../AbstractPreparedStatementAdapter.java | 275 + .../adapter/AbstractResultSetAdapter.java | 119 + .../AbstractResultSetGetterAdapter.java | 327 + .../adapter/AbstractStatementAdapter.java | 163 + .../sharding/jdbc/adapter/WrapperAdapter.java | 77 + .../AbstractResultSetUpdaterAdapter.java | 455 ++ ...bstractUnsupportedOperationConnection.java | 184 + ...bstractUnsupportedOperationDataSource.java | 43 + ...UnsupportedOperationPreparedStatement.java | 95 + ...AbstractUnsupportedOperationResultSet.java | 224 + ...AbstractUnsupportedOperationStatement.java | 103 + .../jdbc/util/JdbcMethodInvocation.java | 51 + .../rdb/sharding/merger/ResultSetFactory.java | 75 + .../aggregation/AbstractAggregationUnit.java | 48 + .../AccumulationAggregationUnit.java | 50 + .../aggregation/AggregationInvokeHandler.java | 69 + .../aggregation/AggregationResultSet.java | 68 + .../merger/aggregation/AggregationUnit.java | 48 + .../aggregation/AggregationUnitFactory.java | 49 + .../merger/aggregation/AggregationValue.java | 38 + .../aggregation/AvgAggregationUnit.java | 54 + .../ComparableAggregationUnit.java | 55 + .../ResultSetAggregationValue.java | 37 + .../common/AbstractMergerInvokeHandler.java | 58 + .../merger/common/ResultSetQueryIndex.java | 71 + .../sharding/merger/common/ResultSetUtil.java | 155 + .../merger/groupby/GroupByInvokeHandler.java | 43 + .../sharding/merger/groupby/GroupByKey.java | 40 + .../merger/groupby/GroupByResultSet.java | 194 + .../sharding/merger/groupby/GroupByValue.java | 114 + .../merger/iterator/IteratorResultSet.java | 52 + .../merger/orderby/OrderByResultSet.java | 81 + .../sharding/merger/orderby/OrderByValue.java | 81 + .../rdb/sharding/metrics/MetricsContext.java | 87 + .../rdb/sharding/parser/SQLParseEngine.java | 73 + .../rdb/sharding/parser/SQLParserFactory.java | 99 + .../sharding/parser/SQLVisitorRegistry.java | 127 + .../parser/result/SQLParsedResult.java | 44 + .../result/merger/AggregationColumn.java | 63 + .../parser/result/merger/GroupByColumn.java | 41 + .../sharding/parser/result/merger/Limit.java | 37 + .../parser/result/merger/MergeContext.java | 71 + .../parser/result/merger/OrderByColumn.java | 71 + .../parser/result/router/Condition.java | 79 + .../result/router/ConditionContext.java | 88 + .../parser/result/router/RouteContext.java | 40 + .../parser/result/router/SQLBuilder.java | 150 + .../sharding/parser/result/router/Table.java | 45 + .../sharding/parser/visitor/ParseContext.java | 312 + .../sharding/parser/visitor/SQLVisitor.java | 57 + .../parser/visitor/VisitorLogProxy.java | 103 + .../basic/mysql/AbstractMySQLVisitor.java | 165 + .../basic/mysql/MySQLDeleteVisitor.java | 35 + .../basic/mysql/MySQLInsertVisitor.java | 39 + .../basic/mysql/MySQLSelectVisitor.java | 195 + .../basic/mysql/MySQLUpdateVisitor.java | 35 + .../sharding/parser/visitor/or/OrParser.java | 55 + .../sharding/parser/visitor/or/OrVisitor.java | 98 + .../visitor/or/node/AbstractOrASTNode.java | 107 + .../visitor/or/node/CompositeOrASTNode.java | 52 + .../visitor/or/node/SimpleOrASTNode.java | 72 + .../rdb/sharding/router/RoutingResult.java | 38 + .../rdb/sharding/router/SQLExecutionUnit.java | 43 + .../rdb/sharding/router/SQLRouteEngine.java | 103 + .../rdb/sharding/router/SQLRouteResult.java | 40 + .../binding/BindingRoutingDataSource.java | 55 + .../router/binding/BindingRoutingResult.java | 51 + .../binding/BindingRoutingTableFactor.java | 52 + .../router/binding/BindingTablesRouter.java | 73 + .../router/mixed/CartesianDataSource.java | 57 + .../router/mixed/CartesianResult.java | 66 + .../router/mixed/CartesianTableReference.java | 49 + .../router/mixed/CartesianTablesRouter.java | 126 + .../router/mixed/MixedTablesRouter.java | 77 + .../single/SingleRoutingDataSource.java | 107 + .../router/single/SingleRoutingResult.java | 149 + .../single/SingleRoutingTableFactor.java | 48 + .../router/single/SingleTableRouter.java | 140 + .../com/dangdang/ddframe/rdb/AllTests.java | 43 + .../rdb/integrate/AbstractDBUnitTest.java | 146 + .../rdb/integrate/AllIntegrateTests.java | 70 + .../rdb/integrate/DataBaseEnvironment.java | 71 + .../integrate/ShardingJdbcDatabaseTester.java | 46 + ...stractShardingDataBasesOnlyDBUnitTest.java | 78 + .../db/DMLShardingDataBasesOnlyTest.java | 174 + ...ectAggregateShardingDataBasesOnlyTest.java | 75 + ...electGroupByShardingDataBasesOnlyTest.java | 72 + .../db/SelectShardingDataBasesOnlyTest.java | 97 + ...StatementDMLShardingDataBasesOnlyTest.java | 105 + ...ectAggregateShardingDataBasesOnlyTest.java | 43 + ...tementSelectShardingDataBasesOnlyTest.java | 82 + ...rdingBothDataBasesAndTablesDBUnitTest.java | 98 + ...DMLShardingBothDataBasesAndTablesTest.java | 185 + ...ateShardingBothDataBasesAndTablesTest.java | 75 + ...pByShardingBothDataBasesAndTablesTest.java | 72 + ...ectShardingBothDataBasesAndTablesTest.java | 111 + ...DMLShardingBothDataBasesAndTablesTest.java | 114 + ...ateShardingBothDataBasesAndTablesTest.java | 43 + ...ectShardingBothDataBasesAndTablesTest.java | 91 + ...leKeysModuloDatabaseShardingAlgorithm.java | 81 + ...tipleKeysModuloTableShardingAlgorithm.java | 80 + ...gleKeyModuloDatabaseShardingAlgorithm.java | 65 + ...SingleKeyModuloTableShardingAlgorithm.java | 65 + .../AbstractShardingTablesOnlyDBUnitTest.java | 79 + .../tbl/DMLShardingTablesOnlyTest.java | 196 + ...SelectAggregateShardingTablesOnlyTest.java | 75 + .../SelectGroupByShardingTablesOnlyTest.java | 72 + .../tbl/SelectShardingTablesOnlyTest.java | 112 + .../StatementDMLShardingTablesOnlyTest.java | 113 + ...SelectAggregateShardingTablesOnlyTest.java | 43 + ...StatementSelectShardingTablesOnlyTest.java | 98 + .../ddframe/rdb/sharding/api/AllApiTest.java | 51 + .../rdb/sharding/api/DatabaseTypeTest.java | 42 + .../sharding/api/ShardingDataSourceTest.java | 70 + .../rdb/sharding/api/ShardingValueTest.java | 61 + .../api/config/ShardingConfigurationTest.java | 90 + .../api/rule/BindingTableRuleTest.java | 78 + .../sharding/api/rule/DataSourceRuleTest.java | 63 + .../sharding/api/rule/ShardingRuleTest.java | 207 + .../rdb/sharding/api/rule/TableRuleTest.java | 105 + .../api/rule/fixture/TestDataSource.java | 28 + .../strategy/common/ShardingStrategyTest.java | 74 + .../DatabaseShardingStrategyTest.java | 34 + .../NoneDatabaseShardingAlgorithmTest.java | 60 + .../TestMultipleKeysShardingAlgorithm.java | 33 + .../TestSingleKeyShardingAlgorithm.java | 48 + .../table/NoneTableShardingAlgorithmTest.java | 60 + .../table/TableShardingStrategyTest.java | 34 + .../rdb/sharding/jdbc/AllJDBCTest.java | 56 + .../jdbc/ShardingPreparedStatementTest.java | 213 + .../sharding/jdbc/ShardingStatementTest.java | 188 + .../jdbc/adapter/ConnectionAdapterTest.java | 179 + .../jdbc/adapter/DataSourceAdapterTest.java | 96 + .../adapter/PreparedStatementAdapterTest.java | 287 + .../jdbc/adapter/ResultSetAdapterTest.java | 152 + .../adapter/ResultSetGetterAdapterTest.java | 428 ++ .../jdbc/adapter/StatementAdapterTest.java | 153 + .../ResultSetUpdaterAdapterTest.java | 478 ++ .../UnsupportedOperationConnectionTest.java | 189 + .../UnsupportedOperationDataSourceTest.java | 47 + ...pportedOperationPreparedStatementTest.java | 104 + .../UnsupportedOperationResultSetTest.java | 248 + .../UnsupportedOperationStatementTest.java | 122 + .../jdbc/util/JdbcMethodInvocationTest.java | 37 + .../rdb/sharding/merger/AllMergerTest.java | 48 + .../merger/ResultSetQueryIndexTest.java | 47 + .../sharding/merger/ResultSetUtilTest.java | 116 + .../AccumulationAggregationUnitTest.java | 35 + .../aggregation/AggregationResultSetTest.java | 107 + .../aggregation/AvgAggregationUnitTest.java | 37 + .../ComparableAggregationUnitTest.java | 44 + .../ResultSetAggregationValueTest.java | 50 + ...ractUnsupportedOperationMockResultSet.java | 346 + .../merger/fixture/MockResultSet.java | 115 + .../merger/groupby/GroupByValueTest.java | 95 + .../iterator/IteratorResultSetTest.java | 72 + .../merger/orderby/OrderByResultSetTest.java | 74 + .../merger/orderby/OrderByValueTest.java | 83 + .../rdb/sharding/metrics/AllMetricsTest.java | 26 + .../sharding/metrics/MetricsContextTest.java | 42 + .../parser/AbstractBaseParseTest.java | 244 + .../rdb/sharding/parser/AllParserTest.java | 41 + .../sharding/parser/UnsupportedParseTest.java | 48 + .../parser/jaxb/AggregationColumn.java | 53 + .../rdb/sharding/parser/jaxb/Assert.java | 67 + .../rdb/sharding/parser/jaxb/Asserts.java | 34 + .../rdb/sharding/parser/jaxb/Condition.java | 46 + .../parser/jaxb/ConditionContext.java | 34 + .../sharding/parser/jaxb/GroupByColumn.java | 40 + .../rdb/sharding/parser/jaxb/Limit.java | 37 + .../sharding/parser/jaxb/OrderByColumn.java | 40 + .../rdb/sharding/parser/jaxb/Table.java | 37 + .../rdb/sharding/parser/jaxb/Value.java | 53 + ...LPreparedStatementForOneParameterTest.java | 52 + ...PreparedStatementForTowParametersTest.java | 52 + .../parser/mysql/MySQLStatementTest.java | 53 + .../sharding/parser/mysql/OrParseTest.java | 55 + .../parser/result/SQLParsedResultTest.java | 75 + .../result/merger/MergeContextTest.java | 41 + .../router/AbstractBaseRouteSqlTest.java | 107 + .../rdb/sharding/router/AllRouterTest.java | 38 + .../ddframe/rdb/sharding/router/DMLTest.java | 45 + .../router/SelectBindingTableTest.java | 47 + .../router/SelectMixedTablesTest.java | 58 + .../router/SelectSingleTableTest.java | 57 + .../binding/BindingRoutingResultTest.java | 42 + .../fixture/OrderAttrShardingAlgorithm.java | 57 + .../fixture/OrderShardingAlgorithm.java | 64 + .../router/mixed/CartesianResultTest.java | 40 + .../single/SingleRoutingResultTest.java | 36 + .../sharding/parser/mysql/or/select_or.xml | 123 + .../preparedstatement/one_param/delete.xml | 15 + .../preparedstatement/one_param/insert.xml | 18 + .../preparedstatement/one_param/select.xml | 17 + .../one_param/select_limit.xml | 12 + .../preparedstatement/one_param/update.xml | 15 + .../preparedstatement/two_params/select.xml | 16 + .../two_params/select_limit.xml | 12 + .../parser/mysql/statement/delete.xml | 31 + .../parser/mysql/statement/insert.xml | 50 + .../statement/multiple_tables_select.xml | 73 + .../mysql/statement/select_aggregate.xml | 76 + .../mysql/statement/select_group_by.xml | 27 + .../parser/mysql/statement/select_limit.xml | 22 + .../mysql/statement/select_order_by.xml | 33 + .../parser/mysql/statement/simple_select.xml | 151 + .../parser/mysql/statement/update.xml | 17 + .../resources/integrate/dataset/Empty.xml | 4 + .../dataset/db/expect/delete/db_0.xml | 3 + .../dataset/db/expect/delete/db_1.xml | 3 + .../dataset/db/expect/delete/db_2.xml | 3 + .../dataset/db/expect/delete/db_3.xml | 3 + .../dataset/db/expect/delete/db_4.xml | 3 + .../dataset/db/expect/delete/db_5.xml | 3 + .../dataset/db/expect/delete/db_6.xml | 3 + .../dataset/db/expect/delete/db_7.xml | 3 + .../dataset/db/expect/delete/db_8.xml | 3 + .../dataset/db/expect/delete/db_9.xml | 3 + .../dataset/db/expect/insert/db_0.xml | 3 + .../dataset/db/expect/insert/db_1.xml | 3 + .../dataset/db/expect/insert/db_2.xml | 3 + .../dataset/db/expect/insert/db_3.xml | 3 + .../dataset/db/expect/insert/db_4.xml | 3 + .../dataset/db/expect/insert/db_5.xml | 3 + .../dataset/db/expect/insert/db_6.xml | 3 + .../dataset/db/expect/insert/db_7.xml | 3 + .../dataset/db/expect/insert/db_8.xml | 3 + .../dataset/db/expect/insert/db_9.xml | 3 + .../select/SelectBetweenWithSingleTable.xml | 6 + .../select/SelectEqualsWithSingleTable_0.xml | 3 + .../select/SelectEqualsWithSingleTable_1.xml | 3 + .../select/SelectGroupByWithBindingTable.xml | 4 + .../SelectGroupByWithoutGroupedColumn.xml | 4 + .../select/SelectInWithSingleTable_0.xml | 4 + .../select/SelectInWithSingleTable_1.xml | 3 + .../select/SelectLimitWithBindingTable.xml | 4 + ...lectLimitWithBindingTableWithoutOffset.xml | 4 + .../expect/select/SelectNoShardingTable.xml | 82 + .../db/expect/select_aggregate/SelectAvg.xml | 3 + .../expect/select_aggregate/SelectCount.xml | 3 + .../SelectCountWithBindingTable_0.xml | 3 + .../SelectCountWithBindingTable_1.xml | 3 + .../db/expect/select_aggregate/SelectMax.xml | 3 + .../db/expect/select_aggregate/SelectMin.xml | 3 + .../db/expect/select_aggregate/SelectSum.xml | 3 + .../db/expect/select_groupby/SelectAvg.xml | 22 + .../db/expect/select_groupby/SelectCount.xml | 22 + .../db/expect/select_groupby/SelectMax.xml | 22 + .../db/expect/select_groupby/SelectMin.xml | 22 + .../select_groupby/SelectOrderByDesc.xml | 22 + .../db/expect/select_groupby/SelectSum.xml | 22 + .../dataset/db/expect/update/db_0.xml | 6 + .../dataset/db/expect/update/db_1.xml | 6 + .../dataset/db/expect/update/db_2.xml | 6 + .../dataset/db/expect/update/db_3.xml | 6 + .../dataset/db/expect/update/db_4.xml | 6 + .../dataset/db/expect/update/db_5.xml | 6 + .../dataset/db/expect/update/db_6.xml | 6 + .../dataset/db/expect/update/db_7.xml | 6 + .../dataset/db/expect/update/db_8.xml | 6 + .../dataset/db/expect/update/db_9.xml | 6 + .../integrate/dataset/db/init/db_0.xml | 15 + .../integrate/dataset/db/init/db_1.xml | 15 + .../integrate/dataset/db/init/db_2.xml | 15 + .../integrate/dataset/db/init/db_3.xml | 15 + .../integrate/dataset/db/init/db_4.xml | 15 + .../integrate/dataset/db/init/db_5.xml | 15 + .../integrate/dataset/db/init/db_6.xml | 15 + .../integrate/dataset/db/init/db_7.xml | 15 + .../integrate/dataset/db/init/db_8.xml | 15 + .../integrate/dataset/db/init/db_9.xml | 15 + .../dataset/dbtbl/expect/delete/dbtbl_0.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_1.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_2.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_3.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_4.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_5.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_6.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_7.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_8.xml | 12 + .../dataset/dbtbl/expect/delete/dbtbl_9.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_0.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_1.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_2.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_3.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_4.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_5.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_6.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_7.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_8.xml | 12 + .../dataset/dbtbl/expect/insert/dbtbl_9.xml | 12 + .../select/SelectBetweenWithSingleTable.xml | 12 + .../select/SelectEqualsWithSingleTable_0.xml | 3 + .../select/SelectEqualsWithSingleTable_1.xml | 3 + .../select/SelectGroupByWithBindingTable.xml | 4 + .../SelectGroupByWithoutGroupedColumn.xml | 4 + .../select/SelectInWithSingleTable_0.xml | 4 + .../select/SelectInWithSingleTable_1.xml | 3 + .../select/SelectLimitWithBindingTable.xml | 4 + ...lectLimitWithBindingTableWithoutOffset.xml | 4 + .../expect/select/SelectNoShardingTable.xml | 202 + .../SelectWithBingdingTableAndConfigTable.xml | 22 + .../expect/select_aggregate/SelectAvg.xml | 3 + .../expect/select_aggregate/SelectCount.xml | 3 + .../SelectCountWithBindingTable_0.xml | 3 + .../SelectCountWithBindingTable_1.xml | 3 + .../expect/select_aggregate/SelectMax.xml | 3 + .../expect/select_aggregate/SelectMin.xml | 3 + .../expect/select_aggregate/SelectSum.xml | 3 + .../dbtbl/expect/select_groupby/SelectAvg.xml | 12 + .../expect/select_groupby/SelectCount.xml | 12 + .../dbtbl/expect/select_groupby/SelectMax.xml | 12 + .../dbtbl/expect/select_groupby/SelectMin.xml | 12 + .../select_groupby/SelectOrderByDesc.xml | 12 + .../dbtbl/expect/select_groupby/SelectSum.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_0.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_1.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_2.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_3.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_4.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_5.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_6.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_7.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_8.xml | 12 + .../dataset/dbtbl/expect/update/dbtbl_9.xml | 12 + .../integrate/dataset/dbtbl/init/dbtbl_0.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_1.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_2.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_3.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_4.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_5.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_6.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_7.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_8.xml | 35 + .../integrate/dataset/dbtbl/init/dbtbl_9.xml | 35 + .../dataset/tbl/expect/delete/db_single.xml | 12 + .../dataset/tbl/expect/insert/db_single.xml | 12 + .../select/SelectBetweenWithSingleTable.xml | 12 + .../select/SelectEqualsWithSingleTable_0.xml | 3 + .../select/SelectEqualsWithSingleTable_1.xml | 3 + .../select/SelectGroupByWithBindingTable.xml | 4 + .../SelectGroupByWithoutGroupedColumn.xml | 4 + .../select/SelectInWithSingleTable_0.xml | 4 + .../select/SelectInWithSingleTable_1.xml | 3 + .../select/SelectLimitWithBindingTable.xml | 4 + ...lectLimitWithBindingTableWithoutOffset.xml | 4 + .../expect/select/SelectNoShardingTable.xml | 42 + .../SelectWithBingdingTableAndConfigTable.xml | 22 + .../tbl/expect/select_aggregate/SelectAvg.xml | 3 + .../expect/select_aggregate/SelectCount.xml | 3 + .../SelectCountWithBindingTable_0.xml | 3 + .../SelectCountWithBindingTable_1.xml | 3 + .../tbl/expect/select_aggregate/SelectMax.xml | 3 + .../tbl/expect/select_aggregate/SelectMin.xml | 3 + .../tbl/expect/select_aggregate/SelectSum.xml | 3 + .../tbl/expect/select_groupby/SelectAvg.xml | 4 + .../tbl/expect/select_groupby/SelectCount.xml | 4 + .../tbl/expect/select_groupby/SelectMax.xml | 4 + .../tbl/expect/select_groupby/SelectMin.xml | 4 + .../select_groupby/SelectOrderByDesc.xml | 4 + .../tbl/expect/select_groupby/SelectSum.xml | 4 + .../dataset/tbl/expect/update/db_single.xml | 22 + .../integrate/dataset/tbl/init/db_single.xml | 65 + .../resources/integrate/schema/all_schema.sql | 295 + .../resources/integrate/schema/db/db_0.sql | 4 + .../resources/integrate/schema/db/db_1.sql | 4 + .../resources/integrate/schema/db/db_2.sql | 4 + .../resources/integrate/schema/db/db_3.sql | 4 + .../resources/integrate/schema/db/db_4.sql | 4 + .../resources/integrate/schema/db/db_5.sql | 4 + .../resources/integrate/schema/db/db_6.sql | 4 + .../resources/integrate/schema/db/db_7.sql | 4 + .../resources/integrate/schema/db/db_8.sql | 4 + .../resources/integrate/schema/db/db_9.sql | 4 + .../integrate/schema/dbtbl/dbtbl_0.sql | 23 + .../integrate/schema/dbtbl/dbtbl_1.sql | 23 + .../integrate/schema/dbtbl/dbtbl_2.sql | 23 + .../integrate/schema/dbtbl/dbtbl_3.sql | 23 + .../integrate/schema/dbtbl/dbtbl_4.sql | 23 + .../integrate/schema/dbtbl/dbtbl_5.sql | 23 + .../integrate/schema/dbtbl/dbtbl_6.sql | 23 + .../integrate/schema/dbtbl/dbtbl_7.sql | 23 + .../integrate/schema/dbtbl/dbtbl_8.sql | 23 + .../integrate/schema/dbtbl/dbtbl_9.sql | 23 + .../integrate/schema/tbl/db_single.sql | 23 + .../src/test/resources/logback-test.xml | 23 + sharding-jdbc-doc/config.toml | 24 + .../content/img/AlgorithmClass.900.png | Bin 0 -> 67255 bytes .../content/img/AlgorithmClass.png | Bin 0 -> 28716 bytes .../content/img/StrategyClass.900.png | Bin 0 -> 63651 bytes .../content/img/StrategyClass.png | Bin 0 -> 19166 bytes .../content/img/architecture.png | Bin 0 -> 188679 bytes sharding-jdbc-doc/content/img/execute.png | Bin 0 -> 113012 bytes sharding-jdbc-doc/content/img/intro-bg.svg | 65 + sharding-jdbc-doc/content/img/merge.png | Bin 0 -> 235192 bytes sharding-jdbc-doc/content/img/parse.png | Bin 0 -> 144333 bytes sharding-jdbc-doc/content/img/route.png | Bin 0 -> 106562 bytes sharding-jdbc-doc/content/index/index.md | 96 + .../content/post/architecture.md | 16 + sharding-jdbc-doc/content/post/features.md | 43 + sharding-jdbc-doc/content/post/restriction.md | 57 + sharding-jdbc-doc/content/post/roadmap.md | 17 + sharding-jdbc-doc/content/post/user_guide.md | 385 + sharding-jdbc-doc/layouts/_default/list.html | 4 + .../layouts/_default/single.html | 28 + sharding-jdbc-doc/layouts/index.html | 5 + .../layouts/partials/footer.html | 15 + sharding-jdbc-doc/layouts/partials/head.html | 25 + .../layouts/partials/header.html | 29 + sharding-jdbc-doc/layouts/partials/js.html | 28 + sharding-jdbc-doc/layouts/partials/nav.html | 31 + sharding-jdbc-doc/layouts/partials/pages.html | 0 .../layouts/partials/services.html | 36 + .../layouts/partials/template.css | 36 + sharding-jdbc-doc/static/css/bootstrap.css | 6203 +++++++++++++++++ .../static/css/bootstrap.min.css | 5 + sharding-jdbc-doc/static/css/landing-page.css | 209 + sharding-jdbc-doc/static/css/prism.css | 139 + sharding-jdbc-doc/static/css/table.css | 11 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20335 bytes .../fonts/glyphicons-halflings-regular.svg | 229 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 41280 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23320 bytes sharding-jdbc-doc/static/js/bootstrap.js | 2114 ++++++ sharding-jdbc-doc/static/js/bootstrap.min.js | 6 + sharding-jdbc-doc/static/js/prism.js | 5 + sharding-jdbc-example/pom.xml | 173 + .../sharding-jdbc-example-jdbc/pom.xml | 9 + .../rdb/sharding/example/jdbc/Main.java | 107 + .../ModuloDatabaseShardingAlgorithm.java | 65 + .../ModuloTableShardingAlgorithm.java | 65 + .../src/main/resources/all_schema.sql | 56 + .../src/main/resources/logback.xml | 21 + .../sharding-jdbc-example-jpa/pom.xml | 44 + .../rdb/sharding/example/jdbc/Main.java | 52 + ...gleKeyModuloDatabaseShardingAlgorithm.java | 66 + ...SingleKeyModuloTableShardingAlgorithm.java | 66 + .../sharding/example/jdbc/entity/Order.java | 71 + .../jdbc/repository/OrderRepository.java | 35 + .../jdbc/repository/OrderRepositoryImpl.java | 62 + .../main/resources/META-INF/jpaContext.xml | 38 + .../resources/META-INF/shardingContext.xml | 69 + .../src/main/resources/all_schema.sql | 56 + .../src/main/resources/logback.xml | 21 + .../sharding-jdbc-example-mybatis/pom.xml | 36 + .../rdb/sharding/example/jdbc/Main.java | 52 + ...gleKeyModuloDatabaseShardingAlgorithm.java | 66 + ...SingleKeyModuloTableShardingAlgorithm.java | 66 + .../sharding/example/jdbc/entity/Order.java | 56 + .../jdbc/repository/OrderRepository.java | 37 + .../META-INF/mybatis/mappers/OrderMapper.xml | 54 + .../resources/META-INF/mybatisContext.xml | 18 + .../resources/META-INF/shardingContext.xml | 69 + .../src/main/resources/all_schema.sql | 56 + .../src/main/resources/logback.xml | 28 + src/resources/dd_checks.xml | 152 + src/resources/dd_pmd.xml | 107 + 499 files changed, 36077 insertions(+), 9 deletions(-) create mode 100644 README.md create mode 100644 pom.xml create mode 100644 sharding-jdbc-core/pom.xml create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/DatabaseType.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/ShardingDataSource.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/ShardingValue.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfiguration.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfigurationConstant.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/BindingTableRule.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataNode.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataSourceRule.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/ShardingRule.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/TableRule.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/MultipleKeysShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingStrategy.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/SingleKeyShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingStrategy.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/MultipleKeysDatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/NoneDatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/SingleKeyDatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/MultipleKeysTableShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/NoneTableShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/SingleKeyTableShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingStrategy.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/DatabaseTypeUnsupportedException.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/SQLParserException.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/ShardingJdbcException.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/ExecuteUnit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/ExecutorEngine.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/MergeUnit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/PreparedStatementExecutor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/StatementExecutor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/AbstractShardingResultSet.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingConnection.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingPreparedStatement.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingStatement.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractConnectionAdapter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractDataSourceAdapter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractPreparedStatementAdapter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractResultSetAdapter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractResultSetGetterAdapter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractStatementAdapter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/WrapperAdapter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractResultSetUpdaterAdapter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationConnection.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationDataSource.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationPreparedStatement.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationResultSet.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationStatement.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/util/JdbcMethodInvocation.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetFactory.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AbstractAggregationUnit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AccumulationAggregationUnit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationInvokeHandler.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationResultSet.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationUnit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationUnitFactory.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationValue.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AvgAggregationUnit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ComparableAggregationUnit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ResultSetAggregationValue.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/AbstractMergerInvokeHandler.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/ResultSetQueryIndex.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/ResultSetUtil.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByInvokeHandler.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByKey.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByResultSet.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByValue.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/iterator/IteratorResultSet.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByResultSet.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByValue.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/metrics/MetricsContext.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLParseEngine.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLParserFactory.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLVisitorRegistry.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/SQLParsedResult.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/AggregationColumn.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/GroupByColumn.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/Limit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/MergeContext.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/OrderByColumn.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/Condition.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/ConditionContext.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/RouteContext.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/SQLBuilder.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/Table.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/ParseContext.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/SQLVisitor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/VisitorLogProxy.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/AbstractMySQLVisitor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLDeleteVisitor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLInsertVisitor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLSelectVisitor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLUpdateVisitor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/OrParser.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/OrVisitor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/AbstractOrASTNode.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/CompositeOrASTNode.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/SimpleOrASTNode.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/RoutingResult.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLExecutionUnit.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLRouteEngine.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLRouteResult.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingDataSource.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingResult.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingTableFactor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingTablesRouter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianDataSource.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianResult.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianTableReference.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianTablesRouter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/MixedTablesRouter.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingDataSource.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingResult.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingTableFactor.java create mode 100644 sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleTableRouter.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/AllTests.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/AbstractDBUnitTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/AllIntegrateTests.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/DataBaseEnvironment.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/ShardingJdbcDatabaseTester.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/AbstractShardingDataBasesOnlyDBUnitTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/DMLShardingDataBasesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectAggregateShardingDataBasesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectGroupByShardingDataBasesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectShardingDataBasesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementDMLShardingDataBasesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementSelectAggregateShardingDataBasesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementSelectShardingDataBasesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/AbstractShardingBothDataBasesAndTablesDBUnitTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/DMLShardingBothDataBasesAndTablesTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectAggregateShardingBothDataBasesAndTablesTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectGroupByShardingBothDataBasesAndTablesTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectShardingBothDataBasesAndTablesTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementDMLShardingBothDataBasesAndTablesTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementSelectAggregateShardingBothDataBasesAndTablesTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementSelectShardingBothDataBasesAndTablesTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/MultipleKeysModuloDatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/MultipleKeysModuloTableShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/SingleKeyModuloDatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/SingleKeyModuloTableShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/AbstractShardingTablesOnlyDBUnitTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/DMLShardingTablesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectAggregateShardingTablesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectGroupByShardingTablesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectShardingTablesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementDMLShardingTablesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementSelectAggregateShardingTablesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementSelectShardingTablesOnlyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/AllApiTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/DatabaseTypeTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/ShardingDataSourceTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/ShardingValueTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfigurationTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/BindingTableRuleTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataSourceRuleTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/ShardingRuleTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/TableRuleTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/fixture/TestDataSource.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingStrategyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingStrategyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/NoneDatabaseShardingAlgorithmTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/fixture/TestMultipleKeysShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/fixture/TestSingleKeyShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/NoneTableShardingAlgorithmTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingStrategyTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/AllJDBCTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingPreparedStatementTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingStatementTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ConnectionAdapterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/DataSourceAdapterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/PreparedStatementAdapterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ResultSetAdapterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ResultSetGetterAdapterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/StatementAdapterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/ResultSetUpdaterAdapterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationConnectionTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationDataSourceTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationPreparedStatementTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationResultSetTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationStatementTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/util/JdbcMethodInvocationTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/AllMergerTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetQueryIndexTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetUtilTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AccumulationAggregationUnitTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationResultSetTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AvgAggregationUnitTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ComparableAggregationUnitTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ResultSetAggregationValueTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/fixture/AbstractUnsupportedOperationMockResultSet.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/fixture/MockResultSet.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByValueTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/iterator/IteratorResultSetTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByResultSetTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByValueTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/metrics/AllMetricsTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/metrics/MetricsContextTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/AbstractBaseParseTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/AllParserTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/UnsupportedParseTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/AggregationColumn.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Assert.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Asserts.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Condition.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/ConditionContext.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/GroupByColumn.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Limit.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/OrderByColumn.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Table.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Value.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLPreparedStatementForOneParameterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLPreparedStatementForTowParametersTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLStatementTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/OrParseTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/result/SQLParsedResultTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/MergeContextTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/AbstractBaseRouteSqlTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/AllRouterTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/DMLTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectBindingTableTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectMixedTablesTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectSingleTableTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingResultTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/fixture/OrderAttrShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/fixture/OrderShardingAlgorithm.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianResultTest.java create mode 100644 sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingResultTest.java create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/or/select_or.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/delete.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/insert.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/select.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/select_limit.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/update.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/select.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/select_limit.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/delete.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/insert.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/multiple_tables_select.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_aggregate.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_group_by.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_limit.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_order_by.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/simple_select.xml create mode 100644 sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/update.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/Empty.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_2.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_3.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_4.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_5.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_6.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_7.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_8.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_9.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_2.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_3.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_4.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_5.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_6.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_7.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_8.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_9.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectBetweenWithSingleTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectGroupByWithBindingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectGroupByWithoutGroupedColumn.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectInWithSingleTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectInWithSingleTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectLimitWithBindingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectLimitWithBindingTableWithoutOffset.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectNoShardingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectAvg.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCount.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectMax.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectMin.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectSum.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectAvg.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectCount.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectMax.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectMin.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectOrderByDesc.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectSum.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_2.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_3.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_4.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_5.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_6.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_7.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_8.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_9.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_2.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_3.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_4.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_5.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_6.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_7.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_8.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_9.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_2.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_3.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_4.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_5.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_6.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_7.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_8.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_9.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_2.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_3.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_4.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_5.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_6.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_7.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_8.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_9.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectBetweenWithSingleTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectGroupByWithBindingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectGroupByWithoutGroupedColumn.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectNoShardingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectWithBingdingTableAndConfigTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectAvg.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCount.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectMax.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectMin.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectSum.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectAvg.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectCount.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectMax.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectMin.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectOrderByDesc.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectSum.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_2.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_3.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_4.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_5.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_6.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_7.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_8.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_9.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_2.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_3.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_4.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_5.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_6.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_7.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_8.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_9.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/delete/db_single.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/insert/db_single.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectBetweenWithSingleTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectGroupByWithBindingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectGroupByWithoutGroupedColumn.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectInWithSingleTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectInWithSingleTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectLimitWithBindingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectNoShardingTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectWithBingdingTableAndConfigTable.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectAvg.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCount.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectMax.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectMin.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectSum.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectAvg.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectCount.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectMax.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectMin.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectOrderByDesc.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectSum.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/update/db_single.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/init/db_single.xml create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/all_schema.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_0.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_1.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_2.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_3.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_4.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_5.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_6.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_7.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_8.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/db/db_9.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_0.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_1.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_2.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_3.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_4.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_5.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_6.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_7.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_8.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_9.sql create mode 100644 sharding-jdbc-core/src/test/resources/integrate/schema/tbl/db_single.sql create mode 100644 sharding-jdbc-core/src/test/resources/logback-test.xml create mode 100644 sharding-jdbc-doc/config.toml create mode 100644 sharding-jdbc-doc/content/img/AlgorithmClass.900.png create mode 100644 sharding-jdbc-doc/content/img/AlgorithmClass.png create mode 100644 sharding-jdbc-doc/content/img/StrategyClass.900.png create mode 100644 sharding-jdbc-doc/content/img/StrategyClass.png create mode 100644 sharding-jdbc-doc/content/img/architecture.png create mode 100644 sharding-jdbc-doc/content/img/execute.png create mode 100644 sharding-jdbc-doc/content/img/intro-bg.svg create mode 100644 sharding-jdbc-doc/content/img/merge.png create mode 100644 sharding-jdbc-doc/content/img/parse.png create mode 100644 sharding-jdbc-doc/content/img/route.png create mode 100644 sharding-jdbc-doc/content/index/index.md create mode 100644 sharding-jdbc-doc/content/post/architecture.md create mode 100644 sharding-jdbc-doc/content/post/features.md create mode 100644 sharding-jdbc-doc/content/post/restriction.md create mode 100644 sharding-jdbc-doc/content/post/roadmap.md create mode 100644 sharding-jdbc-doc/content/post/user_guide.md create mode 100644 sharding-jdbc-doc/layouts/_default/list.html create mode 100644 sharding-jdbc-doc/layouts/_default/single.html create mode 100644 sharding-jdbc-doc/layouts/index.html create mode 100644 sharding-jdbc-doc/layouts/partials/footer.html create mode 100644 sharding-jdbc-doc/layouts/partials/head.html create mode 100644 sharding-jdbc-doc/layouts/partials/header.html create mode 100644 sharding-jdbc-doc/layouts/partials/js.html create mode 100644 sharding-jdbc-doc/layouts/partials/nav.html create mode 100644 sharding-jdbc-doc/layouts/partials/pages.html create mode 100644 sharding-jdbc-doc/layouts/partials/services.html create mode 100644 sharding-jdbc-doc/layouts/partials/template.css create mode 100644 sharding-jdbc-doc/static/css/bootstrap.css create mode 100644 sharding-jdbc-doc/static/css/bootstrap.min.css create mode 100644 sharding-jdbc-doc/static/css/landing-page.css create mode 100644 sharding-jdbc-doc/static/css/prism.css create mode 100644 sharding-jdbc-doc/static/css/table.css create mode 100644 sharding-jdbc-doc/static/fonts/glyphicons-halflings-regular.eot create mode 100644 sharding-jdbc-doc/static/fonts/glyphicons-halflings-regular.svg create mode 100644 sharding-jdbc-doc/static/fonts/glyphicons-halflings-regular.ttf create mode 100644 sharding-jdbc-doc/static/fonts/glyphicons-halflings-regular.woff create mode 100644 sharding-jdbc-doc/static/js/bootstrap.js create mode 100644 sharding-jdbc-doc/static/js/bootstrap.min.js create mode 100644 sharding-jdbc-doc/static/js/prism.js create mode 100644 sharding-jdbc-example/pom.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jdbc/pom.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jdbc/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/Main.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jdbc/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/algorithm/ModuloDatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jdbc/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/algorithm/ModuloTableShardingAlgorithm.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jdbc/src/main/resources/all_schema.sql create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jdbc/src/main/resources/logback.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/pom.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/Main.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/algorithm/SingleKeyModuloDatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/algorithm/SingleKeyModuloTableShardingAlgorithm.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/entity/Order.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/repository/OrderRepository.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/repository/OrderRepositoryImpl.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/resources/META-INF/jpaContext.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/resources/META-INF/shardingContext.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/resources/all_schema.sql create mode 100644 sharding-jdbc-example/sharding-jdbc-example-jpa/src/main/resources/logback.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/pom.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/Main.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/algorithm/SingleKeyModuloDatabaseShardingAlgorithm.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/algorithm/SingleKeyModuloTableShardingAlgorithm.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/entity/Order.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/java/com/dangdang/ddframe/rdb/sharding/example/jdbc/repository/OrderRepository.java create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/resources/META-INF/mybatis/mappers/OrderMapper.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/resources/META-INF/mybatisContext.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/resources/META-INF/shardingContext.xml create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/resources/all_schema.sql create mode 100644 sharding-jdbc-example/sharding-jdbc-example-mybatis/src/main/resources/logback.xml create mode 100644 src/resources/dd_checks.xml create mode 100644 src/resources/dd_pmd.xml diff --git a/.gitignore b/.gitignore index 32858aad3c383..f79a22e5b1f11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,32 @@ -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # +# maven ignore +target/ *.jar *.war -*.ear +*.zip +*.tar +*.tar.gz + +# eclipse ignore +.settings/ +.project +.classpath + +# idea ignore +.idea/ +*.ipr +*.iml +*.iws + +# temp ignore +logs/ +*.doc +*.log +*.cache +*.diff +*.patch +*.tmp + +# system ignore +.DS_Store +Thumbs.db -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..5617aead126fc --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# 主要贡献者 + +* 张亮    [当当](http://www.dangdang.com/) zhangliang@dangdang.com +* 高洪涛 [当当](http://www.dangdang.com/) gaohongtao@dangdang.com +* 曹昊    [当当](http://www.dangdang.com/) caohao@dangdang.com +* 岳令    [当当](http://www.dangdang.com/) yueling@dangdang.com + +**讨论QQ群:**xxx(不限于Sharding-JDBC,包括分布式,数据库相关以及其他互联网技术交流) + +# 简介 +`Sharding-JDBC`是当当应用框架`ddframe`中,关系型数据库模块`dd-rdb`中分离出来的数据库水平扩展框架,即透明化数据库分库分表访问。 + +`Sharding-JDBC`直接封装`JDBC API`,可以理解为增强版的`JDBC`驱动,旧代码迁移成本几乎为零: + +* 可适用于任何基于`java`的`ORM`框架,如:`JPA`, `Hibernate`, `Mybatis`, `Spring JDBC Template`或直接使用`JDBC`。 +* 可基于任何第三方的数据库连接池,如:`DBCP`, `C3P0`, `BoneCP`, `Druid`等。 +* 理论上可支持任意实现`JDBC`规范的数据库。虽然目前仅支持`MySQL`,但已有支持`Oracle`,`SQLServer`,`DB2`等数据库的计划。 + +`Sharding-JDBC`定位为轻量级`java`框架,使用客户端直连数据库,以`jar`包形式提供服务,未使用中间层,无需额外部署,无其他依赖,`DBA`也无需改变原有的运维方式。`SQL`解析使用`Druid`解析器,是目前性能最高的`SQL`解析器。 + +`Sharding-JDBC`功能灵活且全面: + +* 分片策略灵活,可支持`=`,`BETWEEN`,`IN`等多维度分片,也可支持多分片键共用。 +* `SQL`解析功能完善,支持聚合,分组,排序,`Limit`,`OR`等查询,并且支持`Binding Table`以及笛卡尔积的表查询。 + +*** + +以下是常见的分库分表产品和`Sharding-JDBC`的对比: + +| 功能 | Cobar(MyCAT) | Cobar-client | TDDL | Sharding-JDBC | +| ------------- |:-------------:| -------------:| -----------:|---------------:| +| 分库 | 有 | 有 | 未开源 | 有 | +| 分表 | 无 | 无 | 未开源 | 有 | +| 中间层 | 是 | 否 | 否 | 否 | +| ORM支持 | 任意 | 仅MyBatis | 任意 | 任意 | +| 数据库支持 | 仅MySQL | 任意 | 任意 | 任意 | +| 异构语言 | 可 | 仅Java | 仅Java | 仅Java | +| 外部依赖 | 无 | 无 | Diamond | 无 | + +*** + +# 整体架构图 + +![整体架构图1](../img/architecture.png) + +# Quick Start + +## 引入maven依赖 +```xml + + + com.dangdang + sharding-jdbc-core + 1.0.0 + +``` + +## 规则配置 +`Sharding-JDBC`的分库分表通过规则配置描述,请简单浏览配置全貌: +```java + ShardingRule shardingRule = new ShardingRule( + dataSourceRule, + Arrays.asList(tableRule), + new DatabaseShardingStrategy("sharding_column_1", new XXXShardingAlgorithm()), + new TableShardingStrategy("sharding_column_2", new XXXShardingAlgorithm())); +``` +规则配置包括数据源配置、表规则配置、分库策略和分表策略组成。这只是最简单的配置方式,实际使用可更加灵活,如:多分片键,分片策略直接和`tableRule`绑定等。 + +>详细的规则配置请参考[用户指南](post/user_guide) + +## 使用基于ShardingDataSource的JDBC接口 +通过规则配置对象获取`ShardingDataSource`,`ShardingDataSource`实现自`JDBC`的标准接口`DataSource`。然后可通过`DataSource`选择使用原生`JDBC`开发,或者使用`JPA`, `MyBatis`等`ORM`工具。 +以`JDBC`原生实现为例: +```java +DataSource dataSource = new ShardingDataSource(shardingRule); +String sql = "SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.user_id=? AND o.order_id=?"; +try ( + Connection conn = dataSource.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setInt(1, 10); + pstmt.setInt(2, 1001); + try (ResultSet rs = pstmt.executeQuery()) { + while(rs.next()) { + System.out.println(rs.getInt(1)); + System.out.println(rs.getInt(2)); + System.out.println(rs.getInt(3)); + } + } +} +``` + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000..76417a4f94844 --- /dev/null +++ b/pom.xml @@ -0,0 +1,650 @@ + + 4.0.0 + com.dangdang + sharding-jdbc + 1.0.0 + pom + ${project.artifactId} + + + sharding-jdbc-core + + + + 1.7 + [3.0.4,) + UTF-8 + zh_CN + + 1.16.4 + 18.0 + 2.6 + 3.3.2 + 4.1 + 2.4 + 1.6 + 2.3 + 2.3 + 1.8.0 + 3.1 + 4.1.1.RELEASE + 3.2.8 + 1.2.2 + 2.1 + 3.18.2-GA + 1.4 + 0.9.1.2 + 1.0.12 + 0.9.3 + 1.1.3 + 1.1.2 + 1.7.7 + 3.1.0 + 2.0 + 2.4.5 + + 5.1.30 + 1.4.184 + + 4.12 + 2.5.0 + 3.4.2 + 1.2.1 + 1.10.19 + + 3.3 + 2.7 + 2.6 + 2.18.1 + 3.4 + 1.0.0 + 1.4 + 2.8 + 3.4 + 2.10.3 + 2.4 + 2.5 + 2.7 + 3.0.2 + 2.16 + 3.5 + 2.0 + 2.4 + ${java.home}/../bin/javadoc + + + + + + org.projectlombok + lombok + ${lombok.version} + + + com.google.guava + guava + ${guava.version} + + + commons-lang + commons-lang + ${commons-lang.version} + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + commons-io + commons-io + ${commons-io.version} + + + commons-pool + commons-pool + ${commons-pool.version} + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + joda-time + joda-time + ${joda-time.version} + + + org.aspectj + aspectjweaver + ${aspectj.version} + + + cglib + cglib + ${cglib.version} + + + org.springframework + spring-context + ${springframework.version} + + + org.springframework + spring-core + ${springframework.version} + + + org.springframework + spring-beans + ${springframework.version} + + + org.springframework + spring-aspects + ${springframework.version} + + + org.springframework + spring-aop + ${springframework.version} + + + org.springframework + spring-jdbc + ${springframework.version} + + + org.springframework + spring-tx + ${springframework.version} + + + org.springframework + spring-context-support + ${springframework.version} + + + org.springframework + spring-web + ${springframework.version} + + + org.springframework + spring-webmvc + ${springframework.version} + + + org.springframework + spring-expression + ${springframework.version} + + + org.objenesis + objenesis + ${objenesis.version} + + + org.javassist + javassist + ${javassist.version} + + + org.mybatis + mybatis + ${mybatis.version} + + + org.mybatis + mybatis-spring + ${mybatis-spring.version} + + + commons-dbcp + commons-dbcp + ${commons-dbcp.version} + + + commons-pool + commons-pool + + + + + c3p0 + c3p0 + ${c3p0.version} + + + com.alibaba + druid + ${druid.version} + + + com.github.jsqlparser + jsqlparser + ${jsqlparser.version} + + + commons-logging + commons-logging + ${commons-logging.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + org.slf4j + slf4j-api + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + + + io.dropwizard.metrics + metrics-core + ${metrics.version} + + + com.jfinal + jfinal + ${jfinal.version} + + + org.codehaus.groovy + groovy + ${groovy.version} + indy + + + + mysql + mysql-connector-java + ${mysql-connector-java.version} + runtime + + + + junit + junit + ${junit.version} + test + + + org.dbunit + dbunit + ${dbunit.version} + test + + + org.unitils + unitils-core + ${unitils.core.version} + test + + + commons-lang + commons-lang + + + + + org.springframework + spring-test + ${springframework.version} + test + + + com.github.springtestdbunit + spring-test-dbunit + ${spring-test-dbunit.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.hamcrest + hamcrest-core + + + + + com.h2database + h2 + ${h2.version} + test + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + ${java.version} + ${java.version} + + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.apache.maven.plugins + maven-surefire-report-plugin + ${maven-surefire-report-plugin.version} + + once + -XX:-UseSplitVerifier + + + + org.apache.maven.plugins + maven-site-plugin + ${maven-site-plugin.version} + + ${project.build.locale} + + + + org.eclipse.m2e + lifecycle-mapping + ${lifecycle-mapping.version} + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.0.0,) + + enforce + + + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + [1.0.0,) + + descriptor + + + + + + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven-plugin-plugin.version} + + true + + + + default-descriptor + process-classes + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + true + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-banned-dependencies + + enforce + + + + + ${maven.version.range} + + + ${java.version} + + + true + + + + + + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + ${maven-project-info-reports-plugin.version} + + false + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-jxr-plugin + ${maven-jxr-plugin.version} + + true + + + + org.codehaus.mojo + cobertura-maven-plugin + ${cobertura-maven-plugin.version} + + true + ${project.build.sourceEncoding} + true + + + com/dangdang/**/*Test.class + com/dangdang/**/Test*.class + + + + + + org.codehaus.mojo + findbugs-maven-plugin + ${findbugs-maven-plugin.version} + + true + Max + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + src/main/resources/dd_checks.xml + + + + org.apache.maven.plugins + maven-pmd-plugin + ${maven-pmd-plugin.version} + + true + true + ${project.build.sourceEncoding} + ${java.version} + + + + org.codehaus.mojo + jdepend-maven-plugin + ${jdepend-maven-plugin.version} + + + org.codehaus.mojo + taglist-maven-plugin + ${taglist-maven-plugin.version} + + true + + + + + + + + henryyan-mavenrepo + https://maven.alfresco.com/nexus/content/groups/public/ + + + java-net + https://oss.sonatype.org/content/repositories/snapshots/ + + false + + + false + + + + terracotta-releases + http://terracotta.org/download/ + + false + + + false + + + + + http://www.dangdang.com + RDB Sharding + + + Apache Licene 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + scm:https://github.com/dangdangdotcom/sharding-jdbc + scm:https://github.com/dangdangdotcom/sharding-jdbc + https://github.com/dangdangdotcom/sharding-jdbc + + + + + Zhangliang + zhangliang@dangdang.com + + + + + + zhangliang + ZhangLiang + zhangliang@dangdang.com + 8 + + + \ No newline at end of file diff --git a/sharding-jdbc-core/pom.xml b/sharding-jdbc-core/pom.xml new file mode 100644 index 0000000000000..4caaa00714b68 --- /dev/null +++ b/sharding-jdbc-core/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + + com.dangdang + sharding-jdbc + 1.0.0 + + sharding-jdbc-core + ${project.artifactId} + + + org.projectlombok + lombok + + + com.google.guava + guava + + + org.apache.commons + commons-collections4 + + + + junit + junit + + + org.mockito + mockito-core + + + org.dbunit + dbunit + + + com.h2database + h2 + + + commons-dbcp + commons-dbcp + test + + + commons-pool + commons-pool + test + + + mysql + mysql-connector-java + + + com.alibaba + druid + + + org.slf4j + slf4j-api + + + cglib + cglib + + + io.dropwizard.metrics + metrics-core + + + ch.qos.logback + logback-classic + test + + + org.slf4j + jcl-over-slf4j + test + + + \ No newline at end of file diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/DatabaseType.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/DatabaseType.java new file mode 100644 index 0000000000000..2d0c3fe739ab6 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/DatabaseType.java @@ -0,0 +1,44 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api; + +import com.dangdang.ddframe.rdb.sharding.exception.DatabaseTypeUnsupportedException; + +/** + * 支持的数据库类型. + * + * @author zhangliang + */ +public enum DatabaseType { + + H2, MySQL, Oracle, SQLServer, DB2; + + /** + * 获取数据库类型枚举. + * + * @param databaseProductName 数据库类型 + * @return 数据库类型枚举 + */ + public static DatabaseType valueFrom(final String databaseProductName) { + try { + return DatabaseType.valueOf(databaseProductName); + } catch (final IllegalArgumentException ex) { + throw new DatabaseTypeUnsupportedException(databaseProductName); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/ShardingDataSource.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/ShardingDataSource.java new file mode 100644 index 0000000000000..45ee1ba86ff74 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/ShardingDataSource.java @@ -0,0 +1,94 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Properties; + +import javax.sql.DataSource; + +import com.dangdang.ddframe.rdb.sharding.api.config.ShardingConfiguration; +import com.dangdang.ddframe.rdb.sharding.api.config.ShardingConfigurationConstant; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractDataSourceAdapter; +import com.dangdang.ddframe.rdb.sharding.metrics.MetricsContext; +import com.google.common.base.Preconditions; + +/** + * 支持分片的数据源. + * + * @author zhangliang + */ +public class ShardingDataSource extends AbstractDataSourceAdapter { + + private final ShardingRule shardingRule; + + private final DatabaseMetaData databaseMetaData; + + private final ShardingConfiguration configuration; + + private final MetricsContext metricsContext; + + public ShardingDataSource(final ShardingRule shardingRule) { + this(shardingRule, new Properties()); + } + + public ShardingDataSource(final ShardingRule shardingRule, final Properties props) { + this.shardingRule = shardingRule; + databaseMetaData = getDatabaseMetaData(); + configuration = new ShardingConfiguration(props); + metricsContext = new MetricsContext(configuration.getConfig(ShardingConfigurationConstant.METRICS_ENABLE, boolean.class), + configuration.getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD, long.class), + configuration.getConfig(ShardingConfigurationConstant.METRICS_PACKAGE_NAME, String.class)); + } + + private DatabaseMetaData getDatabaseMetaData() { + String databaseProductName = null; + DatabaseMetaData result = null; + for (DataSource each : shardingRule.getDataSourceRule().getDataSources()) { + String databaseProductNameInEach; + DatabaseMetaData metaDataInEach; + try { + metaDataInEach = each.getConnection().getMetaData(); + databaseProductNameInEach = metaDataInEach.getDatabaseProductName(); + } catch (final SQLException ex) { + throw new ShardingJdbcException("Can not get data source DatabaseProductName", ex); + } + Preconditions.checkState(null == databaseProductName || databaseProductName.equals(databaseProductNameInEach), + String.format("Database type inconsistent with '%s' and '%s'", databaseProductName, databaseProductNameInEach)); + databaseProductName = databaseProductNameInEach; + result = metaDataInEach; + } + return result; + } + + @Override + public ShardingConnection getConnection() throws SQLException { + metricsContext.register(); + return new ShardingConnection(shardingRule, databaseMetaData); + } + + @Override + public final Connection getConnection(final String username, final String password) throws SQLException { + return getConnection(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/ShardingValue.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/ShardingValue.java new file mode 100644 index 0000000000000..31647efab58b5 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/ShardingValue.java @@ -0,0 +1,88 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api; + +import java.util.Collection; +import java.util.Collections; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +import com.google.common.collect.Range; + +/** + * 分片值. + * + *

+ * 目前支持{@code =, IN, BETWEEN}; + * 不支持{@code , >, <-, >=, LIKE, NOT, NOT IN}. + *

+ * + * @author zhangliang + */ +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@ToString +public final class ShardingValue> { + + private final String columnName; + + private final T value; + + private final Collection values; + + private final Range valueRange; + + public ShardingValue(final String columnName, final T value) { + this(columnName, value, Collections.emptyList(), null); + } + + public ShardingValue(final String columnName, final Collection values) { + this(columnName, null, values, null); + } + + public ShardingValue(final String columnName, final Range valueRange) { + this(columnName, null, Collections.emptyList(), valueRange); + } + + /** + * 获取分片值类型. + * + * @return 分片值类型 + */ + public ShardingValueType getType() { + if (null != value) { + return ShardingValueType.SINGLE; + } + if (!values.isEmpty()) { + return ShardingValueType.LIST; + } + return ShardingValueType.RANGE; + } + + /** + * 分片值类型. + * + * @author zhangliang + */ + public enum ShardingValueType { + SINGLE, LIST, RANGE + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfiguration.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfiguration.java new file mode 100644 index 0000000000000..2d963d9e9e6bf --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfiguration.java @@ -0,0 +1,74 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.config; + +import java.util.Properties; + +import lombok.RequiredArgsConstructor; + +/** + * 配置类. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +public final class ShardingConfiguration { + + private final Properties props; + + /** + * 获取字符串类型的配置. + * + * @param key 配置项的键值 + * @return 配置值 + */ + public String getConfig(final ShardingConfigurationConstant key) { + return props.getProperty(key.getKey(), key.getDefaultValue()); + } + + /** + * 获取制定类型的配置. + * + * @param key 配置项的键值 + * @param type 配置值的类型 + * @return 配置值 + */ + public T getConfig(final ShardingConfigurationConstant key, final Class type) { + return convert(getConfig(key), type); + } + + @SuppressWarnings("unchecked") + private T convert(final String value, final Class convertType) { + if (Boolean.class == convertType || boolean.class == convertType) { + return (T) Boolean.valueOf(value); + } + if (Integer.class == convertType || int.class == convertType) { + return (T) Integer.valueOf(value); + } + if (Long.class == convertType || long.class == convertType) { + return (T) Long.valueOf(value); + } + if (Double.class == convertType || double.class == convertType) { + return (T) Double.valueOf(value); + } + if (String.class == convertType) { + return (T) value; + } + throw new UnsupportedOperationException(String.format("unsupported config data type %s", convertType.getName())); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfigurationConstant.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfigurationConstant.java new file mode 100644 index 0000000000000..d037e8b7e4b3d --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfigurationConstant.java @@ -0,0 +1,53 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 配置项常量. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +@Getter +public enum ShardingConfigurationConstant { + + /** + * 度量输出周期. + * 单位为秒 + * 默认值:30秒 + */ + METRICS_SECOND_PERIOD("metrics.second.period", "1"), + + /** + * 是否开启度量采集. + * 默认值: 不开启 + */ + METRICS_ENABLE("metrics.enable", Boolean.FALSE.toString()), + + /** + * 度量输出在日志中的标识名称. + */ + METRICS_PACKAGE_NAME("metrics.package.name", "com.dangdang.ddframe.rdb.sharding.metrics"); + + private final String key; + + private final String defaultValue; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/BindingTableRule.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/BindingTableRule.java new file mode 100644 index 0000000000000..57ba649be0cc7 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/BindingTableRule.java @@ -0,0 +1,90 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.rule; + +import java.util.Collection; +import java.util.List; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Binding表规则配置对象. + * + * @author zhangliang + */ +@RequiredArgsConstructor +@Getter +public final class BindingTableRule { + + private final List tableRules; + + /** + * 判断此绑定表规则是否包含该逻辑表. + * + * @param logicTableName 逻辑表名称 + * @return 此绑定表规则是否包含该逻辑表 + */ + public boolean hasLogicTable(final String logicTableName) { + for (TableRule each : tableRules) { + if (each.getLogicTable().equals(logicTableName)) { + return true; + } + } + return false; + } + + /** + * 根据其他Binding表真实表名称获取相应的真实Binding表名称. + * + * @param dataSource 数据源名称 + * @param logicTable 逻辑表名称 + * @param otherActualTable 其他真实Binding表名称 + * @return 真实Binding表名称 + */ + public String getBindingActualTable(final String dataSource, final String logicTable, final String otherActualTable) { + int index = -1; + for (TableRule each : tableRules) { + index = each.findActualTableIndex(dataSource, otherActualTable); + if (-1 != index) { + break; + } + } + Preconditions.checkState(-1 != index, String.format("Actual table [%s].[%s] is not in table config", dataSource, otherActualTable)); + for (TableRule each : tableRules) { + if (each.getLogicTable().equals(logicTable)) { + return each.getActualTables().get(index).getTableName(); + } + } + throw new IllegalStateException(String.format("Cannot find binding actual table, data source: %s, logic table: %s, other actual table: %s", dataSource, logicTable, otherActualTable)); + } + + Collection getAllLogicTables() { + return Lists.transform(tableRules, new Function() { + + @Override + public String apply(final TableRule input) { + return input.getLogicTable(); + } + }); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataNode.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataNode.java new file mode 100644 index 0000000000000..187639a2dad03 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataNode.java @@ -0,0 +1,39 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.rule; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * 分库分表数据单元. + * + * @author zhangliang + */ +@RequiredArgsConstructor +@Getter +@EqualsAndHashCode +@ToString +public final class DataNode { + + private final String dataSourceName; + + private final String tableName; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataSourceRule.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataSourceRule.java new file mode 100644 index 0000000000000..9064f36f13beb --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataSourceRule.java @@ -0,0 +1,69 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.rule; + +import java.util.Collection; +import java.util.Map; + +import javax.sql.DataSource; + +import com.google.common.base.Preconditions; + +/** + * 数据源配置对象. + * + * @author zhangliang + */ +public final class DataSourceRule { + + private final Map dataSourceMap; + + public DataSourceRule(final Map dataSourceMap) { + Preconditions.checkNotNull(dataSourceMap, "Must have one data source at least."); + Preconditions.checkState(!dataSourceMap.isEmpty(), "Must have one data source at least."); + this.dataSourceMap = dataSourceMap; + } + + /** + * 获取数据源实例. + * + * @param name 数据源名称 + * @return 数据源实例 + */ + public DataSource getDataSource(final String name) { + return dataSourceMap.get(name); + } + + /** + * 获取所有数据源名称. + * + * @return 所有数据源名称 + */ + public Collection getDataSourceNames() { + return dataSourceMap.keySet(); + } + + /** + * 获取所有数据源. + * + * @return 所有数据源 + */ + public Collection getDataSources() { + return dataSourceMap.values(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/ShardingRule.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/ShardingRule.java new file mode 100644 index 0000000000000..50c3c6270317d --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/ShardingRule.java @@ -0,0 +1,213 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.rule; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.NoneTableShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 分库分表规则配置对象. + * + * @author zhangliang + */ +@AllArgsConstructor +@Getter +public final class ShardingRule { + + private final DataSourceRule dataSourceRule; + + private final Collection tableRules; + + private Collection bindingTableRules; + + private DatabaseShardingStrategy databaseShardingStrategy; + + private TableShardingStrategy tableShardingStrategy; + + public ShardingRule(final DataSourceRule dataSourceRule, final Collection tableRules) { + this(dataSourceRule, tableRules, Collections.emptyList(), + new DatabaseShardingStrategy(Collections.emptyList(), new NoneDatabaseShardingAlgorithm()), + new TableShardingStrategy(Collections.emptyList(), new NoneTableShardingAlgorithm())); + } + + public ShardingRule(final DataSourceRule dataSourceRule, final Collection tableRules, final Collection bindingTableRules) { + this(dataSourceRule, tableRules, bindingTableRules, + new DatabaseShardingStrategy(Collections.emptyList(), new NoneDatabaseShardingAlgorithm()), + new TableShardingStrategy(Collections.emptyList(), new NoneTableShardingAlgorithm())); + } + + public ShardingRule(final DataSourceRule dataSourceRule, final Collection tableRules, final DatabaseShardingStrategy databaseShardingStrategy) { + this(dataSourceRule, tableRules, Collections.emptyList(), + databaseShardingStrategy, new TableShardingStrategy(Collections.emptyList(), new NoneTableShardingAlgorithm())); + } + + public ShardingRule(final DataSourceRule dataSourceRule, final Collection tableRules, final TableShardingStrategy tableShardingStrategy) { + this(dataSourceRule, tableRules, Collections.emptyList(), + new DatabaseShardingStrategy(Collections.emptyList(), new NoneDatabaseShardingAlgorithm()), tableShardingStrategy); + } + + public ShardingRule(final DataSourceRule dataSourceRule, final Collection tableRules, + final DatabaseShardingStrategy databaseShardingStrategy, final TableShardingStrategy tableShardingStrategy) { + this(dataSourceRule, tableRules, Collections.emptyList(), databaseShardingStrategy, tableShardingStrategy); + } + + /** + * 根据逻辑表名称查找分片规则. + * + * @param logicTableName 逻辑表名称 + * @return 该逻辑表的分片规则 + */ + public Optional findTableRule(final String logicTableName) { + for (TableRule each : tableRules) { + if (each.getLogicTable().equals(logicTableName)) { + return Optional.of(each); + } + } + return Optional.absent(); + } + + /** + * 获取数据库分片策略. + * + *

+ * 根据表规则配置对象获取分片策略, 如果获取不到则获取默认分片策略. + *

+ * + * @param tableRule 表规则配置对象 + * @return 数据库分片策略 + */ + public DatabaseShardingStrategy getDatabaseShardingStrategy(final TableRule tableRule) { + DatabaseShardingStrategy result = tableRule.getDatabaseShardingStrategy(); + if (null == result) { + result = databaseShardingStrategy; + } + Preconditions.checkNotNull(result, "no database sharding strategy"); + return result; + } + + /** + * 获取表分片策略. + * + *

+ * 根据表规则配置对象获取分片策略, 如果获取不到则获取默认分片策略. + *

+ * + * @param tableRule 表规则配置对象 + * @return 表分片策略 + */ + public TableShardingStrategy getTableShardingStrategy(final TableRule tableRule) { + TableShardingStrategy result = tableRule.getTableShardingStrategy(); + if (null == result) { + result = tableShardingStrategy; + } + Preconditions.checkNotNull(result, "no table sharding strategy"); + return result; + } + + /** + * 根据逻辑表名称获取binding表配置的逻辑表名称集合. + * + * @param logicTable 逻辑表名称 + * @return binding表配置的逻辑表名称集合 + */ + public Optional getBindingTableRule(final String logicTable) { + if (null == bindingTableRules) { + return Optional.absent(); + } + for (BindingTableRule each : bindingTableRules) { + if (each.hasLogicTable(logicTable)) { + return Optional.of(each); + } + } + return Optional.absent(); + } + + /** + * 过滤出所有的Binding表名称. + * + * @param logicTables 逻辑表名称集合 + * @return 所有的Binding表名称集合 + */ + public Collection filterAllBindingTables(final Collection logicTables) { + if (logicTables.isEmpty()) { + return Collections.emptyList(); + } + Optional bindingTableRule = Optional.absent(); + for (String each : logicTables) { + bindingTableRule = getBindingTableRule(each); + if (bindingTableRule.isPresent()) { + break; + } + } + if (!bindingTableRule.isPresent()) { + return Collections.emptyList(); + } + Collection result = new ArrayList<>(bindingTableRule.get().getAllLogicTables()); + result.retainAll(logicTables); + return result; + } + + /** + * 判断逻辑表名称集合是否全部属于Binding表. + * + * @param logicTables 逻辑表名称集合 + * @return 是否全部属于Binding表 + */ + public boolean isAllBindingTable(final Collection logicTables) { + Collection bindingTables = filterAllBindingTables(logicTables); + return !bindingTables.isEmpty() && bindingTables.containsAll(logicTables); + } + + /** + * 获取所有的分片列名. + * + * @return 分片列名集合 + */ + // TODO 目前使用分片列名称, 为了进一步提升解析性能,应考虑使用表名 + 列名 + public Collection getAllShardingColumns() { + Set result = new HashSet<>(); + if (null != databaseShardingStrategy) { + result.addAll(databaseShardingStrategy.getShardingColumns()); + } + if (null != tableShardingStrategy) { + result.addAll(tableShardingStrategy.getShardingColumns()); + } + for (TableRule each : tableRules) { + if (null != each.getDatabaseShardingStrategy()) { + result.addAll(each.getDatabaseShardingStrategy().getShardingColumns()); + } + if (null != each.getTableShardingStrategy()) { + result.addAll(each.getTableShardingStrategy().getShardingColumns()); + } + } + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/TableRule.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/TableRule.java new file mode 100644 index 0000000000000..32d9611d3ffe5 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/rule/TableRule.java @@ -0,0 +1,134 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.rule; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; +import com.google.common.base.Splitter; + +/** + * 表规则配置对象. + * + * @author zhangliang + */ +@AllArgsConstructor +@RequiredArgsConstructor +@Getter +@ToString +public final class TableRule { + + private final String logicTable; + + private final List actualTables; + + private DatabaseShardingStrategy databaseShardingStrategy; + + private TableShardingStrategy tableShardingStrategy; + + public TableRule(final String logicTable, final List actualTables, final DataSourceRule dataSourceRule, + final DatabaseShardingStrategy databaseShardingStrategy, final TableShardingStrategy tableShardingStrategy) { + this(logicTable, new ArrayList(actualTables.size() * dataSourceRule.getDataSourceNames().size()), databaseShardingStrategy, tableShardingStrategy); + generateDataNodes(actualTables, dataSourceRule); + } + + public TableRule(final String logicTable, final List actualTables, final DataSourceRule dataSourceRule) { + this(logicTable, actualTables, dataSourceRule, null, null); + } + + public TableRule(final String logicTable, final List actualTables, final DataSourceRule dataSourceRule, final DatabaseShardingStrategy databaseShardingStrategy) { + this(logicTable, actualTables, dataSourceRule, databaseShardingStrategy, null); + } + + public TableRule(final String logicTable, final List actualTables, final DataSourceRule dataSourceRule, final TableShardingStrategy tableShardingStrategy) { + this(logicTable, actualTables, dataSourceRule, null, tableShardingStrategy); + } + + private void generateDataNodes(final List actualTables, final DataSourceRule dataSourceRule) { + for (String actualTable : actualTables) { + if (actualTable.contains(".")) { + List actualDatabaseTable = Splitter.on(".").splitToList(actualTable); + this.actualTables.add(new DataNode(actualDatabaseTable.get(0), actualDatabaseTable.get(1))); + } else { + for (String dataSourceName : dataSourceRule.getDataSourceNames()) { + this.actualTables.add(new DataNode(dataSourceName, actualTable)); + } + } + } + } + + /** + * 根据数据源名称过滤获取真实数据单元. + * + * @param targetDataSources 数据源名称集合 + * @param targetTables 真实表名称集合 + * @return 真实数据单元 + */ + public Collection getActualDataNodes(final Collection targetDataSources, final Collection targetTables) { + Collection result = new LinkedHashSet<>(actualTables.size()); + for (DataNode each : actualTables) { + if (targetDataSources.contains(each.getDataSourceName()) && targetTables.contains(each.getTableName())) { + result.add(each); + } + } + return result; + } + + /** + * 根据数据源名称过滤获取真实表名称. + * + * @param targetDataSources 数据源名称 + * @return 真实表名称 + */ + public Collection getActualTableNames(final Collection targetDataSources) { + Collection result = new LinkedHashSet<>(actualTables.size()); + for (DataNode each : actualTables) { + if (targetDataSources.contains(each.getDataSourceName())) { + result.add(each.getTableName()); + } + } + return result; + } + + /** + * 根据数据源和真实表名称查找真实表顺序. + * + * @param dataSourceName 数据源名称 + * @param actualTableName 真实表名称 + * @return 真实表顺序 + */ + public int findActualTableIndex(final String dataSourceName, final String actualTableName) { + int result = 0; + for (DataNode each : actualTables) { + if (each.getDataSourceName().equals(dataSourceName) && each.getTableName().equals(actualTableName)) { + return result; + } + result++; + } + return -1; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/MultipleKeysShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/MultipleKeysShardingAlgorithm.java new file mode 100644 index 0000000000000..eeed9b410330c --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/MultipleKeysShardingAlgorithm.java @@ -0,0 +1,39 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.common; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; + +/** + * 多片键分片法接口. + * + * @author zhangliang + */ +public interface MultipleKeysShardingAlgorithm extends ShardingAlgorithm { + + /** + * 根据分片值计算分片结果名称集合. + * + * @param availableTargetNames 所有的可用目标名称集合, 一般是数据源或表名称 + * @param shardingValues 分片值集合 + * @return 分片后指向的目标名称集合, 一般是数据源或表名称 + */ + Collection doSharding(Collection availableTargetNames, Collection> shardingValues); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingAlgorithm.java new file mode 100644 index 0000000000000..f8402baa26d6f --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingAlgorithm.java @@ -0,0 +1,26 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.common; + +/** + * 分片算法的标识接口. + * + * @author zhangliang + */ +public interface ShardingAlgorithm { +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingStrategy.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingStrategy.java new file mode 100644 index 0000000000000..970877f3b8a8d --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingStrategy.java @@ -0,0 +1,76 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.common; + +import java.util.Arrays; +import java.util.Collection; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; + +/** + * 分片策略. + * + * @author zhangliang + */ +@RequiredArgsConstructor +public class ShardingStrategy { + + @Getter + private final Collection shardingColumns; + + private final ShardingAlgorithm shardingAlgorithm; + + public ShardingStrategy(final String shardingColumn, final ShardingAlgorithm shardingAlgorithm) { + this(Arrays.asList(shardingColumn), shardingAlgorithm); + } + + /** + * 根据分片值计算数据源名称集合. + * + * @param availableTargetNames 所有的可用数据源名称集合 + * @param shardingValues 分库片值集合 + * @return 分库后指向的数据源名称集合 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Collection doSharding(final Collection availableTargetNames, final Collection>> shardingValues) { + if (shardingValues.isEmpty()) { + return availableTargetNames; + } + if (shardingAlgorithm instanceof SingleKeyShardingAlgorithm) { + SingleKeyShardingAlgorithm singleKeyShardingAlgorithm = (SingleKeyShardingAlgorithm) shardingAlgorithm; + ShardingValue shardingValue = shardingValues.iterator().next(); + switch (shardingValue.getType()) { + case SINGLE: + return Arrays.asList(singleKeyShardingAlgorithm.doEqualSharding(availableTargetNames, shardingValue)); + case LIST: + return singleKeyShardingAlgorithm.doInSharding(availableTargetNames, shardingValue); + case RANGE: + return singleKeyShardingAlgorithm.doBetweenSharding(availableTargetNames, shardingValue); + default: + throw new UnsupportedOperationException(shardingValue.getType().getClass().getName()); + } + } + if (shardingAlgorithm instanceof MultipleKeysShardingAlgorithm) { + return ((MultipleKeysShardingAlgorithm) shardingAlgorithm).doSharding(availableTargetNames, shardingValues); + } + throw new UnsupportedOperationException(shardingAlgorithm.getClass().getName()); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/SingleKeyShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/SingleKeyShardingAlgorithm.java new file mode 100644 index 0000000000000..0f0efacf9f47b --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/SingleKeyShardingAlgorithm.java @@ -0,0 +1,59 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.common; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; + +/** + * 单片键分片法接口. + * + * @author zhangliang + * + * @param 片键类型 + */ +public interface SingleKeyShardingAlgorithm> extends ShardingAlgorithm { + + /** + * 根据分片值和SQL的=运算符计算分片结果名称集合. + * + * @param availableTargetNames 所有的可用目标名称集合, 一般是数据源或表名称 + * @param shardingValue 分片值 + * @return 分片后指向的目标名称, 一般是数据源或表名称 + */ + String doEqualSharding(Collection availableTargetNames, ShardingValue shardingValue); + + /** + * 根据分片值和SQL的IN运算符计算分片结果名称集合. + * + * @param availableTargetNames 所有的可用目标名称集合, 一般是数据源或表名称 + * @param shardingValue 分片值 + * @return 分片后指向的目标名称集合, 一般是数据源或表名称 + */ + Collection doInSharding(Collection availableTargetNames, ShardingValue shardingValue); + + /** + * 根据分片值和SQL的BETWEEN运算符计算分片结果名称集合. + * + * @param availableTargetNames 所有的可用目标名称集合, 一般是数据源或表名称 + * @param shardingValue 分片值 + * @return 分片后指向的目标名称集合, 一般是数据源或表名称 + */ + Collection doBetweenSharding(Collection availableTargetNames, ShardingValue shardingValue); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingAlgorithm.java new file mode 100644 index 0000000000000..3e0a28f0f882f --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingAlgorithm.java @@ -0,0 +1,28 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.database; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.ShardingAlgorithm; + +/** + * 分库算法接口. + * + * @author zhangliang + */ +public interface DatabaseShardingAlgorithm extends ShardingAlgorithm { +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingStrategy.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingStrategy.java new file mode 100644 index 0000000000000..687610b923c4e --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingStrategy.java @@ -0,0 +1,38 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.database; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.ShardingStrategy; + +/** + * 分库策略. + * + * @author zhangliang + */ +public final class DatabaseShardingStrategy extends ShardingStrategy { + + public DatabaseShardingStrategy(final String shardingColumn, final SingleKeyDatabaseShardingAlgorithm databaseShardingAlgorithm) { + super(shardingColumn, databaseShardingAlgorithm); + } + + public DatabaseShardingStrategy(final Collection shardingColumns, final MultipleKeysDatabaseShardingAlgorithm databaseShardingAlgorithm) { + super(shardingColumns, databaseShardingAlgorithm); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/MultipleKeysDatabaseShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/MultipleKeysDatabaseShardingAlgorithm.java new file mode 100644 index 0000000000000..3e32a715cd28a --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/MultipleKeysDatabaseShardingAlgorithm.java @@ -0,0 +1,28 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.database; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.MultipleKeysShardingAlgorithm; + +/** + * 多片键分库算法接口. + * + * @author zhangliang + */ +public interface MultipleKeysDatabaseShardingAlgorithm extends MultipleKeysShardingAlgorithm, DatabaseShardingAlgorithm { +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/NoneDatabaseShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/NoneDatabaseShardingAlgorithm.java new file mode 100644 index 0000000000000..44cd0472dfff8 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/NoneDatabaseShardingAlgorithm.java @@ -0,0 +1,50 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.database; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; + +/** + * 无需分库的分片算法. + * + * @author zhangliang + */ +public final class NoneDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm, MultipleKeysDatabaseShardingAlgorithm { + + @Override + public Collection doSharding(final Collection availableTargetNames, final Collection> shardingValues) { + return availableTargetNames; + } + + @Override + public String doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + return availableTargetNames.isEmpty() ? null : availableTargetNames.iterator().next(); + } + + @Override + public Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + return availableTargetNames; + } + + @Override + public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + return availableTargetNames; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/SingleKeyDatabaseShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/SingleKeyDatabaseShardingAlgorithm.java new file mode 100644 index 0000000000000..40b65195b5cc0 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/SingleKeyDatabaseShardingAlgorithm.java @@ -0,0 +1,28 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.database; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.SingleKeyShardingAlgorithm; + +/** + * 单分片键的分库算法接口. + * + * @author zhangliang + */ +public interface SingleKeyDatabaseShardingAlgorithm> extends SingleKeyShardingAlgorithm, DatabaseShardingAlgorithm { +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/MultipleKeysTableShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/MultipleKeysTableShardingAlgorithm.java new file mode 100644 index 0000000000000..207964265ee73 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/MultipleKeysTableShardingAlgorithm.java @@ -0,0 +1,28 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.table; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.MultipleKeysShardingAlgorithm; + +/** + * 多片键分表算法接口. + * + * @author zhangliang + */ +public interface MultipleKeysTableShardingAlgorithm extends MultipleKeysShardingAlgorithm, TableShardingAlgorithm { +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/NoneTableShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/NoneTableShardingAlgorithm.java new file mode 100644 index 0000000000000..a279d895d2332 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/NoneTableShardingAlgorithm.java @@ -0,0 +1,50 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.table; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; + +/** + * 无需分表的分片算法. + * + * @author zhangliang + */ +public final class NoneTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm, MultipleKeysTableShardingAlgorithm { + + @Override + public Collection doSharding(final Collection availableTableNames, final Collection> shardingValues) { + return availableTableNames; + } + + @Override + public String doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + return availableTargetNames.isEmpty() ? null : availableTargetNames.iterator().next(); + } + + @Override + public Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + return availableTargetNames; + } + + @Override + public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + return availableTargetNames; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/SingleKeyTableShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/SingleKeyTableShardingAlgorithm.java new file mode 100644 index 0000000000000..5eb5ce5c26cb9 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/SingleKeyTableShardingAlgorithm.java @@ -0,0 +1,28 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.table; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.SingleKeyShardingAlgorithm; + +/** + * 单分片键的分表算法接口. + * + * @author zhangliang + */ +public interface SingleKeyTableShardingAlgorithm> extends SingleKeyShardingAlgorithm, TableShardingAlgorithm { +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingAlgorithm.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingAlgorithm.java new file mode 100644 index 0000000000000..25da6aed6f3dd --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingAlgorithm.java @@ -0,0 +1,28 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.table; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.ShardingAlgorithm; + +/** + * 分表算法接口. + * + * @author zhangliang + */ +public interface TableShardingAlgorithm extends ShardingAlgorithm { +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingStrategy.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingStrategy.java new file mode 100644 index 0000000000000..195ff34c18bb6 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingStrategy.java @@ -0,0 +1,38 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.table; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.ShardingStrategy; + +/** + * 分表策略. + * + * @author zhangliang + */ +public final class TableShardingStrategy extends ShardingStrategy { + + public TableShardingStrategy(final String shardingColumn, final SingleKeyTableShardingAlgorithm tableShardingAlgorithm) { + super(shardingColumn, tableShardingAlgorithm); + } + + public TableShardingStrategy(final Collection shardingColumns, final MultipleKeysTableShardingAlgorithm tableShardingAlgorithm) { + super(shardingColumns, tableShardingAlgorithm); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/DatabaseTypeUnsupportedException.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/DatabaseTypeUnsupportedException.java new file mode 100644 index 0000000000000..0c8f370491539 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/DatabaseTypeUnsupportedException.java @@ -0,0 +1,34 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.exception; + +/** + * 不支持的数据库抛出的异常. + * + * @author zhangliang + */ +public class DatabaseTypeUnsupportedException extends RuntimeException { + + private static final long serialVersionUID = -7807395469148925091L; + + private static final String MESSAGE = "Can not support database type [%s]."; + + public DatabaseTypeUnsupportedException(final String databaseType) { + super(String.format(MESSAGE, databaseType)); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/SQLParserException.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/SQLParserException.java new file mode 100644 index 0000000000000..21053e8068860 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/SQLParserException.java @@ -0,0 +1,32 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.exception; + +/** + * SQL解析异常. + * + * @author gaohongtao + */ +public final class SQLParserException extends ShardingJdbcException { + + private static final long serialVersionUID = -1498980479829506655L; + + public SQLParserException(final String message, final Object... args) { + super(String.format(message, args)); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/ShardingJdbcException.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/ShardingJdbcException.java new file mode 100644 index 0000000000000..a3ad81b16c74c --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/exception/ShardingJdbcException.java @@ -0,0 +1,36 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.exception; + +/** + * JDBC分片抛出的异常基类. + * + * @author zhangliang + */ +public class ShardingJdbcException extends RuntimeException { + + private static final long serialVersionUID = -1343739516839252250L; + + public ShardingJdbcException(final String errorMessage, final Object... args) { + super(String.format(errorMessage, args)); + } + + public ShardingJdbcException(final Exception cause) { + super(cause); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/ExecuteUnit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/ExecuteUnit.java new file mode 100644 index 0000000000000..70a9e4472f180 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/ExecuteUnit.java @@ -0,0 +1,35 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.executor; + +/** + * 执行单元. + * + * @author gaohongtao + */ +public interface ExecuteUnit { + + /** + * 执行任务. + * + * @param input 输入待处理数据 + * @return 返回处理结果 + * @throws Exception 执行期异常 + */ + O execute(I input) throws Exception; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/ExecutorEngine.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/ExecutorEngine.java new file mode 100644 index 0000000000000..1defea8912904 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/ExecutorEngine.java @@ -0,0 +1,116 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.executor; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; + +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 多线程执行框架. + * + * @author gaohongtao + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Slf4j +public final class ExecutorEngine { + + /** + * 多线程执行任务. + * + * @param inputs 输入参数 + * @param executeUnit 执行单元 + * @param 入参类型 + * @param 出参类型 + * @return 执行结果 + */ + public static List execute(final Collection inputs, final ExecuteUnit executeUnit) { + ListenableFuture> futures = submitFutures(inputs, executeUnit); + addCallback(futures); + return getFutureResults(futures); + } + + /** + * 多线程执行任务并归并结果. + * + * @param inputs 执行入参 + * @param executeUnit 执行单元 + * @param mergeUnit 合并结果单元 + * @param 入参类型 + * @param 中间结果类型 + * @param 最终结果类型 + * @return 执行结果 + */ + public static O execute(final Collection inputs, final ExecuteUnit executeUnit, final MergeUnit mergeUnit) { + return mergeUnit.merge(execute(inputs, executeUnit)); + } + + private static ListenableFuture> submitFutures(final Collection inputs, final ExecuteUnit executeUnit) { + Set> result = new HashSet<>(inputs.size()); + ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(inputs.size())); + for (final I each : inputs) { + result.add(service.submit(new Callable() { + + @Override + public O call() throws Exception { + return executeUnit.execute(each); + } + })); + } + service.shutdown(); + return Futures.allAsList(result); + } + + private static void addCallback(final ListenableFuture allFutures) { + Futures.addCallback(allFutures, new FutureCallback() { + + @Override + public void onSuccess(final T result) { + log.trace("Concurrent execute result success {}", result); + } + + @Override + public void onFailure(final Throwable thrown) { + log.error("Concurrent execute result error {}", thrown); + } + }); + } + + private static O getFutureResults(final ListenableFuture futures) { + try { + return futures.get(); + } catch (final InterruptedException | ExecutionException ex) { + throw new ShardingJdbcException(ex); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/MergeUnit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/MergeUnit.java new file mode 100644 index 0000000000000..3f36e264e48ce --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/MergeUnit.java @@ -0,0 +1,36 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.executor; + +import java.util.List; + +/** + * 合并执行单元. + * + * @author gaohongtao + */ +public interface MergeUnit { + + /** + * 合并执行结果. + * + * @param params 合并前数据 + * @return 合并后结果 + */ + O merge(final List params); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/PreparedStatementExecutor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/PreparedStatementExecutor.java new file mode 100644 index 0000000000000..52ed3dce08c8f --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/PreparedStatementExecutor.java @@ -0,0 +1,125 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.executor; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.codahale.metrics.Timer.Context; +import com.dangdang.ddframe.rdb.sharding.metrics.MetricsContext; + +import lombok.RequiredArgsConstructor; + +/** + * 多线程执行预编译语句对象请求的执行器. + * + * @author zhangliang + */ +@RequiredArgsConstructor +public final class PreparedStatementExecutor { + + private final Collection preparedStatements; + + /** + * 执行SQL查询. + * + * @return 结果集列表 + * @throws SQLException SQL异常 + */ + public List executeQuery() throws SQLException { + Context context = MetricsContext.start("ShardingPreparedStatement-executeQuery"); + List result; + if (1 == preparedStatements.size()) { + result = Arrays.asList(preparedStatements.iterator().next().executeQuery()); + MetricsContext.stop(context); + return result; + } + result = ExecutorEngine.execute(preparedStatements, new ExecuteUnit() { + + @Override + public ResultSet execute(final PreparedStatement input) throws Exception { + return input.executeQuery(); + } + }); + MetricsContext.stop(context); + return result; + } + + /** + * 执行SQL更新. + * + * @return 更新数量 + * @throws SQLException SQL异常 + */ + public int executeUpdate() throws SQLException { + Context context = MetricsContext.start("ShardingPreparedStatement-executeUpdate"); + int result; + if (1 == preparedStatements.size()) { + result = preparedStatements.iterator().next().executeUpdate(); + MetricsContext.stop(context); + return result; + } + result = ExecutorEngine.execute(preparedStatements, new ExecuteUnit() { + + @Override + public Integer execute(final PreparedStatement input) throws Exception { + return input.executeUpdate(); + } + }, new MergeUnit() { + + @Override + public Integer merge(final List results) { + int result = 0; + for (Integer each : results) { + result += each; + } + return result; + } + }); + MetricsContext.stop(context); + return result; + } + + /** + * 执行SQL请求. + * + * @return true表示执行DQL, false表示执行的DML + * @throws SQLException SQL异常 + */ + public boolean execute() throws SQLException { + Context context = MetricsContext.start("ShardingPreparedStatement-execute"); + if (1 == preparedStatements.size()) { + boolean result = preparedStatements.iterator().next().execute(); + MetricsContext.stop(context); + return result; + } + List result = ExecutorEngine.execute(preparedStatements, new ExecuteUnit() { + + @Override + public Boolean execute(final PreparedStatement input) throws Exception { + return input.execute(); + } + }); + MetricsContext.stop(context); + return result.get(0); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/StatementExecutor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/StatementExecutor.java new file mode 100644 index 0000000000000..2a25040695353 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/executor/StatementExecutor.java @@ -0,0 +1,237 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.executor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.codahale.metrics.Timer.Context; +import com.dangdang.ddframe.rdb.sharding.metrics.MetricsContext; + +import lombok.RequiredArgsConstructor; + +/** + * 多线程执行静态语句对象请求的执行器. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +public final class StatementExecutor { + + private final Collection statements = new ArrayList<>(); + + /** + * 添加静态语句对象至执行上下文. + * + * @param sql 转换后的SQL语句 + * @param statement 静态语句对象 + */ + public void addStatement(final String sql, final Statement statement) { + statements.add(new StatementEntity(sql, statement)); + } + + /** + * 执行SQL查询. + * + * @return 结果集列表 + * @throws SQLException SQL异常 + */ + public List executeQuery() throws SQLException { + Context context = MetricsContext.start("ShardingStatement-executeQuery"); + List result; + if (1 == statements.size()) { + StatementEntity entity = statements.iterator().next(); + result = Arrays.asList(entity.statement.executeQuery(entity.sql)); + MetricsContext.stop(context); + return result; + } + result = ExecutorEngine.execute(statements, new ExecuteUnit() { + + @Override + public ResultSet execute(final StatementEntity input) throws Exception { + return input.statement.executeQuery(input.sql); + } + }); + MetricsContext.stop(context); + return result; + } + + /** + * 执行SQL更新. + * + * @return 更新数量 + * @throws SQLException SQL异常 + */ + public int executeUpdate() throws SQLException { + return executeUpdate(new Updater() { + + @Override + public int executeUpdate(final Statement statement, final String sql) throws SQLException { + return statement.executeUpdate(sql); + } + }); + } + + public int executeUpdate(final int autoGeneratedKeys) throws SQLException { + return executeUpdate(new Updater() { + + @Override + public int executeUpdate(final Statement statement, final String sql) throws SQLException { + return statement.executeUpdate(sql, autoGeneratedKeys); + } + }); + } + + public int executeUpdate(final int[] columnIndexes) throws SQLException { + return executeUpdate(new Updater() { + + @Override + public int executeUpdate(final Statement statement, final String sql) throws SQLException { + return statement.executeUpdate(sql, columnIndexes); + } + }); + } + + public int executeUpdate(final String[] columnNames) throws SQLException { + return executeUpdate(new Updater() { + + @Override + public int executeUpdate(final Statement statement, final String sql) throws SQLException { + return statement.executeUpdate(sql, columnNames); + } + }); + } + + private int executeUpdate(final Updater updater) throws SQLException { + Context context = MetricsContext.start("ShardingStatement-executeUpdate"); + int result; + if (1 == statements.size()) { + StatementEntity entity = statements.iterator().next(); + result = updater.executeUpdate(entity.statement, entity.sql); + MetricsContext.stop(context); + return result; + } + result = ExecutorEngine.execute(statements, new ExecuteUnit() { + + @Override + public Integer execute(final StatementEntity input) throws Exception { + return updater.executeUpdate(input.statement, input.sql); + } + }, new MergeUnit() { + + @Override + public Integer merge(final List results) { + int result = 0; + for (int each : results) { + result += each; + } + return result; + } + }); + MetricsContext.stop(context); + return result; + } + + /** + * 执行SQL请求. + * + * @return true表示执行DQL语句, false表示执行的DML语句 + * @throws SQLException SQL异常 + */ + public boolean execute() throws SQLException { + return execute(new Executor() { + + @Override + public boolean execute(final Statement statement, final String sql) throws SQLException { + return statement.execute(sql); + } + }); + } + + public boolean execute(final int autoGeneratedKeys) throws SQLException { + return execute(new Executor() { + + @Override + public boolean execute(final Statement statement, final String sql) throws SQLException { + return statement.execute(sql, autoGeneratedKeys); + } + }); + } + + public boolean execute(final int[] columnIndexes) throws SQLException { + return execute(new Executor() { + + @Override + public boolean execute(final Statement statement, final String sql) throws SQLException { + return statement.execute(sql, columnIndexes); + } + }); + } + + public boolean execute(final String[] columnNames) throws SQLException { + return execute(new Executor() { + + @Override + public boolean execute(final Statement statement, final String sql) throws SQLException { + return statement.execute(sql, columnNames); + } + }); + } + + private boolean execute(final Executor executor) throws SQLException { + Context context = MetricsContext.start("ShardingStatement-execute"); + if (1 == statements.size()) { + StatementEntity entity = statements.iterator().next(); + boolean result = executor.execute(entity.statement, entity.sql); + MetricsContext.stop(context); + return result; + } + List result = ExecutorEngine.execute(statements, new ExecuteUnit() { + + @Override + public Boolean execute(final StatementEntity input) throws Exception { + return executor.execute(input.statement, input.sql); + } + }); + MetricsContext.stop(context); + return result.get(0); + } + + private interface Updater { + + int executeUpdate(Statement statement, String sql) throws SQLException; + } + + private interface Executor { + + boolean execute(Statement statement, String sql) throws SQLException; + } + + @RequiredArgsConstructor + private class StatementEntity { + + private final String sql; + + private final Statement statement; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/AbstractShardingResultSet.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/AbstractShardingResultSet.java new file mode 100644 index 0000000000000..33f5fef29deb5 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/AbstractShardingResultSet.java @@ -0,0 +1,77 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractResultSetAdapter; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.Limit; + +import lombok.extern.slf4j.Slf4j; + +/** + * 支持分片的结果集抽象类. + * + * @author zhangliang + */ +@Slf4j +public abstract class AbstractShardingResultSet extends AbstractResultSetAdapter { + + private final Limit limit; + + private boolean offsetSkipped; + + private int readCount; + + protected AbstractShardingResultSet(final List resultSets, final Limit limit) { + super(resultSets); + this.limit = limit; + setCurrentResultSet(resultSets.get(0)); + } + + @Override + public final boolean next() throws SQLException { + if (null != limit && !offsetSkipped) { + skipOffset(); + } + return null == limit ? nextForSharding() : ++readCount <= limit.getRowCount() && nextForSharding(); + } + + private void skipOffset() { + for (int i = 0; i < limit.getOffset(); i++) { + try { + if (!nextForSharding()) { + break; + } + } catch (final SQLException ignored) { + log.warn("Skip result set error", ignored); + } + } + offsetSkipped = true; + } + + /** + * 迭代结果集. + * + * @return true 可以继续访问 false 不能继续访问 + * @throws SQLException + */ + protected abstract boolean nextForSharding() throws SQLException; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingConnection.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingConnection.java new file mode 100644 index 0000000000000..a7c0ffff21831 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingConnection.java @@ -0,0 +1,129 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import com.codahale.metrics.Timer.Context; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractConnectionAdapter; +import com.dangdang.ddframe.rdb.sharding.metrics.MetricsContext; +import com.dangdang.ddframe.rdb.sharding.router.SQLRouteEngine; + +/** + * 支持分片的数据库连接. + * + * @author zhangliang + */ +public final class ShardingConnection extends AbstractConnectionAdapter { + + private final ShardingRule shardingRule; + + private final DatabaseMetaData metaData; + + private Map connectionMap = new HashMap<>(); + + private SQLRouteEngine sqlRouteEngine; + + public ShardingConnection(final ShardingRule shardingRule, final DatabaseMetaData metaData) throws SQLException { + this.shardingRule = shardingRule; + this.metaData = metaData; + sqlRouteEngine = new SQLRouteEngine(shardingRule, DatabaseType.valueFrom(metaData.getDatabaseProductName())); + } + + /** + * 根据数据源名称获取相应的数据库连接. + * + * @param dataSourceName 数据源名称 + * @return 数据库连接 + */ + public Connection getConnection(final String dataSourceName) throws SQLException { + if (connectionMap.containsKey(dataSourceName)) { + return connectionMap.get(dataSourceName); + } + Context context = MetricsContext.start("ShardingConnection-getConnection", dataSourceName); + Connection connection = shardingRule.getDataSourceRule().getDataSource(dataSourceName).getConnection(); + MetricsContext.stop(context); + replayMethodsInvovation(connection); + connectionMap.put(dataSourceName, connection); + return connection; + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return metaData; + } + + @Override + public PreparedStatement prepareStatement(final String sql) throws SQLException { + return new ShardingPreparedStatement(sqlRouteEngine, this, sql); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { + return new ShardingPreparedStatement(sqlRouteEngine, this, sql, resultSetType, resultSetConcurrency); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + return new ShardingPreparedStatement(sqlRouteEngine, this, sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException { + return new ShardingPreparedStatement(sqlRouteEngine, this, sql, autoGeneratedKeys); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException { + return new ShardingPreparedStatement(sqlRouteEngine, this, sql, columnIndexes); + } + + @Override + public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException { + return new ShardingPreparedStatement(sqlRouteEngine, this, sql, columnNames); + } + + @Override + public Statement createStatement() throws SQLException { + return new ShardingStatement(sqlRouteEngine, this); + } + + @Override + public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException { + return new ShardingStatement(sqlRouteEngine, this, resultSetType, resultSetConcurrency); + } + + @Override + public Statement createStatement(final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + return new ShardingStatement(sqlRouteEngine, this, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public Collection getConnections() { + return connectionMap.values(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingPreparedStatement.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingPreparedStatement.java new file mode 100644 index 0000000000000..817790dd38413 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingPreparedStatement.java @@ -0,0 +1,186 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.executor.PreparedStatementExecutor; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractPreparedStatementAdapter; +import com.dangdang.ddframe.rdb.sharding.merger.ResultSetFactory; +import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit; +import com.dangdang.ddframe.rdb.sharding.router.SQLRouteEngine; +import com.dangdang.ddframe.rdb.sharding.router.SQLRouteResult; +import com.google.common.collect.Lists; + +/** + * 支持分片的预编译语句对象. + * + * @author zhangliang + */ +public final class ShardingPreparedStatement extends AbstractPreparedStatementAdapter { + + private final String sql; + + private final Collection cachedRoutedPreparedStatements = new LinkedList<>(); + + private Integer autoGeneratedKeys; + + private int[] columnIndexes; + + private String[] columnNames; + + private boolean hasExecuted; + + private final List> batchParameters = new ArrayList<>(); + + public ShardingPreparedStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, final String sql) throws SQLException { + this(sqlRouteEngine, shardingConnection, sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + } + + public ShardingPreparedStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, + final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { + this(sqlRouteEngine, shardingConnection, sql, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT); + } + + public ShardingPreparedStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, + final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + super(sqlRouteEngine, shardingConnection, resultSetType, resultSetConcurrency, resultSetHoldability); + this.sql = sql; + } + + public ShardingPreparedStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, final String sql, final int autoGeneratedKeys) throws SQLException { + this(sqlRouteEngine, shardingConnection, sql); + this.autoGeneratedKeys = autoGeneratedKeys; + } + + public ShardingPreparedStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, final String sql, final int[] columnIndexes) throws SQLException { + this(sqlRouteEngine, shardingConnection, sql); + this.columnIndexes = columnIndexes; + } + + public ShardingPreparedStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, final String sql, final String[] columnNames) throws SQLException { + this(sqlRouteEngine, shardingConnection, sql); + this.columnNames = columnNames; + } + + @Override + public ResultSet executeQuery() throws SQLException { + hasExecuted = true; + setCurrentResultSet(ResultSetFactory.getResultSet(new PreparedStatementExecutor(getRoutedPreparedStatements()).executeQuery(), getMergeContext())); + return getCurrentResultSet(); + } + + @Override + public int executeUpdate() throws SQLException { + hasExecuted = true; + return new PreparedStatementExecutor(getRoutedPreparedStatements()).executeUpdate(); + } + + @Override + public boolean execute() throws SQLException { + hasExecuted = true; + return new PreparedStatementExecutor(getRoutedPreparedStatements()).execute(); + } + + @Override + public void addBatch() throws SQLException { + batchParameters.add(Lists.newArrayList(getParameters())); + getParameters().clear(); + } + + @Override + public void clearBatch() throws SQLException { + batchParameters.clear(); + } + + @Override + public int[] executeBatch() throws SQLException { + hasExecuted = true; + int[] result = new int[batchParameters.size()]; + int i = 0; + for (List each : batchParameters) { + result[i++] = new PreparedStatementExecutor(routeSQL(each)).executeUpdate(); + } + return result; + } + + private Collection getRoutedPreparedStatements() throws SQLException { + if (!hasExecuted) { + return Collections.emptyList(); + } + routeIfNeed(); + return cachedRoutedPreparedStatements; + } + + @Override + public Collection getRoutedStatements() throws SQLException { + return getRoutedPreparedStatements(); + } + + private void routeIfNeed() throws SQLException { + if (!cachedRoutedPreparedStatements.isEmpty()) { + return; + } + cachedRoutedPreparedStatements.addAll(routeSQL(getParameters())); + } + + private List routeSQL(final List parameters) throws SQLException { + List result = new ArrayList<>(); + SQLRouteResult sqlRouteResult = getSqlRouteEngine().route(sql, parameters); + setMergeContext(sqlRouteResult.getMergeContext()); + for (SQLExecutionUnit each : sqlRouteResult.getExecutionUnits()) { + PreparedStatement preparedStatement = generatePrepareStatement(getShardingConnection().getConnection(each.getDataSource()), each.getSql()); + replayMethodsInvovation(preparedStatement); + setParameters(preparedStatement, parameters); + result.add(preparedStatement); + } + return result; + } + + private PreparedStatement generatePrepareStatement(final Connection conn, final String shardingSql) throws SQLException { + if (null != autoGeneratedKeys) { + return conn.prepareStatement(shardingSql, autoGeneratedKeys); + } + if (null != columnIndexes) { + return conn.prepareStatement(shardingSql, columnIndexes); + } + if (null != columnNames) { + return conn.prepareStatement(shardingSql, columnNames); + } + if (0 != getResultSetHoldability()) { + return conn.prepareStatement(shardingSql, getResultSetType(), getResultSetConcurrency(), getResultSetHoldability()); + } + return conn.prepareStatement(shardingSql, getResultSetType(), getResultSetConcurrency()); + } + + private void setParameters(final PreparedStatement preparedStatement, final List parameters) throws SQLException { + int i = 1; + for (Object each : parameters) { + preparedStatement.setObject(i++, each); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingStatement.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingStatement.java new file mode 100644 index 0000000000000..cd62d2863e4ce --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingStatement.java @@ -0,0 +1,194 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.dangdang.ddframe.rdb.sharding.executor.StatementExecutor; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractStatementAdapter; +import com.dangdang.ddframe.rdb.sharding.merger.ResultSetFactory; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit; +import com.dangdang.ddframe.rdb.sharding.router.SQLRouteEngine; +import com.dangdang.ddframe.rdb.sharding.router.SQLRouteResult; +import com.google.common.base.Charsets; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +/** + * 支持分片的静态语句对象. + * + * @author gaohongtao + */ +public class ShardingStatement extends AbstractStatementAdapter { + + @Getter(AccessLevel.PROTECTED) + private final ShardingConnection shardingConnection; + + @Getter(AccessLevel.PROTECTED) + private final SQLRouteEngine sqlRouteEngine; + + @Getter + private final int resultSetType; + + @Getter + private final int resultSetConcurrency; + + @Getter + private final int resultSetHoldability; + + private final Map cachedRoutedStatements = new HashMap<>(); + + @Getter(AccessLevel.PROTECTED) + @Setter(AccessLevel.PROTECTED) + private MergeContext mergeContext; + + @Getter(AccessLevel.PROTECTED) + @Setter(AccessLevel.PROTECTED) + private ResultSet currentResultSet; + + public ShardingStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection) throws SQLException { + this(sqlRouteEngine, shardingConnection, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + } + + public ShardingStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, final int resultSetType, final int resultSetConcurrency) throws SQLException { + this(sqlRouteEngine, shardingConnection, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT); + } + + public ShardingStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, + final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + super(Statement.class); + this.shardingConnection = shardingConnection; + this.sqlRouteEngine = sqlRouteEngine; + this.resultSetType = resultSetType; + this.resultSetConcurrency = resultSetConcurrency; + this.resultSetHoldability = resultSetHoldability; + } + + @Override + public Connection getConnection() throws SQLException { + return shardingConnection; + } + + @Override + public ResultSet executeQuery(final String sql) throws SQLException { + if (null != currentResultSet && !currentResultSet.isClosed()) { + currentResultSet.close(); + } + currentResultSet = ResultSetFactory.getResultSet(generateExecutor(sql).executeQuery(), mergeContext); + return currentResultSet; + } + + @Override + public int executeUpdate(final String sql) throws SQLException { + return generateExecutor(sql).executeUpdate(); + } + + @Override + public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException { + return generateExecutor(sql).executeUpdate(autoGeneratedKeys); + } + + @Override + public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException { + return generateExecutor(sql).executeUpdate(columnIndexes); + } + + @Override + public int executeUpdate(final String sql, final String[] columnNames) throws SQLException { + return generateExecutor(sql).executeUpdate(columnNames); + } + + @Override + public boolean execute(final String sql) throws SQLException { + return generateExecutor(sql).execute(); + } + + @Override + public boolean execute(final String sql, final int autoGeneratedKeys) throws SQLException { + return generateExecutor(sql).execute(autoGeneratedKeys); + } + + @Override + public boolean execute(final String sql, final int[] columnIndexes) throws SQLException { + return generateExecutor(sql).execute(columnIndexes); + } + + @Override + public boolean execute(final String sql, final String[] columnNames) throws SQLException { + return generateExecutor(sql).execute(columnNames); + } + + private StatementExecutor generateExecutor(final String sql) throws SQLException { + StatementExecutor result = new StatementExecutor(); + SQLRouteResult sqlRouteResult = sqlRouteEngine.route(sql, Collections.emptyList()); + mergeContext = sqlRouteResult.getMergeContext(); + for (SQLExecutionUnit each : sqlRouteResult.getExecutionUnits()) { + result.addStatement(each.getSql(), generateStatement(each.getSql(), each.getDataSource())); + } + return result; + } + + private Statement generateStatement(final String sql, final String dataSourceName) throws SQLException { + HashCode hashCode = Hashing.md5().newHasher().putString(sql, Charsets.UTF_8).putString(dataSourceName, Charsets.UTF_8).hash(); + if (cachedRoutedStatements.containsKey(hashCode)) { + return cachedRoutedStatements.get(hashCode); + } + Connection connection = shardingConnection.getConnection(dataSourceName); + Statement result; + if (0 == resultSetHoldability) { + result = connection.createStatement(resultSetType, resultSetConcurrency); + } else { + result = connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); + } + replayMethodsInvovation(result); + cachedRoutedStatements.put(hashCode, result); + return result; + } + + @Override + public ResultSet getResultSet() throws SQLException { + if (null != currentResultSet) { + return currentResultSet; + } + List resultSets = new ArrayList<>(getRoutedStatements().size()); + for (Statement each : getRoutedStatements()) { + resultSets.add(each.getResultSet()); + } + currentResultSet = ResultSetFactory.getResultSet(resultSets, mergeContext); + return currentResultSet; + } + + @Override + public Collection getRoutedStatements() throws SQLException { + return cachedRoutedStatements.values(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractConnectionAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractConnectionAdapter.java new file mode 100644 index 0000000000000..d425a9f0ed109 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractConnectionAdapter.java @@ -0,0 +1,142 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.AbstractUnsupportedOperationConnection; + +/** + * 数据库连接适配类. + * + * @author zhangliang + */ +public abstract class AbstractConnectionAdapter extends AbstractUnsupportedOperationConnection { + + private boolean autoCommit = true; + + private boolean readOnly = true; + + private boolean closed; + + private int transactionIsolation = TRANSACTION_READ_UNCOMMITTED; + + protected abstract Collection getConnections(); + + @Override + public final boolean getAutoCommit() throws SQLException { + return autoCommit; + } + + @Override + public final void setAutoCommit(final boolean autoCommit) throws SQLException { + this.autoCommit = autoCommit; + if (getConnections().isEmpty()) { + recordMethodInvocation(Connection.class, "setAutoCommit", new Class[] {boolean.class}, new Object[] {autoCommit}); + return; + } + for (Connection each : getConnections()) { + each.setAutoCommit(autoCommit); + } + } + + @Override + public final void commit() throws SQLException { + for (Connection each : getConnections()) { + each.commit(); + } + } + + @Override + public final void rollback() throws SQLException { + for (Connection each : getConnections()) { + each.rollback(); + } + } + + @Override + public final void close() throws SQLException { + for (Connection each : getConnections()) { + each.close(); + } + closed = true; + } + + @Override + public final boolean isClosed() throws SQLException { + return closed; + } + + @Override + public final boolean isReadOnly() throws SQLException { + return readOnly; + } + + @Override + public final void setReadOnly(final boolean readOnly) throws SQLException { + this.readOnly = readOnly; + if (getConnections().isEmpty()) { + recordMethodInvocation(Connection.class, "setReadOnly", new Class[] {boolean.class}, new Object[] {readOnly}); + return; + } + for (Connection each : getConnections()) { + each.setReadOnly(readOnly); + } + } + + @Override + public final int getTransactionIsolation() throws SQLException { + return transactionIsolation; + } + + @Override + public final void setTransactionIsolation(final int level) throws SQLException { + transactionIsolation = level; + if (getConnections().isEmpty()) { + recordMethodInvocation(Connection.class, "setTransactionIsolation", new Class[] {int.class}, new Object[] {level}); + return; + } + for (Connection each : getConnections()) { + each.setTransactionIsolation(level); + } + } + + // -------以下代码与MySQL实现保持一致.------- + + @Override + public SQLWarning getWarnings() throws SQLException { + return null; + } + + @Override + public void clearWarnings() throws SQLException { + } + + @Override + public final int getHoldability() throws SQLException { + return ResultSet.CLOSE_CURSORS_AT_COMMIT; + } + + @Override + public final void setHoldability(final int holdability) throws SQLException { + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractDataSourceAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractDataSourceAdapter.java new file mode 100644 index 0000000000000..4c7216238a878 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractDataSourceAdapter.java @@ -0,0 +1,53 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import java.io.PrintWriter; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.AbstractUnsupportedOperationDataSource; + +import lombok.RequiredArgsConstructor; + +/** + * 数据源适配类. + * + * @author zhangliang + */ +@RequiredArgsConstructor +public abstract class AbstractDataSourceAdapter extends AbstractUnsupportedOperationDataSource { + + private PrintWriter logWriter = new PrintWriter(System.out); + + @Override + public final PrintWriter getLogWriter() throws SQLException { + return logWriter; + } + + @Override + public final void setLogWriter(final PrintWriter out) throws SQLException { + this.logWriter = out; + } + + @Override + public final Logger getParentLogger() throws SQLFeatureNotSupportedException { + return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractPreparedStatementAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractPreparedStatementAdapter.java new file mode 100644 index 0000000000000..f160fc963e5b6 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractPreparedStatementAdapter.java @@ -0,0 +1,275 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.Ref; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.AbstractUnsupportedOperationPreparedStatement; +import com.dangdang.ddframe.rdb.sharding.router.SQLRouteEngine; + +import lombok.Getter; + +/** + * 预编译语句对象的适配类. + * + *

+ * 封装与分片核心逻辑不相关的方法. + *

+ * + * @author zhangliang + */ +public abstract class AbstractPreparedStatementAdapter extends AbstractUnsupportedOperationPreparedStatement { + + @Getter + private final List parameters = new ArrayList<>(); + + public AbstractPreparedStatementAdapter(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, + final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + super(sqlRouteEngine, shardingConnection, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public final void setNull(final int parameterIndex, final int sqlType) throws SQLException { + setParameter(parameterIndex, null); + } + + @Override + public final void setNull(final int parameterIndex, final int sqlType, final String typeName) throws SQLException { + setParameter(parameterIndex, null); + } + + @Override + public final void setBoolean(final int parameterIndex, final boolean x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setByte(final int parameterIndex, final byte x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setShort(final int parameterIndex, final short x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setInt(final int parameterIndex, final int x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setLong(final int parameterIndex, final long x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setFloat(final int parameterIndex, final float x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setDouble(final int parameterIndex, final double x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setString(final int parameterIndex, final String x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setBigDecimal(final int parameterIndex, final BigDecimal x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setDate(final int parameterIndex, final Date x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setDate(final int parameterIndex, final Date x, final Calendar cal) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setTime(final int parameterIndex, final Time x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setTime(final int parameterIndex, final Time x, final Calendar cal) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setTimestamp(final int parameterIndex, final Timestamp x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setTimestamp(final int parameterIndex, final Timestamp x, final Calendar cal) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setBytes(final int parameterIndex, final byte[] x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setBlob(final int parameterIndex, final Blob x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setBlob(final int parameterIndex, final InputStream x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setBlob(final int parameterIndex, final InputStream x, final long length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setClob(final int parameterIndex, final Clob x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setClob(final int parameterIndex, final Reader x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setClob(final int parameterIndex, final Reader x, final long length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setAsciiStream(final int parameterIndex, final InputStream x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setAsciiStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setAsciiStream(final int parameterIndex, final InputStream x, final long length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setUnicodeStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setBinaryStream(final int parameterIndex, final InputStream x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setBinaryStream(final int parameterIndex, final InputStream x, final int length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setBinaryStream(final int parameterIndex, final InputStream x, final long length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setCharacterStream(final int parameterIndex, final Reader x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setCharacterStream(final int parameterIndex, final Reader x, final int length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setCharacterStream(final int parameterIndex, final Reader x, final long length) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setURL(final int parameterIndex, final URL x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setSQLXML(final int parameterIndex, final SQLXML x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setRef(final int parameterIndex, final Ref x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setObject(final int parameterIndex, final Object x) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setObject(final int parameterIndex, final Object x, final int targetSqlType) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void setObject(final int parameterIndex, final Object x, final int targetSqlType, final int scaleOrLength) throws SQLException { + setParameter(parameterIndex, x); + } + + @Override + public final void clearParameters() throws SQLException { + parameters.clear(); + } + + private void setParameter(final int index, final Object x) { + int extendedSize = index - parameters.size(); + if (extendedSize > 1) { + while (--extendedSize > 0) { + parameters.add(null); + } + } + parameters.add(index - 1, x); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractResultSetAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractResultSetAdapter.java new file mode 100644 index 0000000000000..837389d11c257 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractResultSetAdapter.java @@ -0,0 +1,119 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.List; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 处理多结果集的适配器. + * + * @author zhangliang + */ +@RequiredArgsConstructor +public abstract class AbstractResultSetAdapter extends AbstractResultSetGetterAdapter { + + @Getter + private final List resultSets; + + private boolean closed; + + @Override + public final void close() throws SQLException { + for (ResultSet each : resultSets) { + each.close(); + } + closed = true; + } + + @Override + public final boolean isClosed() throws SQLException { + return closed; + } + + @Override + public final boolean wasNull() throws SQLException { + return getCurrentResultSet().wasNull(); + } + + @Override + public final int getFetchDirection() throws SQLException { + return getCurrentResultSet().getFetchDirection(); + } + + @Override + public final void setFetchDirection(final int direction) throws SQLException { + for (ResultSet each : resultSets) { + each.setFetchDirection(direction); + } + } + + @Override + public final int getFetchSize() throws SQLException { + return getCurrentResultSet().getFetchSize(); + } + + @Override + public final void setFetchSize(final int rows) throws SQLException { + for (ResultSet each : resultSets) { + each.setFetchSize(rows); + } + } + + @Override + public final int getType() throws SQLException { + return getCurrentResultSet().getType(); + } + + @Override + public final int getConcurrency() throws SQLException { + return getCurrentResultSet().getConcurrency(); + } + + @Override + public final Statement getStatement() throws SQLException { + return getCurrentResultSet().getStatement(); + } + + @Override + public final SQLWarning getWarnings() throws SQLException { + return getCurrentResultSet().getWarnings(); + } + + @Override + public final void clearWarnings() throws SQLException { + getCurrentResultSet().clearWarnings(); + } + + @Override + public final ResultSetMetaData getMetaData() throws SQLException { + return getCurrentResultSet().getMetaData(); + } + + @Override + public final int findColumn(final String columnLabel) throws SQLException { + return getCurrentResultSet().findColumn(columnLabel); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractResultSetGetterAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractResultSetGetterAdapter.java new file mode 100644 index 0000000000000..b84c4b5c35f3c --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractResultSetGetterAdapter.java @@ -0,0 +1,327 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Map; + +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.AbstractUnsupportedOperationResultSet; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** + * 处理多结果集的适配器. + * + * @author zhangliang + */ +@RequiredArgsConstructor +public abstract class AbstractResultSetGetterAdapter extends AbstractUnsupportedOperationResultSet { + + @Getter(AccessLevel.PROTECTED) + @Setter(AccessLevel.PROTECTED) + private ResultSet currentResultSet; + + @Override + public final boolean getBoolean(final int columnIndex) throws SQLException { + return currentResultSet.getBoolean(columnIndex); + } + + @Override + public final boolean getBoolean(final String columnLabel) throws SQLException { + return currentResultSet.getBoolean(columnLabel); + } + + @Override + public final byte getByte(final int columnIndex) throws SQLException { + return currentResultSet.getByte(columnIndex); + } + + @Override + public final byte getByte(final String columnLabel) throws SQLException { + return currentResultSet.getByte(columnLabel); + } + + @Override + public final short getShort(final int columnIndex) throws SQLException { + return currentResultSet.getShort(columnIndex); + } + + @Override + public final short getShort(final String columnLabel) throws SQLException { + return currentResultSet.getShort(columnLabel); + } + + @Override + public final int getInt(final int columnIndex) throws SQLException { + return currentResultSet.getInt(columnIndex); + } + + @Override + public final int getInt(final String columnLabel) throws SQLException { + return currentResultSet.getInt(columnLabel); + } + + @Override + public final long getLong(final int columnIndex) throws SQLException { + return currentResultSet.getLong(columnIndex); + } + + @Override + public final long getLong(final String columnLabel) throws SQLException { + return currentResultSet.getLong(columnLabel); + } + + @Override + public final float getFloat(final int columnIndex) throws SQLException { + return currentResultSet.getFloat(columnIndex); + } + + @Override + public final float getFloat(final String columnLabel) throws SQLException { + return currentResultSet.getFloat(columnLabel); + } + + @Override + public final double getDouble(final int columnIndex) throws SQLException { + return currentResultSet.getDouble(columnIndex); + } + + @Override + public final double getDouble(final String columnLabel) throws SQLException { + return currentResultSet.getDouble(columnLabel); + } + + @Override + public final String getString(final int columnIndex) throws SQLException { + return currentResultSet.getString(columnIndex); + } + + @Override + public final String getString(final String columnLabel) throws SQLException { + return currentResultSet.getString(columnLabel); + } + + @Override + public final BigDecimal getBigDecimal(final int columnIndex) throws SQLException { + return currentResultSet.getBigDecimal(columnIndex); + } + + @Override + public final BigDecimal getBigDecimal(final String columnLabel) throws SQLException { + return currentResultSet.getBigDecimal(columnLabel); + } + + @SuppressWarnings("deprecation") + @Override + public final BigDecimal getBigDecimal(final int columnIndex, final int scale) throws SQLException { + return currentResultSet.getBigDecimal(columnIndex, scale); + } + + @SuppressWarnings("deprecation") + @Override + public final BigDecimal getBigDecimal(final String columnLabel, final int scale) throws SQLException { + return currentResultSet.getBigDecimal(columnLabel, scale); + } + + @Override + public final byte[] getBytes(final int columnIndex) throws SQLException { + return currentResultSet.getBytes(columnIndex); + } + + @Override + public final byte[] getBytes(final String columnLabel) throws SQLException { + return currentResultSet.getBytes(columnLabel); + } + + @Override + public final Date getDate(final int columnIndex) throws SQLException { + return currentResultSet.getDate(columnIndex); + } + + @Override + public final Date getDate(final String columnLabel) throws SQLException { + return currentResultSet.getDate(columnLabel); + } + + @Override + public final Date getDate(final int columnIndex, final Calendar cal) throws SQLException { + return currentResultSet.getDate(columnIndex, cal); + } + + @Override + public final Date getDate(final String columnLabel, final Calendar cal) throws SQLException { + return currentResultSet.getDate(columnLabel, cal); + } + + @Override + public final Time getTime(final int columnIndex) throws SQLException { + return currentResultSet.getTime(columnIndex); + } + + @Override + public final Time getTime(final String columnLabel) throws SQLException { + return currentResultSet.getTime(columnLabel); + } + + @Override + public final Time getTime(final int columnIndex, final Calendar cal) throws SQLException { + return currentResultSet.getTime(columnIndex, cal); + } + + @Override + public final Time getTime(final String columnLabel, final Calendar cal) throws SQLException { + return currentResultSet.getTime(columnLabel, cal); + } + + @Override + public final Timestamp getTimestamp(final int columnIndex) throws SQLException { + return currentResultSet.getTimestamp(columnIndex); + } + + @Override + public final Timestamp getTimestamp(final String columnLabel) throws SQLException { + return currentResultSet.getTimestamp(columnLabel); + } + + @Override + public final Timestamp getTimestamp(final int columnIndex, final Calendar cal) throws SQLException { + return currentResultSet.getTimestamp(columnIndex, cal); + } + + @Override + public final Timestamp getTimestamp(final String columnLabel, final Calendar cal) throws SQLException { + return currentResultSet.getTimestamp(columnLabel, cal); + } + + @Override + public final InputStream getAsciiStream(final int columnIndex) throws SQLException { + return currentResultSet.getAsciiStream(columnIndex); + } + + @Override + public final InputStream getAsciiStream(final String columnLabel) throws SQLException { + return currentResultSet.getAsciiStream(columnLabel); + } + + @SuppressWarnings("deprecation") + @Override + public final InputStream getUnicodeStream(final int columnIndex) throws SQLException { + return currentResultSet.getUnicodeStream(columnIndex); + } + + @SuppressWarnings("deprecation") + @Override + public final InputStream getUnicodeStream(final String columnLabel) throws SQLException { + return currentResultSet.getUnicodeStream(columnLabel); + } + + @Override + public final InputStream getBinaryStream(final int columnIndex) throws SQLException { + return currentResultSet.getBinaryStream(columnIndex); + } + + @Override + public final InputStream getBinaryStream(final String columnLabel) throws SQLException { + return currentResultSet.getBinaryStream(columnLabel); + } + + @Override + public final Reader getCharacterStream(final int columnIndex) throws SQLException { + return currentResultSet.getCharacterStream(columnIndex); + } + + @Override + public final Reader getCharacterStream(final String columnLabel) throws SQLException { + return currentResultSet.getCharacterStream(columnLabel); + } + + @Override + public final Blob getBlob(final int columnIndex) throws SQLException { + return currentResultSet.getBlob(columnIndex); + } + + @Override + public final Blob getBlob(final String columnLabel) throws SQLException { + return currentResultSet.getBlob(columnLabel); + } + + @Override + public final Clob getClob(final int columnIndex) throws SQLException { + return currentResultSet.getClob(columnIndex); + } + + @Override + public final Clob getClob(final String columnLabel) throws SQLException { + return currentResultSet.getClob(columnLabel); + } + + @Override + public final URL getURL(final int columnIndex) throws SQLException { + return currentResultSet.getURL(columnIndex); + } + + @Override + public final URL getURL(final String columnLabel) throws SQLException { + return currentResultSet.getURL(columnLabel); + } + + @Override + public final SQLXML getSQLXML(final int columnIndex) throws SQLException { + return currentResultSet.getSQLXML(columnIndex); + } + + @Override + public final SQLXML getSQLXML(final String columnLabel) throws SQLException { + return currentResultSet.getSQLXML(columnLabel); + } + + @Override + public final Object getObject(final int columnIndex) throws SQLException { + return currentResultSet.getObject(columnIndex); + } + + @Override + public final Object getObject(final String columnLabel) throws SQLException { + return currentResultSet.getObject(columnLabel); + } + + @Override + public final Object getObject(final int columnIndex, final Map> map) throws SQLException { + return currentResultSet.getObject(columnIndex, map); + } + + @Override + public final Object getObject(final String columnLabel, final Map> map) throws SQLException { + return currentResultSet.getObject(columnLabel, map); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractStatementAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractStatementAdapter.java new file mode 100644 index 0000000000000..d4296606ffd4b --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/AbstractStatementAdapter.java @@ -0,0 +1,163 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.AbstractUnsupportedOperationStatement; + +import lombok.RequiredArgsConstructor; + +/** + * 静态语句对象适配类. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +public abstract class AbstractStatementAdapter extends AbstractUnsupportedOperationStatement { + + private final Class recordTargetClass; + + private boolean closed; + + private boolean poolable; + + private int fetchSize; + + @Override + public final void close() throws SQLException { + for (Statement each : getRoutedStatements()) { + each.close(); + } + closed = true; + getRoutedStatements().clear(); + } + + @Override + public final boolean isClosed() throws SQLException { + return closed; + } + + @Override + public final boolean isPoolable() throws SQLException { + return poolable; + } + + @Override + public final void setPoolable(final boolean poolable) throws SQLException { + this.poolable = poolable; + if (getRoutedStatements().isEmpty()) { + recordMethodInvocation(recordTargetClass, "setPoolable", new Class[] {boolean.class}, new Object[] {poolable}); + return; + } + for (Statement each : getRoutedStatements()) { + each.setPoolable(poolable); + } + } + + @Override + public final int getFetchSize() throws SQLException { + return fetchSize; + } + + @Override + public final void setFetchSize(final int rows) throws SQLException { + this.fetchSize = rows; + if (getRoutedStatements().isEmpty()) { + recordMethodInvocation(recordTargetClass, "setFetchSize", new Class[] {int.class}, new Object[] {rows}); + return; + } + for (Statement each : getRoutedStatements()) { + each.setFetchSize(rows); + } + } + + @Override + public final void setEscapeProcessing(final boolean enable) throws SQLException { + if (getRoutedStatements().isEmpty()) { + recordMethodInvocation(recordTargetClass, "setEscapeProcessing", new Class[] {boolean.class}, new Object[] {enable}); + return; + } + for (Statement each : getRoutedStatements()) { + each.setEscapeProcessing(enable); + } + } + + @Override + public final void cancel() throws SQLException { + for (Statement each : getRoutedStatements()) { + each.cancel(); + } + } + + @Override + public final void setCursorName(final String name) throws SQLException { + if (getRoutedStatements().isEmpty()) { + recordMethodInvocation(recordTargetClass, "setCursorName", new Class[] {String.class}, new Object[] {name}); + return; + } + for (Statement each : getRoutedStatements()) { + each.setCursorName(name); + } + } + + @Override + public final int getUpdateCount() throws SQLException { + int result = 0; + for (Statement each : getRoutedStatements()) { + result += each.getUpdateCount(); + } + return result; + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return null; + } + + @Override + public void clearWarnings() throws SQLException { + } + + /* + * 只有存储过程会出现多结果集, 因此不支持. + */ + @Override + public final boolean getMoreResults() throws SQLException { + return false; + } + + @Override + public final boolean getMoreResults(final int current) throws SQLException { + return false; + } + + + + /** + * 获取路由的静态语句对象集合. + * + * @return 路由的静态语句对象集合 + * @throws SQLException + */ + public abstract Collection getRoutedStatements() throws SQLException; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/WrapperAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/WrapperAdapter.java new file mode 100644 index 0000000000000..c7fae0c29ecf4 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/WrapperAdapter.java @@ -0,0 +1,77 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import java.sql.SQLException; +import java.sql.Wrapper; +import java.util.ArrayList; +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; +import com.dangdang.ddframe.rdb.sharding.jdbc.util.JdbcMethodInvocation; + +/** + * JDBC Wrapper适配类. + * + * @author zhangliang + */ +public class WrapperAdapter implements Wrapper { + + private Collection jdbcMethodInvocations = new ArrayList<>(); + + @SuppressWarnings("unchecked") + @Override + public final T unwrap(final Class iface) throws SQLException { + if (isWrapperFor(iface)) { + return (T) this; + } + throw new SQLException(String.format("[%s] cannot be unwrapped as [%s]", getClass().getName(), iface.getName())); + } + + @Override + public final boolean isWrapperFor(final Class iface) throws SQLException { + return iface.isInstance(this); + } + + /** + * 记录方法调用. + * + * @param targetClass 目标类 + * @param methodName 方法名称 + * @param argumentTypes 参数类型 + * @param arguments 参数 + */ + protected final void recordMethodInvocation(final Class targetClass, final String methodName, final Class[] argumentTypes, final Object[] arguments) { + try { + jdbcMethodInvocations.add(new JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments)); + } catch (final NoSuchMethodException ex) { + throw new ShardingJdbcException(ex); + } + } + + /** + * 回放记录的方法调用. + * + * @param target 目标对象 + */ + protected final void replayMethodsInvovation(final Object target) { + for (JdbcMethodInvocation each : jdbcMethodInvocations) { + each.invoke(target); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractResultSetUpdaterAdapter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractResultSetUpdaterAdapter.java new file mode 100644 index 0000000000000..2848428c32e41 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractResultSetUpdaterAdapter.java @@ -0,0 +1,455 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; + +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.WrapperAdapter; + +/** + * 更新结果集的适配器. + * + * @author zhangliang + */ +public abstract class AbstractResultSetUpdaterAdapter extends WrapperAdapter implements ResultSet { + + @Override + public final void updateNull(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNull(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBoolean(final int columnIndex, final boolean x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBoolean(final String columnLabel, final boolean x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateByte(final int columnIndex, final byte x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateByte(final String columnLabel, final byte x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateShort(final int columnIndex, final short x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateShort(final String columnLabel, final short x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateInt(final int columnIndex, final int x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateInt(final String columnLabel, final int x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateLong(final int columnIndex, final long x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateLong(final String columnLabel, final long x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateFloat(final int columnIndex, final float x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateFloat(final String columnLabel, final float x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateDouble(final int columnIndex, final double x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateDouble(final String columnLabel, final double x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBigDecimal(final int columnIndex, final BigDecimal x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBigDecimal(final String columnLabel, final BigDecimal x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateString(final int columnIndex, final String x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateString(final String columnLabel, final String x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNString(final int columnIndex, final String nString) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNString(final String columnLabel, final String nString) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBytes(final int columnIndex, final byte[] x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBytes(final String columnLabel, final byte[] x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateDate(final int columnIndex, final Date x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateDate(final String columnLabel, final Date x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateTime(final int columnIndex, final Time x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateTime(final String columnLabel, final Time x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateTimestamp(final int columnIndex, final Timestamp x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateTimestamp(final String columnLabel, final Timestamp x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateAsciiStream(final int columnIndex, final InputStream inputStream) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateAsciiStream(final String columnLabel, final InputStream inputStream) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateAsciiStream(final int columnIndex, final InputStream x, final int length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateAsciiStream(final String columnLabel, final InputStream x, final int length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateAsciiStream(final int columnIndex, final InputStream inputStream, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateAsciiStream(final String columnLabel, final InputStream inputStream, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBinaryStream(final int columnIndex, final InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBinaryStream(final String columnLabel, final InputStream x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBinaryStream(final int columnIndex, final InputStream x, final int length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBinaryStream(final String columnLabel, final InputStream x, final int length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBinaryStream(final int columnIndex, final InputStream x, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBinaryStream(final String columnLabel, final InputStream x, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateCharacterStream(final int columnIndex, final Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateCharacterStream(final String columnLabel, final Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateCharacterStream(final int columnIndex, final Reader x, final int length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateCharacterStream(final String columnLabel, final Reader reader, final int length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateCharacterStream(final int columnIndex, final Reader x, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateCharacterStream(final String columnLabel, final Reader reader, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNCharacterStream(final int columnIndex, final Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNCharacterStream(final String columnLabel, final Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNCharacterStream(final int columnIndex, final Reader x, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNCharacterStream(final String columnLabel, final Reader x, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateObject(final int columnIndex, final Object x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateObject(final String columnLabel, final Object x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateObject(final int columnIndex, final Object x, final int scaleOrLength) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateObject(final String columnLabel, final Object x, final int scaleOrLength) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateRef(final int columnIndex, final Ref x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateRef(final String columnLabel, final Ref x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBlob(final int columnIndex, final Blob x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBlob(final String columnLabel, final Blob x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBlob(final int columnIndex, final InputStream inputStream) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBlob(final String columnLabel, final InputStream inputStream) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBlob(final int columnIndex, final InputStream inputStream, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateBlob(final String columnLabel, final InputStream inputStream, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateClob(final int columnIndex, final Clob x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateClob(final String columnLabel, final Clob x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateClob(final int columnIndex, final Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateClob(final String columnLabel, final Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateClob(final int columnIndex, final Reader reader, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateClob(final String columnLabel, final Reader reader, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNClob(final int columnIndex, final NClob nClob) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNClob(final String columnLabel, final NClob nClob) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNClob(final int columnIndex, final Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNClob(final String columnLabel, final Reader reader) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNClob(final int columnIndex, final Reader reader, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateNClob(final String columnLabel, final Reader reader, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateArray(final int columnIndex, final Array x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateArray(final String columnLabel, final Array x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateRowId(final int columnIndex, final RowId x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateRowId(final String columnLabel, final RowId x) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateSQLXML(final int columnIndex, final SQLXML xmlObject) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } + + @Override + public final void updateSQLXML(final String columnLabel, final SQLXML xmlObject) throws SQLException { + throw new SQLFeatureNotSupportedException("updateXXX"); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationConnection.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationConnection.java new file mode 100644 index 0000000000000..839d94930add4 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationConnection.java @@ -0,0 +1,184 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.NClob; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.WrapperAdapter; + +/** + * 声明不支持操作的数据库连接对象. + * + * @author zhangliang + */ +public abstract class AbstractUnsupportedOperationConnection extends WrapperAdapter implements Connection { + + @Override + public final CallableStatement prepareCall(final String sql) throws SQLException { + throw new SQLFeatureNotSupportedException("prepareCall"); + } + + @Override + public final CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException { + throw new SQLFeatureNotSupportedException("prepareCall"); + } + + @Override + public final CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + throw new SQLFeatureNotSupportedException("prepareCall"); + } + + @Override + public final String nativeSQL(final String sql) throws SQLException { + throw new SQLFeatureNotSupportedException("nativeSQL"); + } + + @Override + public final Savepoint setSavepoint() throws SQLException { + throw new SQLFeatureNotSupportedException("setSavepoint"); + } + + @Override + public final Savepoint setSavepoint(final String name) throws SQLException { + throw new SQLFeatureNotSupportedException("setSavepoint name"); + } + + @Override + public final void releaseSavepoint(final Savepoint savepoint) throws SQLException { + throw new SQLFeatureNotSupportedException("releaseSavepoint"); + } + + @Override + public final void rollback(final Savepoint savepoint) throws SQLException { + throw new SQLFeatureNotSupportedException("rollback savepoint"); + } + + @Override + public final void abort(final Executor executor) throws SQLException { + throw new SQLFeatureNotSupportedException("abort"); + } + + @Override + public final String getCatalog() throws SQLException { + throw new SQLFeatureNotSupportedException("getCatalog"); + } + + @Override + public final void setCatalog(final String catalog) throws SQLException { + throw new SQLFeatureNotSupportedException("setCatalog"); + } + + @Override + public final String getSchema() throws SQLException { + throw new SQLFeatureNotSupportedException("getSchema"); + } + + @Override + public final void setSchema(final String schema) throws SQLException { + throw new SQLFeatureNotSupportedException("setSchema"); + } + + @Override + public final Map> getTypeMap() throws SQLException { + throw new SQLFeatureNotSupportedException("getTypeMap"); + } + + @Override + public final void setTypeMap(final Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException("setTypeMap"); + } + + @Override + public final int getNetworkTimeout() throws SQLException { + throw new SQLFeatureNotSupportedException("getNetworkTimeout"); + } + + @Override + public final void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException { + throw new SQLFeatureNotSupportedException("setNetworkTimeout"); + } + + @Override + public final Clob createClob() throws SQLException { + throw new SQLFeatureNotSupportedException("createClob"); + } + + @Override + public final Blob createBlob() throws SQLException { + throw new SQLFeatureNotSupportedException("createBlob"); + } + + @Override + public final NClob createNClob() throws SQLException { + throw new SQLFeatureNotSupportedException("createNClob"); + } + + @Override + public final SQLXML createSQLXML() throws SQLException { + throw new SQLFeatureNotSupportedException("createSQLXML"); + } + + @Override + public final Array createArrayOf(final String typeName, final Object[] elements) throws SQLException { + throw new SQLFeatureNotSupportedException("createArrayOf"); + } + + @Override + public final Struct createStruct(final String typeName, final Object[] attributes) throws SQLException { + throw new SQLFeatureNotSupportedException("createStruct"); + } + + @Override + public final boolean isValid(final int timeout) throws SQLException { + throw new SQLFeatureNotSupportedException("isValid"); + } + + @Override + public final Properties getClientInfo() throws SQLException { + throw new SQLFeatureNotSupportedException("getClientInfo"); + } + + @Override + public final String getClientInfo(final String name) throws SQLException { + throw new SQLFeatureNotSupportedException("getClientInfo name"); + } + + @Override + public final void setClientInfo(final String name, final String value) throws SQLClientInfoException { + throw new UnsupportedOperationException("setClientInfo name value"); + } + + @Override + public final void setClientInfo(final Properties properties) throws SQLClientInfoException { + throw new UnsupportedOperationException("setClientInfo properties"); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationDataSource.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationDataSource.java new file mode 100644 index 0000000000000..a62d3fe3f6421 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationDataSource.java @@ -0,0 +1,43 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; + +import javax.sql.DataSource; + +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.WrapperAdapter; + +/** + * 声明不支持操作的数据源对象. + * + * @author zhangliang + */ +public abstract class AbstractUnsupportedOperationDataSource extends WrapperAdapter implements DataSource { + + @Override + public final int getLoginTimeout() throws SQLException { + throw new SQLFeatureNotSupportedException("unsupported getLoginTimeout()"); + } + + @Override + public final void setLoginTimeout(final int seconds) throws SQLException { + throw new SQLFeatureNotSupportedException("unsupported setLoginTimeout(int seconds)"); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationPreparedStatement.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationPreparedStatement.java new file mode 100644 index 0000000000000..d6581cc295827 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationPreparedStatement.java @@ -0,0 +1,95 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.io.Reader; +import java.sql.Array; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; + +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingStatement; +import com.dangdang.ddframe.rdb.sharding.router.SQLRouteEngine; + +/** + * 声明不支持操作的预编译语句对象. + * + * @author zhangliang + */ +public abstract class AbstractUnsupportedOperationPreparedStatement extends ShardingStatement implements PreparedStatement { + + public AbstractUnsupportedOperationPreparedStatement(final SQLRouteEngine sqlRouteEngine, final ShardingConnection shardingConnection, + final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException { + super(sqlRouteEngine, shardingConnection, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public final ResultSetMetaData getMetaData() throws SQLException { + throw new SQLFeatureNotSupportedException("getMetaData"); + } + + @Override + public final ParameterMetaData getParameterMetaData() throws SQLException { + throw new SQLFeatureNotSupportedException("ParameterMetaData"); + } + + @Override + public final void setNString(final int parameterIndex, final String x) throws SQLException { + throw new SQLFeatureNotSupportedException("setNString"); + } + + @Override + public final void setNClob(final int parameterIndex, final NClob x) throws SQLException { + throw new SQLFeatureNotSupportedException("setNClob"); + } + + @Override + public final void setNClob(final int parameterIndex, final Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("setNClob"); + } + + @Override + public final void setNClob(final int parameterIndex, final Reader x, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("setNClob"); + } + + @Override + public final void setNCharacterStream(final int parameterIndex, final Reader x) throws SQLException { + throw new SQLFeatureNotSupportedException("setNCharacterStream"); + } + + @Override + public final void setNCharacterStream(final int parameterIndex, final Reader x, final long length) throws SQLException { + throw new SQLFeatureNotSupportedException("setNCharacterStream"); + } + + @Override + public final void setArray(final int parameterIndex, final Array x) throws SQLException { + throw new SQLFeatureNotSupportedException("setArray"); + } + + @Override + public final void setRowId(final int parameterIndex, final RowId x) throws SQLException { + throw new SQLFeatureNotSupportedException("setRowId"); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationResultSet.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationResultSet.java new file mode 100644 index 0000000000000..c0230df879bfb --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationResultSet.java @@ -0,0 +1,224 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.io.Reader; +import java.sql.Array; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; + +/** + * 声明不支持操作的数据结果集对象. + * + * @author zhangliang + */ +public abstract class AbstractUnsupportedOperationResultSet extends AbstractResultSetUpdaterAdapter { + + @Override + public final boolean previous() throws SQLException { + throw new SQLFeatureNotSupportedException("previous"); + } + + @Override + public final boolean isBeforeFirst() throws SQLException { + throw new SQLFeatureNotSupportedException("isBeforeFirst"); + } + + @Override + public final boolean isAfterLast() throws SQLException { + throw new SQLFeatureNotSupportedException("isAfterLast"); + } + + @Override + public final boolean isFirst() throws SQLException { + throw new SQLFeatureNotSupportedException("isFirst"); + } + + @Override + public final boolean isLast() throws SQLException { + throw new SQLFeatureNotSupportedException("isLast"); + } + + @Override + public final void beforeFirst() throws SQLException { + throw new SQLFeatureNotSupportedException("beforeFirst"); + } + + @Override + public final void afterLast() throws SQLException { + throw new SQLFeatureNotSupportedException("afterLast"); + } + + @Override + public final boolean first() throws SQLException { + throw new SQLFeatureNotSupportedException("first"); + } + + @Override + public final boolean last() throws SQLException { + throw new SQLFeatureNotSupportedException("last"); + } + + @Override + public final boolean absolute(final int row) throws SQLException { + throw new SQLFeatureNotSupportedException("absolute"); + } + + @Override + public final boolean relative(final int rows) throws SQLException { + throw new SQLFeatureNotSupportedException("relative"); + } + + @Override + public final int getRow() throws SQLException { + throw new SQLFeatureNotSupportedException("getRow"); + } + + @Override + public final void insertRow() throws SQLException { + throw new SQLFeatureNotSupportedException("insertRow"); + } + + @Override + public final void updateRow() throws SQLException { + throw new SQLFeatureNotSupportedException("updateRow"); + } + + @Override + public final void deleteRow() throws SQLException { + throw new SQLFeatureNotSupportedException("deleteRow"); + } + + @Override + public final void refreshRow() throws SQLException { + throw new SQLFeatureNotSupportedException("refreshRow"); + } + + @Override + public final void cancelRowUpdates() throws SQLException { + throw new SQLFeatureNotSupportedException("cancelRowUpdates"); + } + + @Override + public final void moveToInsertRow() throws SQLException { + throw new SQLFeatureNotSupportedException("moveToInsertRow"); + } + + @Override + public final void moveToCurrentRow() throws SQLException { + throw new SQLFeatureNotSupportedException("moveToCurrentRow"); + } + + @Override + public final boolean rowInserted() throws SQLException { + throw new SQLFeatureNotSupportedException("rowInserted"); + } + + @Override + public final boolean rowUpdated() throws SQLException { + throw new SQLFeatureNotSupportedException("rowUpdated"); + } + + @Override + public final boolean rowDeleted() throws SQLException { + throw new SQLFeatureNotSupportedException("rowDeleted"); + } + + @Override + public final String getCursorName() throws SQLException { + throw new SQLFeatureNotSupportedException("getCursorName"); + } + + @Override + public final int getHoldability() throws SQLException { + throw new SQLFeatureNotSupportedException("getHoldability"); + } + + @Override + public final String getNString(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("getNString"); + } + + @Override + public final String getNString(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("getNString"); + } + + @Override + public final NClob getNClob(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("getNClob"); + } + + @Override + public final NClob getNClob(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("getNClob"); + } + + @Override + public final Reader getNCharacterStream(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("getNCharacterStream"); + } + + @Override + public final Reader getNCharacterStream(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("getNCharacterStream"); + } + + @Override + public final Ref getRef(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("getRef"); + } + + @Override + public final Ref getRef(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("getRef"); + } + + @Override + public final Array getArray(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("getArray"); + } + + @Override + public final Array getArray(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("getArray"); + } + + @Override + public final RowId getRowId(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException("getRowId"); + } + + @Override + public final RowId getRowId(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException("getRowId"); + } + + @Override + public final T getObject(final int columnIndex, final Class type) throws SQLException { + throw new SQLFeatureNotSupportedException("getObject with type"); + } + + @Override + public final T getObject(final String columnLabel, final Class type) throws SQLException { + throw new SQLFeatureNotSupportedException("getObject with type"); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationStatement.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationStatement.java new file mode 100644 index 0000000000000..fc00b7bdb7754 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/AbstractUnsupportedOperationStatement.java @@ -0,0 +1,103 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; + +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.WrapperAdapter; + +/** + * 声明不支持操作的静态语句对象. + * + * @author gaohongtao + */ +public abstract class AbstractUnsupportedOperationStatement extends WrapperAdapter implements Statement { + + @Override + public final int getMaxFieldSize() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxFieldSize"); + } + + @Override + public final void setMaxFieldSize(final int max) throws SQLException { + throw new SQLFeatureNotSupportedException("setMaxFieldSize"); + } + + @Override + public final int getMaxRows() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxRows"); + } + + @Override + public final void setMaxRows(final int max) throws SQLException { + throw new SQLFeatureNotSupportedException("setMaxRows"); + } + + @Override + public final int getQueryTimeout() throws SQLException { + throw new SQLFeatureNotSupportedException("getQueryTimeout"); + } + + @Override + public final void setQueryTimeout(final int seconds) throws SQLException { + throw new SQLFeatureNotSupportedException("setQueryTimeout"); + } + + @Override + public final int getFetchDirection() throws SQLException { + throw new SQLFeatureNotSupportedException("getFetchDirection"); + } + + @Override + public final void setFetchDirection(final int direction) throws SQLException { + throw new SQLFeatureNotSupportedException("setFetchDirection"); + } + + @Override + public final ResultSet getGeneratedKeys() throws SQLException { + throw new SQLFeatureNotSupportedException("getGeneratedKeys"); + } + + @Override + public final void addBatch(final String sql) throws SQLException { + throw new SQLFeatureNotSupportedException("addBatch sql"); + } + + @Override + public void clearBatch() throws SQLException { + throw new SQLFeatureNotSupportedException("clearBatch"); + } + + @Override + public int[] executeBatch() throws SQLException { + throw new SQLFeatureNotSupportedException("executeBatch"); + } + + @Override + public final void closeOnCompletion() throws SQLException { + throw new SQLFeatureNotSupportedException("closeOnCompletion"); + } + + @Override + public final boolean isCloseOnCompletion() throws SQLException { + throw new SQLFeatureNotSupportedException("isCloseOnCompletion"); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/util/JdbcMethodInvocation.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/util/JdbcMethodInvocation.java new file mode 100644 index 0000000000000..18f83b031b692 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/jdbc/util/JdbcMethodInvocation.java @@ -0,0 +1,51 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; + +import lombok.RequiredArgsConstructor; + +/** + * 反射调用JDBC相关方法的工具类. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +public final class JdbcMethodInvocation { + + private final Method method; + + private final Object[] arguments; + + /** + *  调用方法. + * + * @param target 目标对象 + */ + public void invoke(final Object target) { + try { + method.invoke(target, arguments); + } catch (final IllegalAccessException | InvocationTargetException ex) { + throw new ShardingJdbcException("Invoke jdbc method exception", ex); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetFactory.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetFactory.java new file mode 100644 index 0000000000000..d4e5a097f7a29 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetFactory.java @@ -0,0 +1,75 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.AggregationInvokeHandler; +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.AggregationResultSet; +import com.dangdang.ddframe.rdb.sharding.merger.groupby.GroupByInvokeHandler; +import com.dangdang.ddframe.rdb.sharding.merger.groupby.GroupByResultSet; +import com.dangdang.ddframe.rdb.sharding.merger.iterator.IteratorResultSet; +import com.dangdang.ddframe.rdb.sharding.merger.orderby.OrderByResultSet; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext.ResultSetType; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 创建归并分片结果集的工厂. + * + * @author gaohongtao + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Slf4j +public final class ResultSetFactory { + + /** + * 获取结果集. + * + * @param resultSets 结果集列表 + * @param sqlParsedResult SQL解析结果 + * @return 结果集包装 + */ + public static ResultSet getResultSet(final List resultSets, final MergeContext mergeContext) throws SQLException { + ResultSetType resultSetType = mergeContext.getResultSetType(); + log.trace("Get '{}' result set", resultSetType); + switch (resultSetType) { + case GroupBy: + return createDelegateResultSet(new GroupByInvokeHandler(new GroupByResultSet(resultSets, mergeContext))); + case Aggregate: + return createDelegateResultSet(new AggregationInvokeHandler(new AggregationResultSet(resultSets, mergeContext))); + case OrderBy: + return new OrderByResultSet(resultSets, mergeContext); + case Iterator: + return new IteratorResultSet(resultSets, mergeContext); + default: + throw new UnsupportedOperationException(resultSetType.name()); + } + } + + private static ResultSet createDelegateResultSet(final InvocationHandler handler) { + return (ResultSet) Proxy.newProxyInstance(ResultSetFactory.class.getClassLoader(), new Class[]{ResultSet.class}, handler); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AbstractAggregationUnit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AbstractAggregationUnit.java new file mode 100644 index 0000000000000..ae03d33d02057 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AbstractAggregationUnit.java @@ -0,0 +1,48 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; + +/** + * 归并计算单元的抽象类. + * + * @author zhangliang + */ +public abstract class AbstractAggregationUnit implements AggregationUnit { + + @Override + public final void merge(final AggregationColumn aggregationColumn, final AggregationValue aggregationValue, final ResultSetQueryIndex resultSetQueryIndex) throws SQLException { + if (!aggregationColumn.getDerivedColumns().isEmpty()) { + Collection> paramList = new ArrayList>(aggregationColumn.getDerivedColumns().size()); + for (AggregationColumn each : aggregationColumn.getDerivedColumns()) { + paramList.add(aggregationValue.getValue(new ResultSetQueryIndex(each.getAlias().get()))); + } + doMerge(paramList.toArray(new Comparable[aggregationColumn.getDerivedColumns().size()])); + } else { + doMerge(aggregationValue.getValue(resultSetQueryIndex)); + } + } + + protected abstract void doMerge(Comparable... values); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AccumulationAggregationUnit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AccumulationAggregationUnit.java new file mode 100644 index 0000000000000..1fd2055aa8ae9 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AccumulationAggregationUnit.java @@ -0,0 +1,50 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import java.math.BigDecimal; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetUtil; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 累加聚合单元. + * + * @author zhangliang + */ +@RequiredArgsConstructor +@Slf4j +public final class AccumulationAggregationUnit extends AbstractAggregationUnit { + + private final Class returnType; + + private BigDecimal result = new BigDecimal(0); + + @Override + public void doMerge(final Comparable... values) { + result = result.add(new BigDecimal(values[0].toString())); + log.trace("Accumulation result: {}", result.toString()); + } + + @Override + public Comparable getResult() { + return (Comparable) ResultSetUtil.convertValue(result, returnType); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationInvokeHandler.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationInvokeHandler.java new file mode 100644 index 0000000000000..4d3960cd906a6 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationInvokeHandler.java @@ -0,0 +1,69 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import java.lang.reflect.Method; +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.merger.common.AbstractMergerInvokeHandler; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.google.common.base.Optional; + +/** + * 聚合函数动态代理. + * + * @author gaohongtao, zhangliang + */ +public final class AggregationInvokeHandler extends AbstractMergerInvokeHandler { + + public AggregationInvokeHandler(final AggregationResultSet aggregationResultSet) { + super(aggregationResultSet); + } + + @SuppressWarnings("unchecked") + protected Object doMerge(final AggregationResultSet aggregationResultSet, final Method method, final ResultSetQueryIndex resultSetQueryIndex) throws ReflectiveOperationException, SQLException { + Optional aggregationColumn = findAggregationColumn(aggregationResultSet, resultSetQueryIndex); + if (!aggregationColumn.isPresent()) { + return invokeOriginal(method, resultSetQueryIndex); + } + return aggregate(aggregationResultSet, (Class>) method.getReturnType(), resultSetQueryIndex, aggregationColumn.get()); + } + + private Optional findAggregationColumn(final AggregationResultSet aggregationResultSet, final ResultSetQueryIndex resultSetQueryIndex) { + for (AggregationColumn each : aggregationResultSet.getAggregationColumns()) { + if (resultSetQueryIndex.isQueryBySequence() && each.getIndex() == resultSetQueryIndex.getQueryIndex()) { + return Optional.of(each); + } else if (each.getAlias().isPresent() && each.getAlias().get().equals(resultSetQueryIndex.getQueryName())) { + return Optional.of(each); + } + } + return Optional.absent(); + } + + private Object aggregate(final AggregationResultSet aggregationResultSet, final Class> returnType, + final ResultSetQueryIndex resultSetQueryIndex, final AggregationColumn aggregationColumn) + throws SQLException { + AggregationUnit unit = AggregationUnitFactory.create(aggregationColumn.getAggregationType(), returnType); + for (ResultSet each : aggregationResultSet.getEffectivedResultSets()) { + unit.merge(aggregationColumn, new ResultSetAggregationValue(each), resultSetQueryIndex); + } + return unit.getResult(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationResultSet.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationResultSet.java new file mode 100644 index 0000000000000..a37df8b6c0452 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationResultSet.java @@ -0,0 +1,68 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.jdbc.AbstractShardingResultSet; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetUtil; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; + +import lombok.Getter; + +/** + * 聚合结果集. + * + * @author gaohongtao, zhangliang + */ +@Getter +public final class AggregationResultSet extends AbstractShardingResultSet { + + private final Collection effectivedResultSets; + + private final List aggregationColumns; + + private boolean hasIndexesForAggregationColumns; + + public AggregationResultSet(final List resultSets, final MergeContext mergeContext) { + super(resultSets, mergeContext.getLimit()); + aggregationColumns = mergeContext.getAggregationColumns(); + effectivedResultSets = new LinkedHashSet<>(resultSets.size()); + } + + @Override + public boolean nextForSharding() throws SQLException { + if (!hasIndexesForAggregationColumns) { + ResultSetUtil.fillIndexesForDerivedAggregationColumns(getResultSets().iterator().next(), aggregationColumns); + hasIndexesForAggregationColumns = true; + } + for (ResultSet each : getResultSets()) { + if (!each.next()) { + effectivedResultSets.remove(each); + continue; + } + effectivedResultSets.add(each); + } + return !effectivedResultSets.isEmpty(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationUnit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationUnit.java new file mode 100644 index 0000000000000..1337d9dd59137 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationUnit.java @@ -0,0 +1,48 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; + +/** + * 归并计算单元接口. + * + * @author gaohongtao + */ +public interface AggregationUnit { + + /** + * 归并聚合值. + * + * @param aggregationColumn 聚合列 + * @param aggregationValue 聚合值 + * @param resultSetQueryIndex 结果集查询索引 + * @throws SQLException + */ + void merge(AggregationColumn aggregationColumn, AggregationValue aggregationValue, ResultSetQueryIndex resultSetQueryIndex) throws SQLException; + + /** + * 获取计算结果. + * + * @return 计算结果 + */ + Comparable getResult(); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationUnitFactory.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationUnitFactory.java new file mode 100644 index 0000000000000..2734888c37f0f --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationUnitFactory.java @@ -0,0 +1,49 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + + +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn.AggregationType; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +/** + * 聚合函数结果集归并单元工厂. + * + * @author gaohongtao + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class AggregationUnitFactory { + + public static AggregationUnit create(final AggregationType type, final Class returnType) { + switch (type) { + case MAX: + return new ComparableAggregationUnit(false); + case MIN: + return new ComparableAggregationUnit(true); + case SUM: + case COUNT: + return new AccumulationAggregationUnit(returnType); + case AVG: + return new AvgAggregationUnit(returnType); + default: + throw new UnsupportedOperationException(type.name()); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationValue.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationValue.java new file mode 100644 index 0000000000000..c5a4484ca9309 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationValue.java @@ -0,0 +1,38 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; + +/** + * 聚合值接口. + * + * @author zhangliang + */ +public interface AggregationValue { + + /** + * 获取聚合值. + * + * @param resultSetQueryIndex 结果集查询索引. + * @return 聚合值 + */ + Comparable getValue(ResultSetQueryIndex resultSetQueryIndex) throws SQLException; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AvgAggregationUnit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AvgAggregationUnit.java new file mode 100644 index 0000000000000..f55655526acea --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AvgAggregationUnit.java @@ -0,0 +1,54 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import java.math.BigDecimal; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetUtil; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 平均值聚合单元. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +@Slf4j +public class AvgAggregationUnit extends AbstractAggregationUnit { + + private final Class returnType; + + private BigDecimal count = new BigDecimal(0); + + private BigDecimal sum = new BigDecimal(0); + + @Override + public void doMerge(final Comparable... values) { + count = count.add(new BigDecimal(values[0].toString())); + sum = sum.add(new BigDecimal(values[1].toString())); + log.trace("AVG result COUNT: {} SUM: {}", count, sum); + } + + @Override + public Comparable getResult() { + // TODO 通过metadata获取数据库的浮点数精度值 + return (Comparable) ResultSetUtil.convertValue(sum.divide(count, 4, BigDecimal.ROUND_HALF_UP), returnType); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ComparableAggregationUnit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ComparableAggregationUnit.java new file mode 100644 index 0000000000000..6a0238ba3d3ad --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ComparableAggregationUnit.java @@ -0,0 +1,55 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 比较聚合单元. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +@Slf4j +public final class ComparableAggregationUnit extends AbstractAggregationUnit { + + private final boolean asc; + + private Comparable result; + + @SuppressWarnings("unchecked") + @Override + protected void doMerge(@SuppressWarnings("rawtypes") final Comparable... values) { + if (null == result) { + result = values[0]; + log.trace("Comparable result: {}", result); + return; + } + int comparedValue = values[0].compareTo(result); + if (asc && comparedValue < 0 || !asc && comparedValue > 0) { + result = values[0]; + log.trace("Comparable result: {}", result); + } + } + + @Override + public Comparable getResult() { + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ResultSetAggregationValue.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ResultSetAggregationValue.java new file mode 100644 index 0000000000000..1cbda9a8bc86a --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ResultSetAggregationValue.java @@ -0,0 +1,37 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public final class ResultSetAggregationValue implements AggregationValue { + + private final ResultSet resultSet; + + @Override + public Comparable getValue(final ResultSetQueryIndex resultSetQueryIndex) throws SQLException { + return resultSetQueryIndex.isQueryBySequence() + ? (Comparable) resultSet.getObject(resultSetQueryIndex.getQueryIndex()) : (Comparable) resultSet.getObject(resultSetQueryIndex.getQueryName()); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/AbstractMergerInvokeHandler.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/AbstractMergerInvokeHandler.java new file mode 100644 index 0000000000000..ca19d5eb4c77e --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/AbstractMergerInvokeHandler.java @@ -0,0 +1,58 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.common; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.jdbc.AbstractShardingResultSet; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +/** + * 结果归并动态代理抽象类. + * + * @author zhangliang + * + * @param 结果集类型 + */ +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class AbstractMergerInvokeHandler implements InvocationHandler { + + private final T resultSet; + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { + if (!isGetDataMethod(method, args)) { + return method.invoke(resultSet, args); + } + return doMerge(resultSet, method, new ResultSetQueryIndex(args[0])); + } + + private boolean isGetDataMethod(final Method method, final Object[] args) { + return method.getName().startsWith("get") && null != args && 1 == args.length; + } + + protected abstract Object doMerge(T resultSet, Method method, ResultSetQueryIndex resultSetQueryIndex) throws ReflectiveOperationException, SQLException; + + protected Object invokeOriginal(final Method method, final ResultSetQueryIndex resultSetQueryIndex) throws ReflectiveOperationException { + return method.invoke(resultSet, new Object[] {resultSetQueryIndex.getRawQueryIndex()}); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/ResultSetQueryIndex.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/ResultSetQueryIndex.java new file mode 100644 index 0000000000000..8652dfe20d437 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/ResultSetQueryIndex.java @@ -0,0 +1,71 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.common; + +import lombok.Getter; + +/** + * 结果集查询索引. + * + *

+ * 用于查询结果集. + * 如果索引类型是int, 表示根据序号查询, 从1开始. + * 如果索引类型是String, 表示根据字段返回值名称查询. + *

+ * + * @author zhangliang + */ +@Getter +public final class ResultSetQueryIndex { + + private static final int NO_INDEX = -1; + + private final int queryIndex; + + private final String queryName; + + public ResultSetQueryIndex(final Object queryParam) { + if (queryParam instanceof Integer) { + queryIndex = (int) queryParam; + queryName = null; + } else if (queryParam instanceof String) { + queryIndex = NO_INDEX; + queryName = queryParam.toString(); + } else { + throw new IllegalArgumentException(queryParam.getClass().getName()); + } + } + + /** + * 获取是否通过序号查询. + * + * @return 通过序号查询返回true, 通过名称查询返回false + */ + public boolean isQueryBySequence() { + return NO_INDEX != queryIndex; + } + + /** + * 忽略类型获取查询索引. + * + * @return 查询索引 + */ + public Object getRawQueryIndex() { + return isQueryBySequence() ? queryIndex : queryName; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/ResultSetUtil.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/ResultSetUtil.java new file mode 100644 index 0000000000000..d342255567fae --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/common/ResultSetUtil.java @@ -0,0 +1,155 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.common; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.GroupByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; +import com.google.common.base.Preconditions; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +/** + * 结果集处理工具类. + * + * @author gaohongtao + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ResultSetUtil { + + /** + * 从结果集中提取结果值. + * + * @param groupByColumn 分组列对象 + * @param resultSet 目标结果集 + * @return 结果对象 + * @throws SQLException + */ + public static Object getValue(final GroupByColumn groupByColumn, final ResultSet resultSet) throws SQLException { + Object result = getValue(groupByColumn.getAlias(), resultSet); + Preconditions.checkNotNull(result); + return result; + } + + /** + * 从结果集中提取结果值. + * + * @param orderByColumn 排序列对象 + * @param resultSet 目标结果集 + * @return 结果对象 + * @throws SQLException + */ + public static Object getValue(final OrderByColumn orderByColumn, final ResultSet resultSet) throws SQLException { + Object result; + if (orderByColumn.getIndex().isPresent()) { + result = resultSet.getObject(orderByColumn.getIndex().get()); + } else { + result = getValue(orderByColumn.getName().get(), resultSet); + } + Preconditions.checkNotNull(result); + return result; + } + + private static Object getValue(final String columnName, final ResultSet resultSet) throws SQLException { + Object result = resultSet.getObject(columnName); + if (null == result) { + result = resultSet.getObject(columnName.toUpperCase()); + } + if (null == result) { + result = resultSet.getObject(columnName.toLowerCase()); + } + return result; + } + + /** + * 根据返回值类型返回特定类型的结果. + * + * @param value 原始结果 + * @param convertType 返回值类型 + * @return 特定类型的返回结果 + */ + public static Object convertValue(final Object value, final Class convertType) { + if (value instanceof Number) { + return convertNumberValue(value, convertType); + } else { + if (String.class.equals(convertType)) { + return value.toString(); + } else { + return value; + } + } + } + + private static Object convertNumberValue(final Object value, final Class convertType) { + Number number = (Number) value; + switch (convertType.getName()) { + case "int": + return number.intValue(); + case "long": + return number.longValue(); + case "double": + return number.doubleValue(); + case "float": + return number.floatValue(); + case "java.math.BigDecimal": + if (number instanceof BigDecimal) { + return number; + } else { + return new BigDecimal(number.toString()); + } + default: + throw new ShardingJdbcException("Unsupported data type:%s", convertType); + } + } + + /** + * 根据排序类型比较大小. + * + * @param thisValue 待比较的值 + * @param otherValue 待比较的值 + * @param orderByType 排序类型 + * @return 负数,零和正数分别表示小于,等于和大于 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static int compareTo(final Comparable thisValue, final Comparable otherValue, final OrderByType orderByType) { + return OrderByType.ASC == orderByType ? thisValue.compareTo(otherValue) : -thisValue.compareTo(otherValue); + } + + /** + * 向聚合列的补列填充索引值. + * + * @param resultSet 结果集对象 + * @param aggregationColumns 聚合列集合 + * @throws SQLException SQL异常 + */ + public static void fillIndexesForDerivedAggregationColumns(final ResultSet resultSet, final Collection aggregationColumns) throws SQLException { + for (AggregationColumn aggregationColumn : aggregationColumns) { + for (AggregationColumn derivedColumn : aggregationColumn.getDerivedColumns()) { + derivedColumn.setIndex(resultSet.findColumn(derivedColumn.getAlias().get())); + } + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByInvokeHandler.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByInvokeHandler.java new file mode 100644 index 0000000000000..8c29abeda3d3c --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByInvokeHandler.java @@ -0,0 +1,43 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.groupby; + +import java.lang.reflect.Method; +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.merger.common.AbstractMergerInvokeHandler; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetUtil; + +/** + * 分组函数动态代理. + * + * @author gaohongtao + */ +public class GroupByInvokeHandler extends AbstractMergerInvokeHandler { + + public GroupByInvokeHandler(final GroupByResultSet groupByResultSet) { + super(groupByResultSet); + } + + @Override + protected Object doMerge(final GroupByResultSet groupByResultSet, final Method method, final ResultSetQueryIndex resultSetQueryIndex) throws ReflectiveOperationException, SQLException { + // TODO 更新文档:The column is not aggregation function, get first result set + return ResultSetUtil.convertValue(groupByResultSet.getCurrentGroupByResultSet().getValue(resultSetQueryIndex), method.getReturnType()); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByKey.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByKey.java new file mode 100644 index 0000000000000..93eba5f61b850 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByKey.java @@ -0,0 +1,40 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.groupby; + +import java.util.ArrayList; +import java.util.List; + +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 分组结果集数据存储索引. + * + * @author gaohongtao, zhangliang + */ +@EqualsAndHashCode +@ToString +public final class GroupByKey { + + private final List unionKey = new ArrayList<>(); + + public void append(final String key) { + unionKey.add(key); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByResultSet.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByResultSet.java new file mode 100644 index 0000000000000..9dc0bae3c87c5 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByResultSet.java @@ -0,0 +1,194 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.groupby; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.dangdang.ddframe.rdb.sharding.executor.ExecuteUnit; +import com.dangdang.ddframe.rdb.sharding.executor.ExecutorEngine; +import com.dangdang.ddframe.rdb.sharding.executor.MergeUnit; +import com.dangdang.ddframe.rdb.sharding.jdbc.AbstractShardingResultSet; +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.AggregationUnit; +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.AggregationUnitFactory; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetUtil; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.GroupByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.google.common.base.Optional; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * 分组结果集. + * + *

+ * 采用map-reduce的方式. + * map-reduce过程发生在nextForSharding()方法第一次被调用的时候, 将相同group-key的结果集放在一起,并同时做order by的排序处理(相当于shuffle过程). + *

+ * + * @author gaohongtao, zhangliang + */ +@Slf4j +public final class GroupByResultSet extends AbstractShardingResultSet { + + private final List groupByColumns; + + private final List orderByColumns; + + private final List aggregationColumns; + + private final ResultSetMetaData resultSetMetaData; + + private final List columnLabels; + + private Iterator groupByResultIterator; + + @Getter(AccessLevel.PROTECTED) + private GroupByValue currentGroupByResultSet; + + public GroupByResultSet(final List resultSets, final MergeContext mergeContext) throws SQLException { + super(resultSets, mergeContext.getLimit()); + groupByColumns = mergeContext.getGroupByColumns(); + orderByColumns = mergeContext.getOrderByColumns(); + aggregationColumns = mergeContext.getAggregationColumns(); + resultSetMetaData = getResultSets().iterator().next().getMetaData(); + columnLabels = new ArrayList<>(resultSetMetaData.getColumnCount()); + fillRelatedColumnNames(); + } + + private void fillRelatedColumnNames() throws SQLException { + for (int i = 1; i < resultSetMetaData.getColumnCount() + 1; i++) { + columnLabels.add(resultSetMetaData.getColumnLabel(i)); + } + } + + @Override + protected boolean nextForSharding() throws SQLException { + if (null == groupByResultIterator) { + ResultSetUtil.fillIndexesForDerivedAggregationColumns(getResultSets().iterator().next(), aggregationColumns); + groupByResultIterator = reduce(map()).iterator(); + } + if (groupByResultIterator.hasNext()) { + currentGroupByResultSet = groupByResultIterator.next(); + return true; + } else { + return false; + } + } + + private Multimap map() throws SQLException { + ExecuteUnit> executeUnit = new ExecuteUnit>() { + + @Override + public Map execute(final ResultSet resultSet) throws SQLException { + // TODO 应该可以根据limit判断result的初始size,避免size满了重分配 + Map result = new HashMap<>(); + while (resultSet.next()) { + GroupByValue groupByValue = new GroupByValue(); + for (int count = 1; count <= columnLabels.size(); count++) { + groupByValue.put(count, resultSetMetaData.getColumnLabel(count), (Comparable) resultSet.getObject(count)); + } + GroupByKey groupByKey = new GroupByKey(); + for (GroupByColumn each : groupByColumns) { + groupByKey.append(ResultSetUtil.getValue(each, resultSet).toString()); + } + result.put(groupByKey, groupByValue); + } + log.trace("Result set mapping: {}", result); + return result; + } + }; + MergeUnit, Multimap> mergeUnit = new MergeUnit, Multimap>() { + + @Override + public Multimap merge(final List> values) { + Multimap result = HashMultimap.create(); + for (Map each : values) { + for (Entry entry : each.entrySet()) { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + }; + Multimap result = ExecutorEngine.execute(getResultSets(), executeUnit, mergeUnit); + log.trace("Mapped result: {}", result); + return result; + } + + private Collection reduce(final Multimap mappedResult) throws SQLException { + List result = new ArrayList<>(mappedResult.values().size() * columnLabels.size()); + for (GroupByKey key : mappedResult.keySet()) { + Collection each = mappedResult.get(key); + GroupByValue reduceResult = new GroupByValue(); + for (int i = 0; i < columnLabels.size(); i++) { + int index = i + 1; + Optional aggregationColumn = findAggregationColumn(index); + Comparable value = null; + if (aggregationColumn.isPresent()) { + value = aggregate(aggregationColumn.get(), index, each); + } + value = null == value ? each.iterator().next().getValue(new ResultSetQueryIndex(index)) : value; + reduceResult.put(index, columnLabels.get(i), value); + } + if (orderByColumns.isEmpty()) { + reduceResult.addGroupByColumns(groupByColumns); + } else { + reduceResult.addOrderColumns(orderByColumns); + } + result.add(reduceResult); + } + Collections.sort(result); + log.trace("Reduced result: {}", result); + return result; + } + + private Optional findAggregationColumn(final int index) { + for (AggregationColumn each : aggregationColumns) { + if (each.getIndex() == index) { + return Optional.of(each); + } + } + return Optional.absent(); + } + + private Comparable aggregate(final AggregationColumn column, final int index, final Collection groupByValues) throws SQLException { + AggregationUnit unit = AggregationUnitFactory.create(column.getAggregationType(), BigDecimal.class); + for (GroupByValue each : groupByValues) { + unit.merge(column, each, new ResultSetQueryIndex(index)); + } + return unit.getResult(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByValue.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByValue.java new file mode 100644 index 0000000000000..f4793a9e7a275 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByValue.java @@ -0,0 +1,114 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.groupby; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.map.CaseInsensitiveMap; + +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.AggregationValue; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetUtil; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.GroupByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; + +import lombok.ToString; + +/** + * 分组结果集数据存储对象. + * + * @author gaohongtao, zhangliang + */ +@ToString +public class GroupByValue implements AggregationValue, Comparable { + + private final Map> indexMap = new LinkedHashMap<>(); + + private final CaseInsensitiveMap> columnLabelMap = new CaseInsensitiveMap<>(); + + private final List orderColumns = new ArrayList<>(); + + private final List groupByColumns = new ArrayList<>(); + + public void put(final int index, final String columnName, final Comparable value) { + if (!indexMap.containsKey(index)) { + indexMap.put(index, value); + } + if (!columnLabelMap.containsKey(columnName)) { + columnLabelMap.put(columnName, value); + } + } + + @Override + public Comparable getValue(final ResultSetQueryIndex resultSetQueryIndex) { + return resultSetQueryIndex.isQueryBySequence() ? indexMap.get(resultSetQueryIndex.getQueryIndex()) : columnLabelMap.get(resultSetQueryIndex.getQueryName()); + } + + public void addGroupByColumns(final List columns) { + groupByColumns.addAll(columns); + } + + public void addOrderColumns(final List columns) { + orderColumns.addAll(columns); + } + + @Override + public int compareTo(final GroupByValue other) { + if (null == other) { + return -1; + } + if (orderColumns.isEmpty()) { + return compareFromGroupByColumns(other); + } + return compareFromOrderByColumns(other); + } + + private int compareFromGroupByColumns(final GroupByValue other) { + for (GroupByColumn each : groupByColumns) { + int result = ResultSetUtil.compareTo(columnLabelMap.get(each.getAlias()), other.columnLabelMap.get(each.getAlias()), each.getOrderByType()); + if (0 != result) { + return result; + } + } + return 0; + } + + private int compareFromOrderByColumns(final GroupByValue other) { + for (OrderByColumn each : orderColumns) { + OrderByType orderByType = null == each.getOrderByType() ? OrderByType.ASC : each.getOrderByType(); + Comparable thisValue; + Comparable otherValue; + if (each.getName().isPresent()) { + thisValue = columnLabelMap.get(each.getName().get()); + otherValue = other.columnLabelMap.get(each.getName().get()); + } else { + thisValue = indexMap.get(each.getIndex().get()); + otherValue = other.indexMap.get(each.getIndex().get()); + } + int result = ResultSetUtil.compareTo(thisValue, otherValue, orderByType); + if (0 != result) { + return result; + } + } + return 0; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/iterator/IteratorResultSet.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/iterator/IteratorResultSet.java new file mode 100644 index 0000000000000..1542a351e07d0 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/iterator/IteratorResultSet.java @@ -0,0 +1,52 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.iterator; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.jdbc.AbstractShardingResultSet; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; + +/** + * 迭代结果集. + * + * @author zhangliang + */ +public final class IteratorResultSet extends AbstractShardingResultSet { + + public IteratorResultSet(final List resultSets, final MergeContext mergeContext) { + super(resultSets, mergeContext.getLimit()); + } + + @Override + protected boolean nextForSharding() throws SQLException { + if (getCurrentResultSet().next()) { + return true; + } + for (int i = getResultSets().indexOf(getCurrentResultSet()) + 1; i < getResultSets().size(); i++) { + ResultSet each = getResultSets().get(i); + if (each.next()) { + setCurrentResultSet(each); + return true; + } + } + return false; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByResultSet.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByResultSet.java new file mode 100644 index 0000000000000..e8d93128ad133 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByResultSet.java @@ -0,0 +1,81 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.orderby; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.jdbc.AbstractShardingResultSet; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; + +/** + * 排序结果集处理. + * + * @author gaohongtao, zhangliang + */ +public final class OrderByResultSet extends AbstractShardingResultSet { + + private final List orderByColumns; + + private final List effectivedResultSets; + + private boolean initial; + + public OrderByResultSet(final List resultSets, final MergeContext mergeContext) { + super(resultSets, mergeContext.getLimit()); + orderByColumns = mergeContext.getOrderByColumns(); + effectivedResultSets = new ArrayList<>(resultSets.size()); + } + + @Override + public boolean nextForSharding() throws SQLException { + if (!initial) { + initialEffectivedResultSets(); + } else { + nextEffectivedResultSets(); + } + OrderByValue choosenOrderByValue = null; + for (ResultSet each : effectivedResultSets) { + OrderByValue eachOrderByValue = new OrderByValue(orderByColumns, each); + if (null == choosenOrderByValue || choosenOrderByValue.compareTo(eachOrderByValue) > 0) { + choosenOrderByValue = eachOrderByValue; + setCurrentResultSet(each); + } + } + return !effectivedResultSets.isEmpty(); + } + + private void initialEffectivedResultSets() throws SQLException { + for (ResultSet each : getResultSets()) { + if (each.next()) { + effectivedResultSets.add(each); + } + } + initial = true; + } + + private void nextEffectivedResultSets() throws SQLException { + boolean next = getCurrentResultSet().next(); + if (!next) { + effectivedResultSets.remove(getCurrentResultSet()); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByValue.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByValue.java new file mode 100644 index 0000000000000..5b9a995a3428e --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByValue.java @@ -0,0 +1,81 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.orderby; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetUtil; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.google.common.base.Preconditions; + +import lombok.RequiredArgsConstructor; + +/** + * 基于结果集的排序对象. + * + * @author zhangliang + */ +public final class OrderByValue implements Comparable { + + private final List orderByColumns; + + private final Value orderByValue; + + public OrderByValue(final List orderByColumns, final ResultSet resultSet) throws SQLException { + this.orderByColumns = orderByColumns; + orderByValue = new Value(orderByColumns, getValues(resultSet)); + } + + private List> getValues(final ResultSet resultSet) throws SQLException { + List> result = new ArrayList<>(orderByColumns.size()); + for (OrderByColumn each : orderByColumns) { + Object value = ResultSetUtil.getValue(each, resultSet); + Preconditions.checkState(value instanceof Comparable, "Sharding-JDBC: order by value must extends Comparable"); + result.add((Comparable) value); + } + return result; + } + + @Override + public int compareTo(final OrderByValue otherOrderByValue) { + return orderByValue.compareTo(otherOrderByValue.orderByValue); + } + + @RequiredArgsConstructor + static final class Value implements Comparable { + + private final List orderByColumns; + + private final List> values; + + @Override + public int compareTo(final Value otherOrderByValue) { + for (int i = 0; i < orderByColumns.size(); i++) { + OrderByColumn thisOrderByColumn = orderByColumns.get(i); + int result = ResultSetUtil.compareTo(values.get(i), otherOrderByValue.values.get(i), thisOrderByColumn.getOrderByType()); + if (0 != result) { + return result; + } + } + return 0; + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/metrics/MetricsContext.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/metrics/MetricsContext.java new file mode 100644 index 0000000000000..8ec4a59bc95c7 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/metrics/MetricsContext.java @@ -0,0 +1,87 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.metrics; + +import java.util.concurrent.TimeUnit; + +import org.slf4j.LoggerFactory; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Slf4jReporter; +import com.codahale.metrics.Slf4jReporter.LoggingLevel; +import com.codahale.metrics.Timer.Context; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; + +/** + * 度量工具上下文. + * + * @author gaohongtao + */ +public final class MetricsContext { + + private static final ThreadLocal CONTEXT = new ThreadLocal<>(); + + private final Optional metricRegistry; + + public MetricsContext(final boolean enable, final long period, final String packageName) { + if (enable) { + metricRegistry = Optional.of(new MetricRegistry()); + Slf4jReporter reporter = Slf4jReporter.forRegistry(metricRegistry.get()) + .outputTo(LoggerFactory.getLogger(packageName)) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .withLoggingLevel(LoggingLevel.DEBUG) + .build(); + reporter.start(period, TimeUnit.SECONDS); + } else { + metricRegistry = Optional.absent(); + } + } + + /** + * 注册度量上下文. + */ + public void register() { + if (metricRegistry.isPresent() && !this.equals(CONTEXT.get())) { + CONTEXT.set(this); + } + } + + /** + * 开始计时. + * + * @param name 度量目标名称 + * + * @return 计时上下文 + */ + public static Context start(final String... name) { + return null == CONTEXT.get() ? null : CONTEXT.get().metricRegistry.get().timer(MetricRegistry.name(Joiner.on("-").join(name))).time(); + } + + /** + * 停止计时. + * + * @param context 计时上下文 + */ + public static void stop(final Context context) { + if (null != context) { + context.stop(); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLParseEngine.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLParseEngine.java new file mode 100644 index 0000000000000..45c16ad1f5923 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLParseEngine.java @@ -0,0 +1,73 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser; + +import java.util.Collection; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResult; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.SQLVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.or.OrParser; +import com.google.common.base.Preconditions; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 不包含OR语句的SQL构建器解析. + * + * @author gaohongtao, zhangliang + */ +@RequiredArgsConstructor +@Slf4j +public final class SQLParseEngine { + + private final SQLStatement sqlStatement; + + private final List parameters; + + private final SQLASTOutputVisitor visitor; + + private final Collection shardingColumns; + + /** + *  解析SQL. + * + * @return SQL解析结果 + */ + public SQLParsedResult parse() { + Preconditions.checkArgument(visitor instanceof SQLVisitor); + SQLVisitor sqlVisitor = (SQLVisitor) visitor; + visitor.setParameters(parameters); + sqlVisitor.getParseContext().setShardingColumns(shardingColumns); + sqlStatement.accept(visitor); + SQLParsedResult result; + if (sqlVisitor.getParseContext().isHasOrCondition()) { + result = new OrParser(sqlStatement, visitor).parse(); + } else { + sqlVisitor.getParseContext().mergeCurrentConditionContext(); + result = sqlVisitor.getParseContext().getParsedResult(); + } + log.debug("Parsed SQL result: {}", result); + log.debug("Parsed SQL: {}", sqlVisitor.getSQLBuilder()); + result.getRouteContext().setSqlBuilder(sqlVisitor.getSQLBuilder()); + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLParserFactory.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLParserFactory.java new file mode 100644 index 0000000000000..3bca79ef69b0f --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLParserFactory.java @@ -0,0 +1,99 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser; + +import java.util.Collection; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.db2.parser.DB2StatementParser; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser; +import com.alibaba.druid.sql.dialect.sqlserver.parser.SQLServerStatementParser; +import com.alibaba.druid.sql.parser.SQLStatementParser; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.VisitorLogProxy; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * SQL解析器工厂. + * + * @author gaohongtao, zhangliang + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Slf4j +public final class SQLParserFactory { + + /** + * 创建解析器引擎对象. + * + * @param databaseType 数据库类型 + * @param sql SQL语句 + * @param parameters SQL中参数的值 + * @param shardingColumns 分片列名称集合 + * @return 解析器引擎对象 + * @throws SQLParserException SQL解析异常 + */ + public static SQLParseEngine create(final DatabaseType databaseType, final String sql, final List parameters, final Collection shardingColumns) throws SQLParserException { + log.debug("Logic SQL: {}", sql); + SQLStatement sqlStatement = getSQLStatementParser(databaseType, sql).parseStatement(); + log.trace("Get {} SQL Statement", sqlStatement.getClass().getName()); + return new SQLParseEngine(sqlStatement, parameters, getSQLVisitor(databaseType, sqlStatement), shardingColumns); + } + + private static SQLStatementParser getSQLStatementParser(final DatabaseType databaseType, final String sql) { + switch (databaseType) { + case H2: + case MySQL: + return new MySqlStatementParser(sql); + case Oracle: + return new OracleStatementParser(sql); + case SQLServer: + return new SQLServerStatementParser(sql); + case DB2: + return new DB2StatementParser(sql); + default: + throw new UnsupportedOperationException(String.format("Cannot support database type [%s]", databaseType)); + } + } + + private static SQLASTOutputVisitor getSQLVisitor(final DatabaseType databaseType, final SQLStatement sqlStatement) { + if (sqlStatement instanceof SQLSelectStatement) { + return VisitorLogProxy.enhance(SQLVisitorRegistry.getSelectVistor(databaseType)); + } + if (sqlStatement instanceof SQLInsertStatement) { + return VisitorLogProxy.enhance(SQLVisitorRegistry.getInsertVistor(databaseType)); + } + if (sqlStatement instanceof SQLUpdateStatement) { + return VisitorLogProxy.enhance(SQLVisitorRegistry.getUpdateVistor(databaseType)); + } + if (sqlStatement instanceof SQLDeleteStatement) { + return VisitorLogProxy.enhance(SQLVisitorRegistry.getDeleteVistor(databaseType)); + } + throw new SQLParserException("Unsupported SQL statement: [%s]", sqlStatement); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLVisitorRegistry.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLVisitorRegistry.java new file mode 100644 index 0000000000000..66e91c8acd9db --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/SQLVisitorRegistry.java @@ -0,0 +1,127 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.exception.DatabaseTypeUnsupportedException; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql.MySQLDeleteVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql.MySQLInsertVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql.MySQLSelectVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql.MySQLUpdateVisitor; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +/** + * SQL访问器注册表. + * + * @author zhangliang + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class SQLVisitorRegistry { + + private static final Map> SELECT_REGISTRY = new HashMap<>(DatabaseType.values().length); + + private static final Map> INSERT_REGISTRY = new HashMap<>(DatabaseType.values().length); + + private static final Map> UPDATE_REGISTRY = new HashMap<>(DatabaseType.values().length); + + private static final Map> DELETE_REGISTRY = new HashMap<>(DatabaseType.values().length); + + static { + registerSelectVistor(); + registerInsertVistor(); + registerUpdateVistor(); + registerDeleteVistor(); + } + + private static void registerSelectVistor() { + SELECT_REGISTRY.put(DatabaseType.H2, MySQLSelectVisitor.class); + SELECT_REGISTRY.put(DatabaseType.MySQL, MySQLSelectVisitor.class); + // TODO 其他数据库 + } + + private static void registerInsertVistor() { + INSERT_REGISTRY.put(DatabaseType.H2, MySQLInsertVisitor.class); + INSERT_REGISTRY.put(DatabaseType.MySQL, MySQLInsertVisitor.class); + // TODO 其他数据库 + } + + private static void registerUpdateVistor() { + UPDATE_REGISTRY.put(DatabaseType.H2, MySQLUpdateVisitor.class); + UPDATE_REGISTRY.put(DatabaseType.MySQL, MySQLUpdateVisitor.class); + // TODO 其他数据库 + } + + private static void registerDeleteVistor() { + DELETE_REGISTRY.put(DatabaseType.H2, MySQLDeleteVisitor.class); + DELETE_REGISTRY.put(DatabaseType.MySQL, MySQLDeleteVisitor.class); + // TODO 其他数据库 + } + + /** + * 获取SELECT访问器. + * + * @param databaseType 数据库类型 + * @return SELECT访问器 + */ + public static Class getSelectVistor(final DatabaseType databaseType) { + return getVistor(databaseType, SELECT_REGISTRY); + } + + /** + * 获取INSERT访问器. + * + * @param databaseType 数据库类型 + * @return INSERT访问器 + */ + public static Class getInsertVistor(final DatabaseType databaseType) { + return getVistor(databaseType, INSERT_REGISTRY); + } + + /** + * 获取UPDATE访问器. + * + * @param databaseType 数据库类型 + * @return UPDATE访问器 + */ + public static Class getUpdateVistor(final DatabaseType databaseType) { + return getVistor(databaseType, UPDATE_REGISTRY); + } + + /** + * 获取DELETE访问器. + * + * @param databaseType 数据库类型 + * @return DELETE访问器 + */ + public static Class getDeleteVistor(final DatabaseType databaseType) { + return getVistor(databaseType, DELETE_REGISTRY); + } + + private static Class getVistor(final DatabaseType databaseType, final Map> registry) { + if (!registry.containsKey(databaseType)) { + throw new DatabaseTypeUnsupportedException(databaseType.name()); + } + return registry.get(databaseType); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/SQLParsedResult.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/SQLParsedResult.java new file mode 100644 index 0000000000000..d6a768aafc3e8 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/SQLParsedResult.java @@ -0,0 +1,44 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result; + +import java.util.ArrayList; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.RouteContext; + +import lombok.Getter; +import lombok.ToString; + +/** + * SQL解析结果. + * + * @author gaohongtao, zhangliang + */ +@Getter +@ToString +public final class SQLParsedResult { + + private final RouteContext routeContext = new RouteContext(); + + private final List conditionContexts = new ArrayList<>(); + + private final MergeContext mergeContext = new MergeContext(); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/AggregationColumn.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/AggregationColumn.java new file mode 100644 index 0000000000000..5a44751dac419 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/AggregationColumn.java @@ -0,0 +1,63 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.merger; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Optional; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * 聚合列对象. + * + * @author zhangliang + */ +@Getter +@AllArgsConstructor +@RequiredArgsConstructor +@ToString +public final class AggregationColumn { + + private final String expression; + + private final AggregationType aggregationType; + + private final Optional alias; + + private final Optional option; + + private final List derivedColumns = new ArrayList<>(2); + + @Setter + private int index = -1; + + /** + * 聚合函数类型. + * + * @author gaohongtao + */ + public enum AggregationType { + MAX, MIN, SUM, COUNT, AVG + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/GroupByColumn.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/GroupByColumn.java new file mode 100644 index 0000000000000..9a540c68a9de5 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/GroupByColumn.java @@ -0,0 +1,41 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.merger; + +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * 分组列对象. + * + * @author zhangliang + */ +@RequiredArgsConstructor +@Getter +@ToString +public final class GroupByColumn { + + private final String name; + + private final String alias; + + private final OrderByType orderByType; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/Limit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/Limit.java new file mode 100644 index 0000000000000..bd12de46e78d9 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/Limit.java @@ -0,0 +1,37 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.merger; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * 分页对象. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +@Getter +@ToString +public class Limit { + + private final int offset; + + private final int rowCount; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/MergeContext.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/MergeContext.java new file mode 100644 index 0000000000000..470473aa02889 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/MergeContext.java @@ -0,0 +1,71 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.merger; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * 结果归并上下文. + * + * @author zhangliang + */ +@Getter +@ToString +public final class MergeContext { + + private final List orderByColumns = new ArrayList<>(); + + private final List groupByColumns = new ArrayList<>(); + + private final List aggregationColumns = new ArrayList<>(); + + @Setter + private Limit limit; + + /** + * 获取结果集类型. + * + * @return 结果集类型 + */ + public ResultSetType getResultSetType() { + if (!groupByColumns.isEmpty()) { + return ResultSetType.GroupBy; + } + if (!aggregationColumns.isEmpty()) { + return ResultSetType.Aggregate; + } + if (!orderByColumns.isEmpty()) { + return ResultSetType.OrderBy; + } + return ResultSetType.Iterator; + } + + /** + * 结果集类型. + * + * @author zhangliang + */ + public enum ResultSetType { + Iterator, OrderBy, Aggregate, GroupBy + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/OrderByColumn.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/OrderByColumn.java new file mode 100644 index 0000000000000..bef8af3495b20 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/OrderByColumn.java @@ -0,0 +1,71 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.merger; + +import com.alibaba.druid.sql.ast.SQLOrderingSpecification; +import com.google.common.base.Optional; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * 排序列对象. + * + * @author zhangliang + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@ToString +public final class OrderByColumn { + + private final Optional name; + + private final Optional index; + + private final OrderByType orderByType; + + public OrderByColumn(final String name, final OrderByType orderByType) { + this(Optional.of(name), Optional.absent(), orderByType); + } + + public OrderByColumn(final int index, final OrderByType orderByType) { + this(Optional.absent(), Optional.of(index), orderByType); + } + + /** + * 排序类型. + * + * @author gaohongtao, zhangliang + */ + public enum OrderByType { + ASC, + DESC; + + /** + * 适配Druid的枚举类型. + * + * @param sqlOrderingSpecification Druid的枚举类型 + * @return 排序类型 + */ + public static OrderByType valueOf(final SQLOrderingSpecification sqlOrderingSpecification) { + return OrderByType.valueOf(sqlOrderingSpecification.name()); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/Condition.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/Condition.java new file mode 100644 index 0000000000000..48cbd3da2472e --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/Condition.java @@ -0,0 +1,79 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.router; + +import java.util.ArrayList; +import java.util.List; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * 条件对象. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +@Getter +@ToString +@EqualsAndHashCode +public final class Condition { + + private final Column column; + + private final BinaryOperator operator; + + private final List> values = new ArrayList<>(); + + /** + * 列对象. + * + * @author gaohongtao, zhangliang + */ + @RequiredArgsConstructor + @Getter + @EqualsAndHashCode + @ToString + public static final class Column { + + private final String columnName; + + private final String tableName; + } + + /** + * 操作符枚举. + * + * @author gaohongtao, zhangliang + */ + @RequiredArgsConstructor + public enum BinaryOperator { + + EQUAL("="), BETWEEN("BETWEEN"), IN("IN"), NOT_IN("NOT IN"); + + @Getter + private final String expression; + + @Override + public String toString() { + return expression; + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/ConditionContext.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/ConditionContext.java new file mode 100644 index 0000000000000..e2b59b72305a6 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/ConditionContext.java @@ -0,0 +1,88 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.router; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.BinaryOperator; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.Column; +import com.google.common.base.Optional; + +import lombok.ToString; + +/** + * 条件对象上下文. + * + * @author zhangliang + */ +@ToString +public final class ConditionContext { + + private final Map conditions = new LinkedHashMap<>(); + + /** + * 添加条件对象. + * + * @param condition 条件对象 + */ + public void add(final Condition condition) { + // TODO 自关联有问题,表名可考虑使用别名对应 + conditions.put(condition.getColumn(), condition); + } + + /** + * 查找条件对象. + * + * @param table 表名称 + * @param column 列名称 + * @return 条件对象 + */ + public Optional find(final String table, final String column) { + return Optional.fromNullable(conditions.get(new Column(column, table))); + } + + /** + * 查找条件对象. + * + * @param table 表名称 + * @param column 列名称 + * @param operator 操作符 + * @return 条件对象 + */ + public Optional find(final String table, final String column, final BinaryOperator operator) { + Optional result = find(table, column); + if (!result.isPresent()) { + return result; + } + return result.get().getOperator() == operator ? result : Optional.absent(); + } + + public boolean isEmpty() { + return conditions.isEmpty(); + } + + public void clear() { + conditions.clear(); + } + + public Collection getAllConditions() { + return conditions.values(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/RouteContext.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/RouteContext.java new file mode 100644 index 0000000000000..02dabf05393db --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/RouteContext.java @@ -0,0 +1,40 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.router; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * SQL路由上下文. + * + * @author zhangliang + */ +@Getter +@Setter +@ToString +public final class RouteContext { + + private final Collection tables = new LinkedHashSet<>(); + + private SQLBuilder sqlBuilder; +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/SQLBuilder.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/SQLBuilder.java new file mode 100644 index 0000000000000..905127f231007 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/SQLBuilder.java @@ -0,0 +1,150 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.router; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import com.google.common.base.Joiner; + +/** + * SQL构建器. + * + * @author gaohongtao + */ +public class SQLBuilder implements Appendable { + + private final Collection segments = new LinkedList<>(); + + private final Map tokenMap = new HashMap<>(); + + private StringBuilder currentSegment; + + public SQLBuilder() { + currentSegment = new StringBuilder(); + segments.add(currentSegment); + } + + /** + * 增加占位符. + * + * @param token 占位符 + * @return SQL构建器 + */ + public SQLBuilder appendToken(final String token) { + return appendToken(token, true); + } + + /** + * 增加占位符. + * + * @param token 占位符 + * @param isSetValue 是否设置占位值 + * @return SQL构建器 + */ + public SQLBuilder appendToken(final String token, final boolean isSetValue) { + StringToken stringToken; + if (tokenMap.containsKey(token)) { + stringToken = tokenMap.get(token); + } else { + stringToken = new StringToken(); + if (isSetValue) { + stringToken.value = token; + } + tokenMap.put(token, stringToken); + } + segments.add(stringToken); + currentSegment = new StringBuilder(); + segments.add(currentSegment); + return this; + } + + /** + * 用实际的值替代占位符. + * + * @param originToken 占位符 + * @param newToken 实际的值 + * @return SQL构建器 + */ + public SQLBuilder buildSQL(final String originToken, final String newToken) { + if (tokenMap.containsKey(originToken)) { + tokenMap.get(originToken).value = newToken; + } + return this; + } + + /** + * 生成SQL语句. + * + * @return SQL语句 + */ + public String toSQL() { + StringBuilder result = new StringBuilder(); + for (Object each : segments) { + result.append(each.toString()); + } + return result.toString(); + } + + @Override + public Appendable append(final CharSequence sql) throws IOException { + currentSegment.append(sql); + return this; + } + + @Override + public Appendable append(final CharSequence sql, final int start, final int end) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Appendable append(final char c) throws IOException { + currentSegment.append(c); + return this; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for (Object each : segments) { + if (each instanceof StringToken) { + result.append(((StringToken) each).toToken()); + } else { + result.append(each.toString()); + } + } + return result.toString(); + } + + private class StringToken { + + private String value; + + public String toToken() { + return null == value ? "" : Joiner.on("").join("[Token(", value, ")]"); + } + + @Override + public String toString() { + return null == value ? "" : value; + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/Table.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/Table.java new file mode 100644 index 0000000000000..3367250a75769 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/result/router/Table.java @@ -0,0 +1,45 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.result.router; + +import com.google.common.base.Optional; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * 表解析对象. + * + * @author gaohongtao, zhangliang + */ +@RequiredArgsConstructor +@Getter +@EqualsAndHashCode +@ToString +public class Table { + + private final String name; + + private final Optional alias; + + public Table(final String name, final String alias) { + this(name, Optional.fromNullable(alias)); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/ParseContext.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/ParseContext.java new file mode 100644 index 0000000000000..3df926ae0bbdd --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/ParseContext.java @@ -0,0 +1,312 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.visitor.SQLEvalVisitorUtils; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResult; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn.AggregationType; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.GroupByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.BinaryOperator; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.Column; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; +import com.google.common.base.CharMatcher; +import com.google.common.base.Optional; + +import lombok.Getter; +import lombok.Setter; + +/** + * 解析过程的上下文对象. + * + * @author zhangliang + */ +@Getter +public final class ParseContext { + + private static final String SHARDING_GEN_ALIAS = "sharding_gen_%s"; + + private final SQLParsedResult parsedResult = new SQLParsedResult(); + + @Setter + private Collection shardingColumns; + + @Setter + private boolean hasOrCondition; + + private final ConditionContext currentConditionContext = new ConditionContext(); + + private Table currentTable; + + private int selectItemsCount; + + /** + * 设置当前正在访问的表. + * + * @param currentTableName 表名称 + * @param currentAlias 表别名 + */ + public void setCurrentTable(final String currentTableName, final Optional currentAlias) { + Table table = new Table(getExactlyValue(currentTableName), currentAlias.isPresent() ? Optional.of(getExactlyValue(currentAlias.get())) : currentAlias); + parsedResult.getRouteContext().getTables().add(table); + currentTable = table; + } + + /** + * 将表对象加入解析上下文. + * + * @param x 表名表达式, 来源于FROM, INSERT ,UPDATE, DELETE等语句 + */ + public Table addTable(final SQLExprTableSource x) { + Table result = new Table(getExactlyValue(x.getExpr().toString()), getExactlyValue(x.getAlias())); + parsedResult.getRouteContext().getTables().add(result); + return result; + } + + /** + * 向解析上下文中添加条件对象. + * + * @param expr SQL表达式 + * @param operator 操作符 + * @param valueExprs 值对象表达式集合 + * @param databaseType 数据库类型 + * @param paramters 通过占位符传进来的参数 + */ + public void addCondition(final SQLExpr expr, final BinaryOperator operator, final List valueExprs, final DatabaseType databaseType, final List paramters) { + Optional column = getColumn(expr); + if (!column.isPresent()) { + return; + } + List> values = new ArrayList<>(valueExprs.size()); + for (SQLExpr each : valueExprs) { + Comparable evalValue = evalExpression(databaseType, each, paramters); + if (null != evalValue) { + values.add(evalValue); + } + } + if (values.isEmpty()) { + return; + } + addCondition(column.get(), operator, values); + } + + /** + * 将条件对象加入解析上下文. + * + * @param column 列对象 + * @param operator 操作符 + * @param values 条件值集合 + */ + public void addCondition(final String columnName, final String tableName, final BinaryOperator operator, final SQLExpr valueExpr, final DatabaseType databaseType, final List paramters) { + Comparable value = evalExpression(databaseType, valueExpr, paramters); + if (null != value) { + addCondition(createColumn(columnName, tableName), operator, Arrays.>asList(value)); + } + } + + private void addCondition(final Column column, final BinaryOperator operator, final List> values) { + if (!shardingColumns.contains(column.getColumnName())) { + return; + } + Optional optionalCondition = currentConditionContext.find(column.getTableName(), column.getColumnName(), operator); + Condition condition; + // TODO 待讨论 + if (optionalCondition.isPresent()) { + condition = optionalCondition.get(); + } else { + condition = new Condition(column, operator); + currentConditionContext.add(condition); + } + condition.getValues().addAll(values); + } + + private Comparable evalExpression(final DatabaseType databaseType, final SQLObject sqlObject, final List parameters) { + if (sqlObject instanceof SQLMethodInvokeExpr) { + // TODO 解析函数中的sharingValue不支持 + return null; + } + Object result = SQLEvalVisitorUtils.eval(databaseType.name().toLowerCase(), sqlObject, parameters, false); + if (null == result) { + return null; + } + if (result instanceof Comparable) { + return (Comparable) result; + } + // TODO 对于NULL目前解析为空字符串,此处待考虑解决方法 + return ""; + } + + private Optional getColumn(final SQLExpr expr) { + if (expr instanceof SQLPropertyExpr) { + return Optional.fromNullable(getColumnWithQualifiedName((SQLPropertyExpr) expr)); + } + if (expr instanceof SQLIdentifierExpr) { + return Optional.fromNullable(getColumnWithoutAlias((SQLIdentifierExpr) expr)); + } + return Optional.absent(); + } + + private Column getColumnWithQualifiedName(final SQLPropertyExpr expr) { + Optional
table = findTable(((SQLIdentifierExpr) expr.getOwner()).getName()); + return expr.getOwner() instanceof SQLIdentifierExpr && table.isPresent() ? createColumn(expr.getName(), table.get().getName()) : null; + } + + private Column getColumnWithoutAlias(final SQLIdentifierExpr expr) { + return null != currentTable ? createColumn(expr.getName(), currentTable.getName()) : null; + } + + private Column createColumn(final String columName, final String tableName) { + return new Column(getExactlyValue(columName), getExactlyValue(tableName)); + } + + private Optional
findTable(final String tableNameOrAlias) { + Optional
tableFromName = findTableFromName(tableNameOrAlias); + return tableFromName.isPresent() ? tableFromName : findTableFromAlias(tableNameOrAlias); + } + + /** + * 判断SQL表达式是否为二元操作且带有别名. + * + * @param x 待判断的SQL表达式 + * @param tableOrAliasName 表名称或别名 + * @return 是否为二元操作且带有别名 + */ + public boolean isBinaryOperateWithAlias(final SQLPropertyExpr x, final String tableOrAliasName) { + return x.getParent() instanceof SQLBinaryOpExpr && findTableFromAlias(getExactlyValue(tableOrAliasName)).isPresent(); + } + + private Optional
findTableFromName(final String name) { + for (Table each : parsedResult.getRouteContext().getTables()) { + if (each.getName().equalsIgnoreCase(getExactlyValue(name))) { + return Optional.of(each); + } + } + return Optional.absent(); + } + + private Optional
findTableFromAlias(final String alias) { + for (Table each : parsedResult.getRouteContext().getTables()) { + if (each.getAlias().isPresent() && each.getAlias().get().equalsIgnoreCase(getExactlyValue(alias))) { + return Optional.of(each); + } + } + return Optional.absent(); + } + + /** + * 将求平均值函数的补列加入解析上下文. + * + * @param avgColumn 求平均值的列 + */ + public void addDerivedColumnsForAvgColumn(final AggregationColumn avgColumn) { + addDerivedColumnForAvgColumn(avgColumn, getDerivedCountColumn(avgColumn)); + addDerivedColumnForAvgColumn(avgColumn, getDerivedSumColumn(avgColumn)); + } + + private void addDerivedColumnForAvgColumn(final AggregationColumn avgColumn, final AggregationColumn derivedColumn) { + avgColumn.getDerivedColumns().add(derivedColumn); + parsedResult.getMergeContext().getAggregationColumns().add(derivedColumn); + } + + private AggregationColumn getDerivedCountColumn(final AggregationColumn avgColumn) { + String expression = avgColumn.getExpression().replaceFirst(AggregationType.AVG.toString(), AggregationType.COUNT.toString()); + return new AggregationColumn(expression, AggregationType.COUNT, Optional.of(generateDerivedColumnAlias()), avgColumn.getOption()); + } + + private AggregationColumn getDerivedSumColumn(final AggregationColumn avgColumn) { + String expression = avgColumn.getExpression().replaceFirst(AggregationType.AVG.toString(), AggregationType.SUM.toString()); + if (avgColumn.getOption().isPresent()) { + expression = expression.replaceFirst(avgColumn.getOption().get() + " ", ""); + } + return new AggregationColumn(expression, AggregationType.SUM, Optional.of(generateDerivedColumnAlias()), Optional.absent()); + } + + /** + * 将排序列加入解析上下文. + * + * @param index 列顺序索引 + * @param orderByType 排序类型 + */ + public void addOrderByColumn(final int index, final OrderByType orderByType) { + parsedResult.getMergeContext().getOrderByColumns().add(new OrderByColumn(index, orderByType)); + } + + /** + * 将排序列加入解析上下文. + * + * @param name 列名称 + * @param orderByType 排序类型 + */ + public void addOrderByColumn(final String name, final OrderByType orderByType) { + parsedResult.getMergeContext().getOrderByColumns().add(new OrderByColumn(getExactlyValue(name), orderByType)); + } + + /** + * 将分组列加入解析上下文. + * + * @param name 列名称 + * @param alias 列别名 + * @param orderByType 排序类型 + */ + public void addGroupByColumns(final String name, final String alias, final OrderByType orderByType) { + parsedResult.getMergeContext().getGroupByColumns().add(new GroupByColumn(getExactlyValue(name), alias, orderByType)); + } + + /** + * 生成补列别名. + * + * @return 补列的别名 + */ + public String generateDerivedColumnAlias() { + return String.format(SHARDING_GEN_ALIAS, ++selectItemsCount); + } + + /** + * 去掉SQL表达式的特殊字符. + * + * @param value SQL表达式 + * @return 去掉SQL特殊字符的表达式 + */ + public String getExactlyValue(final String value) { + return null == value ? null : CharMatcher.anyOf("[]`'\"").removeFrom(value); + } + + /** + * 将当前解析的条件对象归并入解析结果. + */ + public void mergeCurrentConditionContext() { + parsedResult.getConditionContexts().add(currentConditionContext); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/SQLVisitor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/SQLVisitor.java new file mode 100644 index 0000000000000..d48be590d7276 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/SQLVisitor.java @@ -0,0 +1,57 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; + +/** + * SQL解析基础访问器接口. + * + * @author zhangliang + */ +public interface SQLVisitor { + + /** + * 获取数据库类型. + * + * @return 数据库类型 + */ + DatabaseType getDatabaseType(); + + /** + * 获取解析上下文对象. + * + * @return 解析上下文对象 + */ + ParseContext getParseContext(); + + /** + * 获取SQL构建器. + * + * @return SQL构建器 + */ + SQLBuilder getSQLBuilder(); + + /** + * 打印替换标记. + * + * @param token 替换标记 + */ + void printToken(String token); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/VisitorLogProxy.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/VisitorLogProxy.java new file mode 100644 index 0000000000000..a9cf632fec12b --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/VisitorLogProxy.java @@ -0,0 +1,103 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor; + +import java.lang.reflect.Method; + +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql.AbstractMySQLVisitor; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +/** + * SQL解析日志打印. + * + * @author gaohongtao + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Slf4j +public final class VisitorLogProxy { + + /** + * 打印SQL解析调用树. + * + * @param target 待增强类 + * @param 泛型 + * @return 增强后的新类的对象 + */ + @SuppressWarnings("unchecked") + public static T enhance(final Class target) { + if (log.isTraceEnabled()) { + Enhancer result = new Enhancer(); + result.setSuperclass(target); + result.setCallback(new VisitorHandler()); + return (T) result.create(); + } else { + try { + return target.newInstance(); + } catch (final InstantiationException | IllegalAccessException ex) { + log.error("create Visitor exception: {}", ex); + throw new ShardingJdbcException(ex); + } + } + } + + private static class VisitorHandler implements MethodInterceptor { + + private StringBuilder hierarchyIndex = new StringBuilder(); + + private Integer depth = 0; + + @Override + public Object intercept(final Object enhancedObject, final Method method, final Object[] arguments, final MethodProxy methodProxy) throws Throwable { + if (isPrintable(method)) { + hierarchyIn(); + log.trace("{}visit node: {}", hierarchyIndex, arguments[0].getClass()); + log.trace("{}visit argument: {}", hierarchyIndex, arguments[0]); + } + Object result = methodProxy.invokeSuper(enhancedObject, arguments); + if (isPrintable(method)) { + AbstractMySQLVisitor visitor = (AbstractMySQLVisitor) enhancedObject; + log.trace("{}endVisit node: {}", hierarchyIndex, arguments[0].getClass()); + log.trace("{}endVisit result: {}", hierarchyIndex, visitor.getParseContext().getParsedResult()); + log.trace("{}endVisit condition: {}", hierarchyIndex, visitor.getParseContext().getCurrentConditionContext()); + log.trace("{}endVisit SQL: {}", hierarchyIndex, visitor.getSQLBuilder()); + hierarchyOut(); + } + return result; + } + + private boolean isPrintable(final Method method) { + return log.isTraceEnabled() && "visit".equals(method.getName()); + } + + private void hierarchyIn() { + hierarchyIndex.append(" ").append(++depth).append(" "); + } + + private void hierarchyOut() { + hierarchyIndex.delete(hierarchyIndex.length() - 3 - depth.toString().length(), hierarchyIndex.length()); + depth--; + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/AbstractMySQLVisitor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/AbstractMySQLVisitor.java new file mode 100644 index 0000000000000..c6767aa92857c --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/AbstractMySQLVisitor.java @@ -0,0 +1,165 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql; + +import java.util.Arrays; + +import com.alibaba.druid.sql.ast.SQLHint; +import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLInListExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.BinaryOperator; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.ParseContext; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.SQLVisitor; + +/** + * MySQL解析基础访问器. + * + * @author zhangliang + */ +public abstract class AbstractMySQLVisitor extends MySqlOutputVisitor implements SQLVisitor { + + private final ParseContext parseContext = new ParseContext(); + + public AbstractMySQLVisitor() { + super(new SQLBuilder()); + setPrettyFormat(false); + } + + @Override + public final DatabaseType getDatabaseType() { + return DatabaseType.MySQL; + } + + @Override + public final ParseContext getParseContext() { + return parseContext; + } + + @Override + public final SQLBuilder getSQLBuilder() { + return (SQLBuilder) appender; + } + + @Override + public final void printToken(final String token) { + getSQLBuilder().appendToken(parseContext.getExactlyValue(token)); + } + + /** + * 父类使用@@代替?,此处直接输出参数占位符? + * + * @param x 变量表达式 + * @return false 终止遍历AST + */ + @Override + public final boolean visit(final SQLVariantRefExpr x) { + print(x.getName()); + return false; + } + + @Override + public final boolean visit(final SQLExprTableSource x) { + return visit(x, parseContext.addTable(x)); + } + + private boolean visit(final SQLExprTableSource x, final Table table) { + printToken(table.getName()); + if (table.getAlias().isPresent()) { + print(' '); + print(table.getAlias().get()); + } + for (SQLHint each : x.getHints()) { + print(' '); + each.accept(this); + } + return false; + } + + /** + * 将表名替换成占位符. + * + *

+ * 1. 如果二元表达式使用别名, 如: + * {@code FROM order o WHERE o.column_name = 't' }, 则Column中的tableName为o. + *

+ * + *

+ * 2. 如果二元表达式使用表名, 如: + * {@code FROM order WHERE order.column_name = 't' }, 则Column中的tableName为order. + *

+ * + * @param x SQL属性表达式 + * @return true表示继续遍历AST, false表示终止遍历AST + */ + @Override + // TODO SELECT [别名.xxx]的情况,目前都是替换成token,解析之后应该替换回去 + public final boolean visit(final SQLPropertyExpr x) { + if (!(x.getParent() instanceof SQLBinaryOpExpr) && !(x.getParent() instanceof SQLSelectItem)) { + return super.visit(x); + } + if (!(x.getOwner() instanceof SQLIdentifierExpr)) { + return super.visit(x); + } + String tableOrAliasName = ((SQLIdentifierExpr) x.getOwner()).getLowerName(); + if (parseContext.isBinaryOperateWithAlias(x, tableOrAliasName)) { + return super.visit(x); + } + printToken(tableOrAliasName); + print("."); + print(x.getName()); + return false; + } + + @Override + public boolean visit(final SQLBinaryOpExpr x) { + switch (x.getOperator()) { + case BooleanOr: + parseContext.setHasOrCondition(true); + break; + case Equality: + parseContext.addCondition(x.getLeft(), BinaryOperator.EQUAL, Arrays.asList(x.getRight()), getDatabaseType(), getParameters()); + parseContext.addCondition(x.getRight(), BinaryOperator.EQUAL, Arrays.asList(x.getLeft()), getDatabaseType(), getParameters()); + break; + default: + break; + } + return super.visit(x); + } + + @Override + public boolean visit(final SQLInListExpr x) { + parseContext.addCondition(x.getExpr(), x.isNot() ? BinaryOperator.NOT_IN : BinaryOperator.IN, x.getTargetList(), getDatabaseType(), getParameters()); + return super.visit(x); + } + + @Override + public boolean visit(final SQLBetweenExpr x) { + parseContext.addCondition(x.getTestExpr(), BinaryOperator.BETWEEN, Arrays.asList(x.getBeginExpr(), x.getEndExpr()), getDatabaseType(), getParameters()); + return super.visit(x); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLDeleteVisitor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLDeleteVisitor.java new file mode 100644 index 0000000000000..0115da00ea271 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLDeleteVisitor.java @@ -0,0 +1,35 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql; + +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement; +import com.google.common.base.Optional; + +/** + * MySQL的DELETE语句访问器. + * + * @author gaohongtao, zhangliang + */ +public class MySQLDeleteVisitor extends AbstractMySQLVisitor { + + @Override + public boolean visit(final MySqlDeleteStatement x) { + getParseContext().setCurrentTable(x.getTableName().toString(), Optional.fromNullable(x.getAlias())); + return super.visit(x); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLInsertVisitor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLInsertVisitor.java new file mode 100644 index 0000000000000..84d98c01efc66 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLInsertVisitor.java @@ -0,0 +1,39 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql; + +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.BinaryOperator; +import com.google.common.base.Optional; + +/** + * MySQL的INSERT语句访问器. + * + * @author gaohongtao, zhangliang + */ +public class MySQLInsertVisitor extends AbstractMySQLVisitor { + + @Override + public boolean visit(final MySqlInsertStatement x) { + getParseContext().setCurrentTable(x.getTableName().toString(), Optional.fromNullable(x.getAlias())); + for (int i = 0; i < x.getColumns().size(); i++) { + getParseContext().addCondition(x.getColumns().get(i).toString(), x.getTableName().toString(), BinaryOperator.EQUAL, x.getValues().getValues().get(i), getDatabaseType(), getParameters()); + } + return super.visit(x); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLSelectVisitor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLSelectVisitor.java new file mode 100644 index 0000000000000..99feb457fd051 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLSelectVisitor.java @@ -0,0 +1,195 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql; + +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; +import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlSelectGroupByExpr; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; +import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn.AggregationType; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.GroupByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.Limit; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; +import com.google.common.base.Optional; + +/** + * MySQL的SELECT语句访问器. + * + * @author gaohongtao, zhangliang + */ +public class MySQLSelectVisitor extends AbstractMySQLVisitor { + + private static final String AUTO_GEN_TOKE_KEY = "sharding_auto_gen"; + + // TODO 封装到方法内部 + private int itemIndex; + + @Override + protected void printSelectList(final List selectList) { + super.printSelectList(selectList); + // TODO 提炼成print,或者是否不应该由token的方式替换? + getSQLBuilder().appendToken(AUTO_GEN_TOKE_KEY, false); + } + + @Override + public boolean visit(final MySqlSelectQueryBlock x) { + if (x.getFrom() instanceof SQLExprTableSource) { + SQLExprTableSource tableExpr = (SQLExprTableSource) x.getFrom(); + getParseContext().setCurrentTable(tableExpr.getExpr().toString(), Optional.fromNullable(tableExpr.getAlias())); + } + return super.visit(x); + } + + /** + * 解析 {@code SELECT item1,item2 FROM }中的item. + * + * @param x SELECT item 表达式 + * @return true表示继续遍历AST, false表示终止遍历AST + */ + // TODO SELECT * 导致index不准,不支持SELECT *,且生产环境不建议使用SELECT * + public boolean visit(final SQLSelectItem x) { + itemIndex++; + return super.visit(x); + } + + @Override + public boolean visit(final SQLAggregateExpr x) { + if (!(x.getParent() instanceof SQLSelectItem)) { + return super.visit(x); + } + AggregationType aggregationType; + try { + aggregationType = AggregationType.valueOf(x.getMethodName().toUpperCase()); + } catch (final IllegalArgumentException ex) { + return super.visit(x); + } + StringBuilder expression = new StringBuilder(); + x.accept(new MySqlOutputVisitor(expression)); + // TODO index获取不准,考虑使用别名替换 + AggregationColumn column = new AggregationColumn(expression.toString(), aggregationType, Optional.fromNullable(((SQLSelectItem) x.getParent()).getAlias()), + null == x.getOption() ? Optional.absent() : Optional.of(x.getOption().toString()), itemIndex); + getParseContext().getParsedResult().getMergeContext().getAggregationColumns().add(column); + if (AggregationType.AVG.equals(aggregationType)) { + getParseContext().addDerivedColumnsForAvgColumn(column); + // TODO 将AVG列替换成常数,避免数据库再计算无用的AVG函数 + } + return super.visit(x); + } + + public boolean visit(final SQLOrderBy x) { + for (SQLSelectOrderByItem each : x.getItems()) { + SQLExpr expr = each.getExpr(); + OrderByType orderByType = null == each.getType() ? OrderByType.ASC : OrderByType.valueOf(each.getType()); + if (expr instanceof SQLIntegerExpr) { + getParseContext().addOrderByColumn(((SQLIntegerExpr) expr).getNumber().intValue(), orderByType); + } else if (expr instanceof SQLIdentifierExpr) { + getParseContext().addOrderByColumn(((SQLIdentifierExpr) expr).getName(), orderByType); + } else if (expr instanceof SQLPropertyExpr) { + getParseContext().addOrderByColumn(((SQLPropertyExpr) expr).getName(), orderByType); + } + } + return super.visit(x); + } + + /** + * 将GROUP BY列放入parseResult. + * 直接返回false,防止重复解析GROUP BY表达式. + * + * @param x GROUP BY 表达式 + * @return false 停止遍历AST + */ + @Override + public boolean visit(final MySqlSelectGroupByExpr x) { + String alias = getParseContext().generateDerivedColumnAlias(); + OrderByType orderByType = null == x.getType() ? OrderByType.ASC : OrderByType.valueOf(x.getType()); + if (x.getExpr() instanceof SQLPropertyExpr) { + SQLPropertyExpr expr = (SQLPropertyExpr) x.getExpr(); + getParseContext().addGroupByColumns(expr.toString(), alias, orderByType); + } else if (x.getExpr() instanceof SQLIdentifierExpr) { + SQLIdentifierExpr expr = (SQLIdentifierExpr) x.getExpr(); + getParseContext().addGroupByColumns(expr.getName(), alias, orderByType); + } else { + return super.visit(x); + } + return super.visit(x); + } + + /** + * LIMIT 解析. + * + * @param x LIMIT表达式 + * @return false 停止遍历AST + */ + @Override + public boolean visit(final MySqlSelectQueryBlock.Limit x) { + print("LIMIT "); + int offset = 0; + if (null != x.getOffset()) { + if (x.getOffset() instanceof SQLNumericLiteralExpr) { + offset = ((SQLNumericLiteralExpr) x.getOffset()).getNumber().intValue(); + print("0, "); + } else { + offset = ((Number) getParameters().get(((SQLVariantRefExpr) x.getOffset()).getIndex())).intValue(); + getParameters().set(((SQLVariantRefExpr) x.getOffset()).getIndex(), 0); + print("?, "); + } + } + int rowCount; + if (x.getRowCount() instanceof SQLNumericLiteralExpr) { + rowCount = ((SQLNumericLiteralExpr) x.getRowCount()).getNumber().intValue(); + print(rowCount + offset); + } else { + rowCount = ((Number) getParameters().get(((SQLVariantRefExpr) x.getRowCount()).getIndex())).intValue(); + getParameters().set(((SQLVariantRefExpr) x.getRowCount()).getIndex(), rowCount + offset); + print("?"); + } + getParseContext().getParsedResult().getMergeContext().setLimit(new Limit(offset, rowCount)); + return false; + } + + @Override + public void endVisit(final SQLSelectStatement x) { + StringBuilder derivedSelectItems = new StringBuilder(); + for (AggregationColumn aggregationColumn : getParseContext().getParsedResult().getMergeContext().getAggregationColumns()) { + for (AggregationColumn derivedColumn : aggregationColumn.getDerivedColumns()) { + derivedSelectItems.append(", ").append(derivedColumn.getExpression()).append(" AS ").append(derivedColumn.getAlias().get()); + } + } + for (GroupByColumn each : getParseContext().getParsedResult().getMergeContext().getGroupByColumns()) { + derivedSelectItems.append(", ").append(each.getName()).append(" AS ").append(each.getAlias()); + } + if (0 != derivedSelectItems.length()) { + getSQLBuilder().buildSQL(AUTO_GEN_TOKE_KEY, derivedSelectItems.toString()); + } + super.endVisit(x); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLUpdateVisitor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLUpdateVisitor.java new file mode 100644 index 0000000000000..07734f85c7ee2 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/basic/mysql/MySQLUpdateVisitor.java @@ -0,0 +1,35 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql; + +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement; +import com.google.common.base.Optional; + +/** + * MySQL的UPDATE语句访问器. + * + * @author gaohongtao, zhangliang + */ +public class MySQLUpdateVisitor extends AbstractMySQLVisitor { + + @Override + public boolean visit(final MySqlUpdateStatement x) { + getParseContext().setCurrentTable(x.getTableName().toString(), Optional.absent()); + return super.visit(x); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/OrParser.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/OrParser.java new file mode 100644 index 0000000000000..d6b79e4beef9f --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/OrParser.java @@ -0,0 +1,55 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.or; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResult; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.or.node.AbstractOrASTNode; +import com.google.common.base.Optional; + +/** + * OR表达式解析类. + * + * @author gaohongtao + */ +public final class OrParser { + + private final SQLStatement sqlStatement; + + private final OrVisitor orVisitor; + + public OrParser(final SQLStatement sqlStatement, final SQLASTOutputVisitor dependencyVisitor) { + this.sqlStatement = sqlStatement; + orVisitor = new OrVisitor(dependencyVisitor); + } + + /** + *  解析SQL. + * + * @return SQL解析结果 + */ + public SQLParsedResult parse() { + SQLParsedResult result = orVisitor.getParseContext().getParsedResult(); + Optional rootASTNode = orVisitor.visitHandle(sqlStatement); + if (rootASTNode.isPresent()) { + result.getConditionContexts().addAll(rootASTNode.get().getCondition()); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/OrVisitor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/OrVisitor.java new file mode 100644 index 0000000000000..490230d9e8888 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/OrVisitor.java @@ -0,0 +1,98 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.or; + +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.visitor.SQLASTOutputVisitor; +import com.alibaba.druid.wall.spi.WallVisitorUtils; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.SQLVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql.AbstractMySQLVisitor; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.or.node.AbstractOrASTNode; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.or.node.CompositeOrASTNode; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.or.node.SimpleOrASTNode; +import com.google.common.base.Optional; + +/** + * 逻辑OR条件访问器. + * + * @author gaohongtao + */ +public class OrVisitor extends AbstractMySQLVisitor { + + private AbstractOrASTNode orASTNode; + + public OrVisitor(final SQLASTOutputVisitor dependencyVisitor) { + setParameters(dependencyVisitor.getParameters()); + SQLVisitor visitor = (SQLVisitor) dependencyVisitor; + String currentTableName = null == visitor.getParseContext().getCurrentTable() ? "" : visitor.getParseContext().getCurrentTable().getName(); + getParseContext().setCurrentTable(currentTableName, Optional.absent()); + getParseContext().setShardingColumns(visitor.getParseContext().getShardingColumns()); + } + + /** + * 进行OR表达式的访问. + * + * @param sqlObject SQL对象 + * @return OR访问节点 + */ + public Optional visitHandle(final SQLObject sqlObject) { + reset(); + sqlObject.accept(this); + postVisitHandle(); + return Optional.fromNullable(orASTNode); + } + + private void reset() { + orASTNode = null; + getParseContext().getCurrentConditionContext().clear(); + getParseContext().setHasOrCondition(false); + } + + private void postVisitHandle() { + if (null == orASTNode) { + return; + } + if (!getParseContext().getCurrentConditionContext().isEmpty()) { + CompositeOrASTNode existingOutConditionOrASTNode = new CompositeOrASTNode(); + existingOutConditionOrASTNode.addSubNode(orASTNode); + existingOutConditionOrASTNode.addOutConditions(getParseContext().getCurrentConditionContext()); + orASTNode = existingOutConditionOrASTNode; + } + orASTNode.createOrASTAsRootNode(); + } + + /** + * 逻辑OR访问器, 每次只解析一层OR条件. + * + * @param x 二元表达式 + * @return false 停止访问AST + */ + @Override + public boolean visit(final SQLBinaryOpExpr x) { + if (!SQLBinaryOperator.BooleanOr.equals(x.getOperator())) { + return super.visit(x); + } + if (Boolean.TRUE.equals(WallVisitorUtils.getValue(x))) { + return false; + } + orASTNode = new SimpleOrASTNode(x, new OrVisitor(this)); + return false; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/AbstractOrASTNode.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/AbstractOrASTNode.java new file mode 100644 index 0000000000000..d4146ac90ecb4 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/AbstractOrASTNode.java @@ -0,0 +1,107 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.or.node; + +import java.util.ArrayList; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.google.common.base.Function; +import com.google.common.collect.Lists; + +import lombok.AccessLevel; +import lombok.Getter; + +/** + * 抽象的OR语法树节点. + * + * @author gaohongtao + */ +@Getter(AccessLevel.PROTECTED) +public abstract class AbstractOrASTNode { + + private List subNodes = new ArrayList<>(); + + private List> nestedConditions = new ArrayList<>(); + + public final void addSubNode(final AbstractOrASTNode node) { + subNodes.add(node); + } + + protected final void addNestedConditions(final ConditionContext conditionContext) { + nestedConditions.add(Lists.newArrayList(conditionContext.getAllConditions())); + } + + /** + * 使用该节点作为根节点生成抽象语法树. + * + *

+ * 使用深度优先后续的方式生成语法树. + * 其中后续遍历是由于DRUID进行SQL语法解析时产生的行为. + *

+ */ + public abstract void createOrASTAsRootNode(); + + /** + * 获取解析结果需要的条件. + * + * @return 解析后的条件 + */ + public final List getCondition() { + return Lists.transform(nestedConditions, new Function, ConditionContext>() { + + @Override + public ConditionContext apply(final List input) { + ConditionContext result = new ConditionContext(); + for (Condition each : input) { + result.add(each); + } + return result; + } + }); + } + + /** + * 多个子节点之间做笛卡尔积. + */ + protected final void mergeSubConditions() { + if (subNodes.isEmpty()) { + return; + } + List> result = new ArrayList<>(); + result.addAll(subNodes.get(0).getNestedConditions()); + for (int i = 1; i < subNodes.size(); i++) { + result = cartesianNestedConditions(result, subNodes.get(i).getNestedConditions()); + } + nestedConditions.addAll(result); + } + + private List> cartesianNestedConditions(final List> oneNestedConditions, final List> anotherNestedConditions) { + List> result = new ArrayList<>(); + for (List oneNestedCondition : oneNestedConditions) { + for (List anotherNestedCondition : anotherNestedConditions) { + List mergedConditions = new ArrayList<>(); + mergedConditions.addAll(oneNestedCondition); + mergedConditions.addAll(anotherNestedCondition); + result.add(mergedConditions); + } + } + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/CompositeOrASTNode.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/CompositeOrASTNode.java new file mode 100644 index 0000000000000..1aadf83a48cf4 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/CompositeOrASTNode.java @@ -0,0 +1,52 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.or.node; + +import java.util.ArrayList; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; + +import lombok.Getter; + +/** + * 存在外层条件的节点. + * + * @author gaohongtao + */ +@Getter +public class CompositeOrASTNode extends AbstractOrASTNode { + + private List outConditions = new ArrayList<>(); + + public void addOutConditions(final ConditionContext outConditions) { + this.outConditions.addAll(outConditions.getAllConditions()); + } + + @Override + public void createOrASTAsRootNode() { + for (AbstractOrASTNode each : getSubNodes()) { + each.createOrASTAsRootNode(); + } + mergeSubConditions(); + for (List each : getNestedConditions()) { + each.addAll(outConditions); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/SimpleOrASTNode.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/SimpleOrASTNode.java new file mode 100644 index 0000000000000..e0dbc9e603642 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/parser/visitor/or/node/SimpleOrASTNode.java @@ -0,0 +1,72 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.visitor.or.node; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.wall.spi.WallVisitorUtils; +import com.dangdang.ddframe.rdb.sharding.parser.visitor.or.OrVisitor; +import com.google.common.base.Optional; + +import lombok.AllArgsConstructor; + +/** + * 只包含OR的节点. + * + * @author gaohongtao + */ +@AllArgsConstructor +public class SimpleOrASTNode extends AbstractOrASTNode { + + private SQLBinaryOpExpr canSplitExpr; + + private final OrVisitor orVisitor; + + @Override + public void createOrASTAsRootNode() { + if (SQLBinaryOperator.BooleanOr == canSplitExpr.getOperator()) { + parseExprIfNotFalse(canSplitExpr.getRight()); + if (canSplitExpr.getLeft() instanceof SQLBinaryOpExpr) { + canSplitExpr = (SQLBinaryOpExpr) canSplitExpr.getLeft(); + createOrASTAsRootNode(); + } else { + finishParseThisNode(canSplitExpr.getLeft()); + } + } else { + finishParseThisNode(canSplitExpr); + } + } + + private void finishParseThisNode(final SQLExpr expr) { + parseExprIfNotFalse(expr); + mergeSubConditions(); + } + + private void parseExprIfNotFalse(final SQLExpr expr) { + if (Boolean.FALSE.equals(WallVisitorUtils.getValue(expr))) { + return; + } + Optional subNode = orVisitor.visitHandle(expr); + if (subNode.isPresent()) { + addSubNode(subNode.get()); + } else { + addNestedConditions(orVisitor.getParseContext().getCurrentConditionContext()); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/RoutingResult.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/RoutingResult.java new file mode 100644 index 0000000000000..f93f04125525d --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/RoutingResult.java @@ -0,0 +1,38 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; + +/** + *  路由结果接口. + * + * @author zhangliang + */ +public interface RoutingResult { + + /** + * 获取SQL执行单元集合. + * + * @param sqlBuilder SQL构建器 + * @return SQL执行单元集合 + */ + Collection getSQLExecutionUnits(SQLBuilder sqlBuilder); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLExecutionUnit.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLExecutionUnit.java new file mode 100644 index 0000000000000..5303752c53128 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLExecutionUnit.java @@ -0,0 +1,43 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import lombok.Getter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +/** + * SQL最小执行单元. + * + * @author gaohongtao + */ +@Getter +@Slf4j +@ToString +public class SQLExecutionUnit { + + private final String dataSource; + + private final String sql; + + public SQLExecutionUnit(final String dataSource, final String sql) { + this.dataSource = dataSource; + this.sql = sql; + log.debug("route sql to db: [{}] sql: [{}]", dataSource, sql); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLRouteEngine.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLRouteEngine.java new file mode 100644 index 0000000000000..db063242675b6 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLRouteEngine.java @@ -0,0 +1,103 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import java.util.Collection; +import java.util.List; + +import com.codahale.metrics.Timer.Context; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; +import com.dangdang.ddframe.rdb.sharding.metrics.MetricsContext; +import com.dangdang.ddframe.rdb.sharding.parser.SQLParserFactory; +import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResult; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; +import com.dangdang.ddframe.rdb.sharding.router.binding.BindingTablesRouter; +import com.dangdang.ddframe.rdb.sharding.router.mixed.MixedTablesRouter; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleTableRouter; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; + +import lombok.RequiredArgsConstructor; + +/** + * SQL路由引擎. + * + * @author gaohongtao, zhangiang + */ +@RequiredArgsConstructor +public final class SQLRouteEngine { + + private final ShardingRule shardingRule; + + private final DatabaseType databaseType; + + /** + * SQL路由. + * + * @param logicSql 逻辑SQL + * @return 路由结果 + * @throws SQLParserException SQL解析失败异常 + */ + public SQLRouteResult route(final String logicSql, final List parameters) throws SQLParserException { + return routeSQL(parseSQL(logicSql, parameters)); + } + + private SQLParsedResult parseSQL(final String logicSql, final List parameters) { + Context context = MetricsContext.start("Parse SQL"); + SQLParsedResult result = SQLParserFactory.create(databaseType, logicSql, parameters, shardingRule.getAllShardingColumns()).parse(); + MetricsContext.stop(context); + return result; + } + + private SQLRouteResult routeSQL(final SQLParsedResult parsedResult) { + Context context = MetricsContext.start("Route SQL"); + SQLRouteResult result = new SQLRouteResult(parsedResult.getMergeContext()); + for (ConditionContext each : parsedResult.getConditionContexts()) { + result.getExecutionUnits().addAll(routeSQL(each, Collections2.transform(parsedResult.getRouteContext().getTables(), new Function() { + + @Override + public String apply(final Table input) { + return input.getName(); + } + }), parsedResult.getRouteContext().getSqlBuilder())); + } + MetricsContext.stop(context); + return result; + } + + private Collection routeSQL(final ConditionContext conditionContext, final Collection logicTables, final SQLBuilder sqlBuilder) { + RoutingResult result; + if (1 == logicTables.size()) { + result = new SingleTableRouter(shardingRule, logicTables.iterator().next(), conditionContext).route(); + } else if (shardingRule.isAllBindingTable(logicTables)) { + result = new BindingTablesRouter(shardingRule, logicTables, conditionContext).route(); + } else { + // TODO 可配置是否执行笛卡尔积 + result = new MixedTablesRouter(shardingRule, logicTables, conditionContext).route(); + } + if (null == result) { + throw new ShardingJdbcException("Sharding-JDBC: cannot route any result, please check your sharding rule."); + } + return result.getSQLExecutionUnits(sqlBuilder); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLRouteResult.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLRouteResult.java new file mode 100644 index 0000000000000..997176d1f18f9 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/SQLRouteResult.java @@ -0,0 +1,40 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import java.util.ArrayList; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * SQL路由结果. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +@Getter +public final class SQLRouteResult { + + private final MergeContext mergeContext; + + private final List executionUnits = new ArrayList<>(); +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingDataSource.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingDataSource.java new file mode 100644 index 0000000000000..d8b5124e2d686 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingDataSource.java @@ -0,0 +1,55 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.binding; + +import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingDataSource; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingTableFactor; +import com.google.common.base.Function; +import com.google.common.collect.Lists; + +import lombok.Getter; +import lombok.ToString; + +/** + * Binding表路由数据源. + * + * @author zhangliang + */ +@Getter +@ToString(callSuper = true) +final class BindingRoutingDataSource extends SingleRoutingDataSource { + + BindingRoutingDataSource(final SingleRoutingDataSource routingDataSource) { + super(routingDataSource.getDataSource()); + getRoutingTableFactors().addAll(Lists.transform(routingDataSource.getRoutingTableFactors(), new Function() { + + @Override + public BindingRoutingTableFactor apply(final SingleRoutingTableFactor input) { + return new BindingRoutingTableFactor(input.getLogicTable(), input.getActualTable()); + } + })); + } + + void bind(final BindingTableRule bindingTableRule, final String bindingLogicTable) { + for (SingleRoutingTableFactor each : getRoutingTableFactors()) { + ((BindingRoutingTableFactor) each).getBindingRoutingTableFactors().add( + new BindingRoutingTableFactor(bindingLogicTable, bindingTableRule.getBindingActualTable(getDataSource(), bindingLogicTable, each.getActualTable()))); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingResult.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingResult.java new file mode 100644 index 0000000000000..cdaa490d4ecd1 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingResult.java @@ -0,0 +1,51 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.binding; + +import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingDataSource; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingResult; +import com.google.common.base.Function; +import com.google.common.collect.Lists; + +import lombok.ToString; + +/** + * Binding表路由结果. + * + * @author zhangliang + */ +@ToString(callSuper = true) +final class BindingRoutingResult extends SingleRoutingResult { + + BindingRoutingResult(final SingleRoutingResult singleRoutingResult) { + getRoutingDataSources().addAll(Lists.transform(singleRoutingResult.getRoutingDataSources(), new Function() { + + @Override + public BindingRoutingDataSource apply(final SingleRoutingDataSource input) { + return new BindingRoutingDataSource(input); + } + })); + } + + void bind(final BindingTableRule bindingTableRule, final String bindingLogicTable) { + for (SingleRoutingDataSource each : getRoutingDataSources()) { + ((BindingRoutingDataSource) each).bind(bindingTableRule, bindingLogicTable); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingTableFactor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingTableFactor.java new file mode 100644 index 0000000000000..99c759bb4ffd3 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingTableFactor.java @@ -0,0 +1,52 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.binding; + +import java.util.ArrayList; +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingTableFactor; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.ToString; + +/** + * Binding表路由表单元. + * + * @author zhangliang + */ +@ToString(callSuper = true) +final class BindingRoutingTableFactor extends SingleRoutingTableFactor { + + @Getter(AccessLevel.PACKAGE) + private final Collection bindingRoutingTableFactors = new ArrayList<>(); + + BindingRoutingTableFactor(final String logicTable, final String actualTable) { + super(logicTable, actualTable); + } + + @Override + public void buildSQL(final SQLBuilder builder) { + super.buildSQL(builder); + for (BindingRoutingTableFactor each : bindingRoutingTableFactors) { + each.buildSQL(builder); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingTablesRouter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingTablesRouter.java new file mode 100644 index 0000000000000..0d7dfb9a250df --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingTablesRouter.java @@ -0,0 +1,73 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.binding; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleTableRouter; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +import lombok.extern.slf4j.Slf4j; + +/** + * Binding库表路由类. + * + * @author zhangliang + */ +@Slf4j +public class BindingTablesRouter { + + private final ShardingRule shardingRule; + + private final Collection logicTables; + + private final ConditionContext conditionContext; + + private final BindingTableRule bindingTableRule; + + public BindingTablesRouter(final ShardingRule shardingRule, final Collection logicTables, final ConditionContext conditionContext) { + this.shardingRule = shardingRule; + this.logicTables = logicTables; + this.conditionContext = conditionContext; + Optional optionalBindingTableRule = shardingRule.getBindingTableRule(logicTables.iterator().next()); + Preconditions.checkState(optionalBindingTableRule.isPresent()); + bindingTableRule = optionalBindingTableRule.get(); + } + + /** + * 路由. + * + * @return 路由结果 + */ + public BindingRoutingResult route() { + BindingRoutingResult result = null; + for (final String each : logicTables) { + if (null == result) { + result = new BindingRoutingResult(new SingleTableRouter(shardingRule, each, conditionContext).route()); + } else { + result.bind(bindingTableRule, each); + } + } + log.trace("binding table sharding result: {}", result); + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianDataSource.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianDataSource.java new file mode 100644 index 0000000000000..795650476a30a --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianDataSource.java @@ -0,0 +1,57 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.mixed; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit; + +import lombok.Getter; +import lombok.ToString; + +/** + * 笛卡尔积路由数据源. + * + * @author zhangliang + */ +@Getter +@ToString +final class CartesianDataSource { + + private final String dataSource; + + private final List routingTableReferences; + + CartesianDataSource(final String dataSource, final CartesianTableReference routingTableReference) { + this.dataSource = dataSource; + routingTableReferences = new ArrayList<>(Arrays.asList(routingTableReference)); + } + + Collection getSQLExecutionUnits(final SQLBuilder sqlBuilder) { + Collection result = new ArrayList<>(); + for (CartesianTableReference each : routingTableReferences) { + each.buildSQL(sqlBuilder); + result.add(new SQLExecutionUnit(dataSource, sqlBuilder.toSQL())); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianResult.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianResult.java new file mode 100644 index 0000000000000..d16c6100b3fd5 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianResult.java @@ -0,0 +1,66 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.mixed; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.router.RoutingResult; +import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit; + +import lombok.Getter; +import lombok.ToString; + +/** + * 笛卡尔积路由结果. + * + * @author gaohongtao, zhangliang + */ +@ToString +final class CartesianResult implements RoutingResult { + + @Getter + private final List routingDataSources = new ArrayList<>(); + + void merge(final String dataSource, final Collection routingTableReferences) { + for (CartesianTableReference each : routingTableReferences) { + merge(dataSource, each); + } + } + + private void merge(final String dataSource, final CartesianTableReference routingTableReference) { + for (CartesianDataSource each : routingDataSources) { + if (each.getDataSource().equals(dataSource)) { + each.getRoutingTableReferences().add(routingTableReference); + return; + } + } + routingDataSources.add(new CartesianDataSource(dataSource, routingTableReference)); + } + + @Override + public Collection getSQLExecutionUnits(final SQLBuilder sqlBuilder) { + Collection result = new ArrayList<>(); + for (CartesianDataSource each : routingDataSources) { + result.addAll(each.getSQLExecutionUnits(sqlBuilder)); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianTableReference.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianTableReference.java new file mode 100644 index 0000000000000..043980b79d088 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianTableReference.java @@ -0,0 +1,49 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.mixed; + +import java.util.ArrayList; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingTableFactor; + +import lombok.Getter; +import lombok.ToString; + +/** + * 笛卡尔积表路由组. + * + * @author gaohongtao, zhangliang + */ +@ToString +@Getter +final class CartesianTableReference { + + private final List routingTableFactors; + + CartesianTableReference(final List routingTableFactors) { + this.routingTableFactors = new ArrayList<>(routingTableFactors); + } + + void buildSQL(final SQLBuilder builder) { + for (SingleRoutingTableFactor each : routingTableFactors) { + each.buildSQL(builder); + } + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianTablesRouter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianTablesRouter.java new file mode 100644 index 0000000000000..9b1b5c1b7840f --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianTablesRouter.java @@ -0,0 +1,126 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.mixed; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingResult; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingTableFactor; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 笛卡尔积的库表路由. + * + * @author zhangliang + */ +@RequiredArgsConstructor +@Slf4j +final class CartesianTablesRouter { + + private final Collection routingResults; + + CartesianResult route() { + CartesianResult result = new CartesianResult(); + for (Entry> entry : getDataSourceLogicTablesMap().entrySet()) { + List> actualTableGroups = getActualTableGroups(entry.getKey(), entry.getValue()); + List> routingTableFactorGroups = toRoutingTableFactorGroups(entry.getKey(), actualTableGroups); + result.merge(entry.getKey(), getCartesianTableReferences(Sets.cartesianProduct(routingTableFactorGroups))); + } + log.trace("cartesian tables sharding result: {}", result); + return result; + } + + private Map> getDataSourceLogicTablesMap() { + Collection intersectionDataSources = getIntersectionDataSources(); + Map> result = new HashMap<>(routingResults.size()); + for (SingleRoutingResult each : routingResults) { + for (Entry> entry : each.getDataSourceLogicTablesMap(intersectionDataSources).entrySet()) { + if (result.containsKey(entry.getKey())) { + result.get(entry.getKey()).addAll(entry.getValue()); + } else { + result.put(entry.getKey(), entry.getValue()); + } + } + } + return result; + } + + private Collection getIntersectionDataSources() { + Collection result = new HashSet<>(); + for (SingleRoutingResult each : routingResults) { + if (result.isEmpty()) { + result.addAll(each.getDataSources()); + } + result.retainAll(each.getDataSources()); + } + return result; + } + + private List> getActualTableGroups(final String dataSource, final Set logicTables) { + List> result = new ArrayList<>(logicTables.size()); + for (SingleRoutingResult each : routingResults) { + result.addAll(each.getActualTableGroups(dataSource, logicTables)); + } + return result; + } + + private List> toRoutingTableFactorGroups(final String dataSource, final List> actualTableGroups) { + List> result = new ArrayList<>(actualTableGroups.size()); + for (Set each : actualTableGroups) { + result.add(new HashSet(Lists.transform(new ArrayList(each), new Function() { + + @Override + public SingleRoutingTableFactor apply(final String input) { + return findRoutingTableFactor(dataSource, input); + } + }))); + } + return result; + } + + private SingleRoutingTableFactor findRoutingTableFactor(final String dataSource, final String actualTable) { + for (SingleRoutingResult each : routingResults) { + Optional result = each.findRoutingTableFactor(dataSource, actualTable); + if (result.isPresent()) { + return result.get(); + } + } + throw new IllegalStateException(String.format("Cannot found routing table factor, data source: %s, actual table: %s", dataSource, actualTable)); + } + + private List getCartesianTableReferences(final Set> cartesianRoutingTableFactorGroups) { + List result = new ArrayList<>(cartesianRoutingTableFactorGroups.size()); + for (List each : cartesianRoutingTableFactorGroups) { + result.add(new CartesianTableReference(each)); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/MixedTablesRouter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/MixedTablesRouter.java new file mode 100644 index 0000000000000..54e8e61d4f4cc --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/mixed/MixedTablesRouter.java @@ -0,0 +1,77 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.mixed; + +import java.util.ArrayList; +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.router.RoutingResult; +import com.dangdang.ddframe.rdb.sharding.router.binding.BindingTablesRouter; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingResult; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleTableRouter; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 混合多库表路由类. + * + * @author gaohongtao, zhangliang + */ +@RequiredArgsConstructor +@Slf4j +public class MixedTablesRouter { + + private final ShardingRule shardingRule; + + private final Collection logicTables; + + private final ConditionContext conditionContext; + + /** + * 路由. + * + * @return 路由结果 + */ + // TODO 支持多bindtable rule + public RoutingResult route() { + Collection bindingTables = shardingRule.filterAllBindingTables(logicTables); + Collection remainingTables = new ArrayList<>(logicTables); + Collection result = new ArrayList<>(logicTables.size()); + if (1 < bindingTables.size()) { + result.add(new BindingTablesRouter(shardingRule, bindingTables, conditionContext).route()); + remainingTables.removeAll(bindingTables); + } + for (String each : remainingTables) { + SingleRoutingResult routingResult = new SingleTableRouter(shardingRule, each, conditionContext).route(); + if (null != routingResult) { + result.add(routingResult); + } + } + log.trace("mixed tables sharding result: {}", result); + if (result.isEmpty()) { + return null; + } + if (1 == result.size()) { + return result.iterator().next(); + } + return new CartesianTablesRouter(result).route(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingDataSource.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingDataSource.java new file mode 100644 index 0000000000000..2b285f433a919 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingDataSource.java @@ -0,0 +1,107 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.single; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; + +import lombok.Getter; +import lombok.ToString; + +/** + * 单表路由数据源. + * + * @author zhangliang + */ +@Getter +@ToString +public class SingleRoutingDataSource { + + private final String dataSource; + + private final List routingTableFactors = new ArrayList<>(); + + public SingleRoutingDataSource(final String dataSource) { + this.dataSource = dataSource; + } + + SingleRoutingDataSource(final String dataSource, final SingleRoutingTableFactor routingTableFactor) { + this(dataSource); + routingTableFactors.add(routingTableFactor); + } + + Collection getSQLExecutionUnits(final SQLBuilder sqlBuilder) { + Collection result = new ArrayList<>(); + for (SingleRoutingTableFactor each : routingTableFactors) { + each.buildSQL(sqlBuilder); + result.add(new SQLExecutionUnit(dataSource, sqlBuilder.toSQL())); + } + return result; + } + + Set getLogicTables() { + Set result = new HashSet(routingTableFactors.size()); + result.addAll(Lists.transform(routingTableFactors, new Function() { + + @Override + public String apply(final SingleRoutingTableFactor input) { + return input.getLogicTable(); + } + })); + return result; + } + + List> getActualTableGroups(final Set logicTables) { + List> result = new ArrayList<>(); + for (String logicTable : logicTables) { + Set actualTables = getActualTables(logicTable); + if (!actualTables.isEmpty()) { + result.add(actualTables); + } + } + return result; + } + + private Set getActualTables(final String logicTable) { + Set result = new HashSet(); + for (SingleRoutingTableFactor each : routingTableFactors) { + if (each.getLogicTable().equals(logicTable)) { + result.add(each.getActualTable()); + } + } + return result; + } + + Optional findRoutingTableFactor(final String actualTable) { + for (SingleRoutingTableFactor each : routingTableFactors) { + if (each.getActualTable().equals(actualTable)) { + return Optional.of(each); + } + } + return Optional.absent(); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingResult.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingResult.java new file mode 100644 index 0000000000000..bafd678c733fe --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingResult.java @@ -0,0 +1,149 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.single; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.router.RoutingResult; +import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; + +import lombok.Getter; +import lombok.ToString; + +/** + * 单表路由结果. + * + * @author gaohongtao + */ +@ToString +public class SingleRoutingResult implements RoutingResult { + + @Getter + private final List routingDataSources = new ArrayList<>(); + + void put(final String dataSourceName, final SingleRoutingTableFactor routingTableFactor) { + for (SingleRoutingDataSource each : routingDataSources) { + if (each.getDataSource().equals(dataSourceName)) { + each.getRoutingTableFactors().add(routingTableFactor); + return; + } + } + routingDataSources.add(new SingleRoutingDataSource(dataSourceName, routingTableFactor)); + } + + /** + * 根据数据源名称获取数据源和逻辑表名称集合的映射关系. + * + * @param dataSources 待获取的数据源名称集合 + * @return 数据源和逻辑表名称集合的映射关系 + */ + public Map> getDataSourceLogicTablesMap(final Collection dataSources) { + Map> result = new HashMap<>(); + for (SingleRoutingDataSource each : routingDataSources) { + if (!dataSources.contains(each.getDataSource())) { + continue; + } + Set logicTables = each.getLogicTables(); + if (logicTables.isEmpty()) { + continue; + } + if (result.containsKey(each.getDataSource())) { + result.get(each.getDataSource()).addAll(logicTables); + } else { + result.put(each.getDataSource(), logicTables); + } + } + return result; + } + + /** + * 获取全部数据源名称. + * + * @return 数据源名称集合 + */ + public Collection getDataSources() { + return Lists.transform(routingDataSources, new Function() { + + @Override + public String apply(final SingleRoutingDataSource input) { + return input.getDataSource(); + } + }); + } + + /** + * 根据数据源和逻辑表名称获取真实表集合组. + *

+ * 每一组的真实表集合都属于同一逻辑表. + *

+ * + * @param dataSource 数据源名称 + * @param logicTables 逻辑表名称集合 + * @return 真实表集合组 + */ + public List> getActualTableGroups(final String dataSource, final Set logicTables) { + Optional routingDataSource = findRoutingDataSource(dataSource); + if (!routingDataSource.isPresent()) { + return Collections.emptyList(); + } + return routingDataSource.get().getActualTableGroups(logicTables); + } + + /** + * 根据数据源和真实表名称查找路由表单元. + * + * @param dataSource 数据源名称 + * @param actualTable 真实表名称 + * @return 查找结果 + */ + public Optional findRoutingTableFactor(final String dataSource, final String actualTable) { + Optional routingDataSource = findRoutingDataSource(dataSource); + if (!routingDataSource.isPresent()) { + return Optional.absent(); + } + return routingDataSource.get().findRoutingTableFactor(actualTable); + } + + private Optional findRoutingDataSource(final String dataSource) { + for (SingleRoutingDataSource each : routingDataSources) { + if (each.getDataSource().equals(dataSource)) { + return Optional.of(each); + } + } + return Optional.absent(); + } + + @Override + public Collection getSQLExecutionUnits(final SQLBuilder sqlBuilder) { + Collection result = new ArrayList<>(); + for (SingleRoutingDataSource each : routingDataSources) { + result.addAll(each.getSQLExecutionUnits(sqlBuilder)); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingTableFactor.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingTableFactor.java new file mode 100644 index 0000000000000..e308045c8de33 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingTableFactor.java @@ -0,0 +1,48 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.single; + +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +/** + * 单表路由表单元. + * + * @author gaohongtao + */ +@RequiredArgsConstructor +@Getter +@ToString +public class SingleRoutingTableFactor { + + private final String logicTable; + + private final String actualTable; + + /** + * 构建SQL. + * + * @param builder SQL构建器 + */ + public void buildSQL(final SQLBuilder builder) { + builder.buildSQL(logicTable, actualTable); + } +} diff --git a/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleTableRouter.java b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleTableRouter.java new file mode 100644 index 0000000000000..6211cbcecc3e6 --- /dev/null +++ b/sharding-jdbc-core/src/main/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleTableRouter.java @@ -0,0 +1,140 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.single; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.rule.DataNode; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.BoundType; +import com.google.common.collect.Range; + +import lombok.extern.slf4j.Slf4j; + +/** + * 单逻辑表的库表路由. + * + * @author gaohongtao, zhangliang + */ +@Slf4j +public final class SingleTableRouter { + + private final ShardingRule shardingRule; + + private final String logicTable; + + private final ConditionContext conditionContext; + + private final Optional tableRule; + + public SingleTableRouter(final ShardingRule shardingRule, final String logicTable, final ConditionContext conditionContext) { + this.shardingRule = shardingRule; + this.logicTable = logicTable; + this.conditionContext = conditionContext; + tableRule = shardingRule.findTableRule(logicTable); + } + + /** + * 路由. + * + * @return 路由结果 + */ + public SingleRoutingResult route() { + if (!tableRule.isPresent()) { + log.trace("Can not find table rule of [{}]", logicTable); + return null; + } + Collection routedDataSources = routeDataSources(); + Collection routedTables = routeTables(routedDataSources); + return generateRoutingResult(routedDataSources, routedTables); + } + + private Collection routeDataSources() { + DatabaseShardingStrategy strategy = shardingRule.getDatabaseShardingStrategy(tableRule.get()); + List> databaseShardingValues = getShardingValues(strategy.getShardingColumns()); + logBeforeRoute("database", logicTable, shardingRule.getDataSourceRule().getDataSourceNames(), strategy.getShardingColumns(), databaseShardingValues); + Collection result = new HashSet<>(strategy.doSharding(shardingRule.getDataSourceRule().getDataSourceNames(), databaseShardingValues)); + logAfterRoute("database", logicTable, result); + Preconditions.checkState(!result.isEmpty(), "no database route info"); + return result; + } + + private Collection routeTables(final Collection routedDataSources) { + TableShardingStrategy strategy = shardingRule.getTableShardingStrategy(tableRule.get()); + List> tableShardingValues = getShardingValues(strategy.getShardingColumns()); + logBeforeRoute("table", logicTable, tableRule.get().getActualTables(), strategy.getShardingColumns(), tableShardingValues); + Collection result = new HashSet<>(strategy.doSharding(tableRule.get().getActualTableNames(routedDataSources), tableShardingValues)); + logAfterRoute("table", logicTable, result); + Preconditions.checkState(!result.isEmpty(), "no table route info"); + return result; + } + + private List> getShardingValues(final Collection shardingColumns) { + List> result = new ArrayList<>(shardingColumns.size()); + for (String each : shardingColumns) { + Optional condition = conditionContext.find(logicTable, each); + if (condition.isPresent()) { + result.add(getShardingValue(condition.get())); + } + } + return result; + } + + private ShardingValue getShardingValue(final Condition condition) { + List> conditionValues = condition.getValues(); + switch (condition.getOperator()) { + case EQUAL: + case IN: + if (1 == conditionValues.size()) { + return new ShardingValue>(condition.getColumn().getColumnName(), conditionValues.get(0)); + } + return new ShardingValue<>(condition.getColumn().getColumnName(), conditionValues); + case BETWEEN: + return new ShardingValue<>(condition.getColumn().getColumnName(), Range.range(conditionValues.get(0), BoundType.CLOSED, conditionValues.get(1), BoundType.CLOSED)); + default: + throw new UnsupportedOperationException(condition.getOperator().getExpression()); + } + } + + private void logBeforeRoute(final String type, final String logicTable, final Collection targets, final Collection shardingColumns, final List> shardingValues) { + log.trace("Before {} sharding {} routes db names: {} sharding columns: {} sharding values: {}", type, logicTable, targets, shardingColumns, shardingValues); + } + + private void logAfterRoute(final String type, final String logicTable, final Collection shardingResults) { + log.trace("After {} sharding {} result: {}", type, logicTable, shardingResults); + } + + private SingleRoutingResult generateRoutingResult(final Collection routedDataSources, final Collection routedTables) { + SingleRoutingResult result = new SingleRoutingResult(); + for (DataNode each : tableRule.get().getActualDataNodes(routedDataSources, routedTables)) { + result.put(each.getDataSourceName(), new SingleRoutingTableFactor(logicTable, each.getTableName())); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/AllTests.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/AllTests.java new file mode 100644 index 0000000000000..bbe06f40c1d40 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/AllTests.java @@ -0,0 +1,43 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +import com.dangdang.ddframe.rdb.integrate.AllIntegrateTests; +import com.dangdang.ddframe.rdb.sharding.api.AllApiTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.AllJDBCTest; +import com.dangdang.ddframe.rdb.sharding.merger.AllMergerTest; +import com.dangdang.ddframe.rdb.sharding.metrics.AllMetricsTest; +import com.dangdang.ddframe.rdb.sharding.parser.AllParserTest; +import com.dangdang.ddframe.rdb.sharding.router.AllRouterTest; + +@RunWith(Suite.class) +@SuiteClasses({ + AllApiTest.class, + AllParserTest.class, + AllRouterTest.class, + AllMergerTest.class, + AllJDBCTest.class, + AllMetricsTest.class, + AllIntegrateTests.class + }) +public class AllTests { +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/AbstractDBUnitTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/AbstractDBUnitTest.java new file mode 100644 index 0000000000000..b074863233671 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/AbstractDBUnitTest.java @@ -0,0 +1,146 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate; + +import static org.dbunit.Assertion.assertEquals; + +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp.BasicDataSource; +import org.dbunit.DatabaseUnitException; +import org.dbunit.IDatabaseTester; +import org.dbunit.database.IDatabaseConnection; +import org.dbunit.dataset.IDataSet; +import org.dbunit.dataset.ITable; +import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; +import org.dbunit.ext.h2.H2Connection; +import org.dbunit.ext.mysql.MySqlConnection; +import org.dbunit.operation.DatabaseOperation; +import org.h2.tools.RunScript; +import org.junit.Before; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; + +public abstract class AbstractDBUnitTest { + + public static final DatabaseType CURRENT_DB_TYPE = DatabaseType.H2; + + private static final Map DATA_SOURCES = new HashMap<>(); + + private final DataBaseEnvironment dbEnv = new DataBaseEnvironment(CURRENT_DB_TYPE); + + @Before + public void createSchema() throws SQLException { + for (String each : getSchemaFiles()) { + Connection conn = createDataSource(each).getConnection(); + RunScript.execute(conn, new InputStreamReader(AbstractDBUnitTest.class.getClassLoader().getResourceAsStream(each))); + conn.close(); + } + } + + @Before + public final void importDataSet() throws Exception { + for (String each : getDataSetFiles()) { + InputStream is = AbstractDBUnitTest.class.getClassLoader().getResourceAsStream(each); + IDataSet dataSet = new FlatXmlDataSetBuilder().build(new InputStreamReader(is)); + IDatabaseTester databaseTester = new ShardingJdbcDatabaseTester(dbEnv.getDriverClassName(), dbEnv.getURL(getFileName(each)), dbEnv.getUsername(), dbEnv.getPassword()); + databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT); + databaseTester.setDataSet(dataSet); + databaseTester.onSetup(); + } + } + + protected abstract List getSchemaFiles(); + + protected abstract List getDataSetFiles(); + + protected final Map createDataSourceMap(final String dataSourceName) { + Map result = new HashMap<>(getDataSetFiles().size()); + for (String each : getDataSetFiles()) { + result.put(String.format(dataSourceName, getFileName(each)), createDataSource(each)); + } + return result; + } + + private DataSource createDataSource(final String dataSetFile) { + if (DATA_SOURCES.containsKey(dataSetFile)) { + return DATA_SOURCES.get(dataSetFile); + } + BasicDataSource result = new BasicDataSource(); + result.setDriverClassName(dbEnv.getDriverClassName()); + result.setUrl(dbEnv.getURL(getFileName(dataSetFile))); + result.setUsername(dbEnv.getUsername()); + result.setPassword(dbEnv.getPassword()); + result.setMaxActive(1000); + DATA_SOURCES.put(dataSetFile, result); + return result; + } + + private String getFileName(final String dataSetFile) { + String fileName = new File(dataSetFile).getName(); + if (-1 == fileName.lastIndexOf(".")) { + return fileName; + } + return fileName.substring(0, fileName.lastIndexOf(".")); + } + + protected void assertDataset(final String expectedDataSetFile, final Connection connection, final String actualTableName, final String sql, final Object... params) + throws SQLException, DatabaseUnitException { + try ( + Connection conn = connection; + PreparedStatement ps = connection.prepareStatement(sql)) { + int i = 1; + for (Object each : params) { + ps.setObject(i++, each); + } + ITable actualTable = getConnection(connection).createTable(actualTableName, ps); + IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(new InputStreamReader(AbstractDBUnitTest.class.getClassLoader().getResourceAsStream(expectedDataSetFile))); + assertEquals(expectedDataSet.getTable(actualTableName), actualTable); + } + } + + protected void assertDataset(final String expectedDataSetFile, final Connection connection, final String actualTableName, final String sql) + throws SQLException, DatabaseUnitException { + try (Connection conn = connection) { + ITable actualTable = getConnection(connection).createQueryTable(actualTableName, sql); + IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(new InputStreamReader(AbstractDBUnitTest.class.getClassLoader().getResourceAsStream(expectedDataSetFile))); + assertEquals(expectedDataSet.getTable(actualTableName), actualTable); + } + } + + private IDatabaseConnection getConnection(final Connection connection) throws SQLException, DatabaseUnitException { + switch (dbEnv.getDatabaseType()) { + case H2: + return new H2Connection(connection, "PUBLIC"); + case MySQL: + return new MySqlConnection(connection, "PUBLIC"); + default: + throw new UnsupportedOperationException(dbEnv.getDatabaseType().name()); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/AllIntegrateTests.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/AllIntegrateTests.java new file mode 100644 index 0000000000000..3302bf0bc2eff --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/AllIntegrateTests.java @@ -0,0 +1,70 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate; + +import com.dangdang.ddframe.rdb.integrate.db.DMLShardingDataBasesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.db.SelectAggregateShardingDataBasesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.db.SelectGroupByShardingDataBasesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.db.SelectShardingDataBasesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.db.StatementDMLShardingDataBasesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.db.StatementSelectAggregateShardingDataBasesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.db.StatementSelectShardingDataBasesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.dbtbl.DMLShardingBothDataBasesAndTablesTest; +import com.dangdang.ddframe.rdb.integrate.dbtbl.SelectAggregateShardingBothDataBasesAndTablesTest; +import com.dangdang.ddframe.rdb.integrate.dbtbl.SelectGroupByShardingBothDataBasesAndTablesTest; +import com.dangdang.ddframe.rdb.integrate.dbtbl.SelectShardingBothDataBasesAndTablesTest; +import com.dangdang.ddframe.rdb.integrate.dbtbl.StatementDMLShardingBothDataBasesAndTablesTest; +import com.dangdang.ddframe.rdb.integrate.dbtbl.StatementSelectAggregateShardingBothDataBasesAndTablesTest; +import com.dangdang.ddframe.rdb.integrate.dbtbl.StatementSelectShardingBothDataBasesAndTablesTest; +import com.dangdang.ddframe.rdb.integrate.tbl.DMLShardingTablesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.tbl.SelectAggregateShardingTablesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.tbl.SelectGroupByShardingTablesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.tbl.SelectShardingTablesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.tbl.StatementDMLShardingTablesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.tbl.StatementSelectAggregateShardingTablesOnlyTest; +import com.dangdang.ddframe.rdb.integrate.tbl.StatementSelectShardingTablesOnlyTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ + SelectShardingBothDataBasesAndTablesTest.class, + SelectAggregateShardingBothDataBasesAndTablesTest.class, + SelectGroupByShardingDataBasesOnlyTest.class, + DMLShardingBothDataBasesAndTablesTest.class, + StatementSelectShardingBothDataBasesAndTablesTest.class, + StatementSelectAggregateShardingBothDataBasesAndTablesTest.class, + StatementDMLShardingBothDataBasesAndTablesTest.class, + SelectShardingDataBasesOnlyTest.class, + SelectAggregateShardingDataBasesOnlyTest.class, + SelectGroupByShardingBothDataBasesAndTablesTest.class, + DMLShardingDataBasesOnlyTest.class, + StatementSelectShardingDataBasesOnlyTest.class, + StatementSelectAggregateShardingDataBasesOnlyTest.class, + StatementDMLShardingDataBasesOnlyTest.class, + SelectShardingTablesOnlyTest.class, + SelectAggregateShardingTablesOnlyTest.class, + SelectGroupByShardingTablesOnlyTest.class, + DMLShardingTablesOnlyTest.class, + StatementSelectShardingTablesOnlyTest.class, + StatementSelectAggregateShardingTablesOnlyTest.class, + StatementDMLShardingTablesOnlyTest.class + }) +public class AllIntegrateTests { +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/DataBaseEnvironment.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/DataBaseEnvironment.java new file mode 100644 index 0000000000000..9a9b0a7d75a55 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/DataBaseEnvironment.java @@ -0,0 +1,71 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate; + +import java.util.HashMap; +import java.util.Map; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; + +import lombok.Getter; + +public final class DataBaseEnvironment { + + private static final Map> DRIVER_CLASS_NAME = new HashMap<>(2); + + private static final Map URL = new HashMap<>(2); + + private static final Map USERNAME = new HashMap<>(2); + + private static final Map PASSWORD = new HashMap<>(2); + + @Getter + private final DatabaseType databaseType; + + public DataBaseEnvironment(final DatabaseType databaseType) { + this.databaseType = databaseType; + fillData(); + } + + private void fillData() { + DRIVER_CLASS_NAME.put(DatabaseType.H2, org.h2.Driver.class); + DRIVER_CLASS_NAME.put(DatabaseType.MySQL, com.mysql.jdbc.Driver.class); + URL.put(DatabaseType.H2, "jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MYSQL"); + URL.put(DatabaseType.MySQL, "jdbc:mysql://localhost:3306/%s"); + USERNAME.put(DatabaseType.H2, "sa"); + USERNAME.put(DatabaseType.MySQL, "root"); + PASSWORD.put(DatabaseType.H2, ""); + PASSWORD.put(DatabaseType.MySQL, ""); + } + + public String getDriverClassName() { + return DRIVER_CLASS_NAME.get(databaseType).getName(); + } + + public String getURL(final String dbName) { + return String.format(URL.get(databaseType), dbName); + } + + public String getUsername() { + return USERNAME.get(databaseType); + } + + public String getPassword() { + return PASSWORD.get(databaseType); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/ShardingJdbcDatabaseTester.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/ShardingJdbcDatabaseTester.java new file mode 100644 index 0000000000000..76569cb519ab9 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/ShardingJdbcDatabaseTester.java @@ -0,0 +1,46 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate; + +import org.dbunit.JdbcDatabaseTester; +import org.dbunit.database.DatabaseConfig; +import org.dbunit.database.IDatabaseConnection; +import org.dbunit.ext.h2.H2DataTypeFactory; +import org.dbunit.ext.mysql.MySqlDataTypeFactory; + +public final class ShardingJdbcDatabaseTester extends JdbcDatabaseTester { + + private String driverClass; + + public ShardingJdbcDatabaseTester(final String driverClass, final String connectionUrl, final String username, + final String password) throws ClassNotFoundException { + super(driverClass, connectionUrl, username, password, null); + this.driverClass = driverClass; + } + + @Override + public IDatabaseConnection getConnection() throws Exception { + IDatabaseConnection result = super.getConnection(); + if (org.h2.Driver.class.getName().equals(driverClass)) { + result.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new H2DataTypeFactory()); + } else if (com.mysql.jdbc.Driver.class.getName().equals(driverClass)) { + result.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory()); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/AbstractShardingDataBasesOnlyDBUnitTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/AbstractShardingDataBasesOnlyDBUnitTest.java new file mode 100644 index 0000000000000..3f8e27cb3314e --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/AbstractShardingDataBasesOnlyDBUnitTest.java @@ -0,0 +1,78 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.db; + +import com.dangdang.ddframe.rdb.integrate.AbstractDBUnitTest; +import com.dangdang.ddframe.rdb.integrate.fixture.MultipleKeysModuloDatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.NoneTableShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +public abstract class AbstractShardingDataBasesOnlyDBUnitTest extends AbstractDBUnitTest { + + private String dataSourceName = "dataSource_%s"; + + @Override + protected List getSchemaFiles() { + return Arrays.asList( + "integrate/schema/db/db_0.sql", + "integrate/schema/db/db_1.sql", + "integrate/schema/db/db_2.sql", + "integrate/schema/db/db_3.sql", + "integrate/schema/db/db_4.sql", + "integrate/schema/db/db_5.sql", + "integrate/schema/db/db_6.sql", + "integrate/schema/db/db_7.sql", + "integrate/schema/db/db_8.sql", + "integrate/schema/db/db_9.sql"); + } + + @Override + protected List getDataSetFiles() { + return Arrays.asList( + "integrate/dataset/db/init/db_0.xml", + "integrate/dataset/db/init/db_1.xml", + "integrate/dataset/db/init/db_2.xml", + "integrate/dataset/db/init/db_3.xml", + "integrate/dataset/db/init/db_4.xml", + "integrate/dataset/db/init/db_5.xml", + "integrate/dataset/db/init/db_6.xml", + "integrate/dataset/db/init/db_7.xml", + "integrate/dataset/db/init/db_8.xml", + "integrate/dataset/db/init/db_9.xml"); + } + + protected final ShardingDataSource getShardingDataSource() throws SQLException { + DataSourceRule dataSourceRule = new DataSourceRule(createDataSourceMap(dataSourceName)); + TableRule orderTableRule = new TableRule("t_order", Arrays.asList("t_order"), dataSourceRule); + TableRule orderItemTableRule = new TableRule("t_order_item", Arrays.asList("t_order_item"), dataSourceRule); + ShardingRule shardingRule = new ShardingRule(dataSourceRule, Arrays.asList(orderTableRule, orderItemTableRule), + Arrays.asList(new BindingTableRule(Arrays.asList(orderTableRule, orderItemTableRule))), + new DatabaseShardingStrategy(Arrays.asList("user_id"), new MultipleKeysModuloDatabaseShardingAlgorithm()), + new TableShardingStrategy(Arrays.asList("order_id"), new NoneTableShardingAlgorithm())); + return new ShardingDataSource(shardingRule); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/DMLShardingDataBasesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/DMLShardingDataBasesOnlyTest.java new file mode 100644 index 0000000000000..1f566cb5a3511 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/DMLShardingDataBasesOnlyTest.java @@ -0,0 +1,174 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public final class DMLShardingDataBasesOnlyTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertInsertWithAllPlacehloders() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (?, ?, ?)"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setInt(1, i); + pstmt.setInt(2, i); + pstmt.setString(3, "insert"); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithoutPlacehloder() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, 'insert')"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, i)); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithPlacehlodersForShardingKeys() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, ?)"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, i)); + pstmt.setString(1, "insert"); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithPlacehlodersForNotShardingKeys() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, ?)"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, i)); + pstmt.setString(1, "insert"); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertUpdateWithoutAlias() throws SQLException, DatabaseUnitException { + String sql = "UPDATE `t_order` SET `status` = ? WHERE `order_id` = ? AND `user_id` = ?"; + for (int i = 10; i < 30; i++) { + for (int j = 0; j < 2; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setInt(2, i * 100 + j); + pstmt.setInt(3, i); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithAlias() throws SQLException, DatabaseUnitException { + String sql = "UPDATE `t_order` AS o SET o.`status` = ? WHERE o.`order_id` = ? AND o.`user_id` = ?"; + for (int i = 10; i < 30; i++) { + for (int j = 0; j < 2; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setInt(2, i * 100 + j); + pstmt.setInt(3, i); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithoutShardingValue() throws SQLException, DatabaseUnitException { + String sql = "UPDATE `t_order` SET `status` = ? WHERE `status` = ?"; + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setString(2, "init"); + assertThat(pstmt.executeUpdate(), is(40)); + } + assertDataset("update", "updated"); + } + + @Test + public void assertDeleteWithoutAlias() throws SQLException, DatabaseUnitException { + String sql = "DELETE `t_order` WHERE `order_id` = ? AND `user_id` = ? AND `status` = ?"; + for (int i = 10; i < 30; i++) { + for (int j = 0; j < 2; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setInt(1, i * 100 + j); + pstmt.setInt(2, i); + pstmt.setString(3, "init"); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("delete", "init"); + } + + @Test + public void assertDeleteWithoutShardingValue() throws SQLException, DatabaseUnitException { + String sql = "DELETE `t_order` WHERE `status` = ?"; + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "init"); + assertThat(pstmt.executeUpdate(), is(40)); + } + assertDataset("delete", "init"); + } + + private void assertDataset(final String expectedDataSetPattern, final String status) throws SQLException, DatabaseUnitException { + for (int i = 0; i < 10; i++) { + assertDataset(String.format("integrate/dataset/db/expect/%s/db_%s.xml", expectedDataSetPattern, i), + shardingDataSource.getConnection().getConnection(String.format("dataSource_db_%s", i)), "t_order", "SELECT * FROM `t_order` WHERE `status`=?", status); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectAggregateShardingDataBasesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectAggregateShardingDataBasesOnlyTest.java new file mode 100644 index 0000000000000..80285fffe27a9 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectAggregateShardingDataBasesOnlyTest.java @@ -0,0 +1,75 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.db; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class SelectAggregateShardingDataBasesOnlyTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectCount() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order`"; + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectCount.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectSum() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(`user_id`) AS `user_id_sum` FROM `t_order`"; + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectSum.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMax() throws SQLException, DatabaseUnitException { + String sql = "SELECT MAX(`user_id`) AS `max_user_id` FROM `t_order`"; + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectMax.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMin() throws SQLException, DatabaseUnitException { + String sql = "SELECT MIN(`user_id`) AS `min_user_id` FROM `t_order`"; + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectMin.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + // TODO 改名 avg SHARDING_GEN_2 SHARDING_GEN_3 + public void assertSelectAvg() throws SQLException, DatabaseUnitException { + String sql = "SELECT AVG(`user_id`) AS `user_id_avg` FROM `t_order`"; + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectAvg.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectCountWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `items_count` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ?"; + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_0.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909); + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_1.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectGroupByShardingDataBasesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectGroupByShardingDataBasesOnlyTest.java new file mode 100644 index 0000000000000..dedd98c33adcc --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectGroupByShardingDataBasesOnlyTest.java @@ -0,0 +1,72 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.db; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class SelectGroupByShardingDataBasesOnlyTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectSum() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(order_id) AS `orders_sum`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/db/expect/select_groupby/SelectSum.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectCount() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(order_id) AS `orders_count`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/db/expect/select_groupby/SelectCount.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMax() throws SQLException, DatabaseUnitException { + String sql = "SELECT MAX(order_id) AS `max_order_id`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/db/expect/select_groupby/SelectMax.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMin() throws SQLException, DatabaseUnitException { + String sql = "SELECT MIN(order_id) AS `min_order_id`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/db/expect/select_groupby/SelectMin.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectAvg() throws SQLException, DatabaseUnitException { + String sql = "SELECT AVG(order_id) AS `orders_avg`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/db/expect/select_groupby/SelectAvg.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectOrderByDesc() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(order_id) AS `orders_sum`, `user_id` FROM `t_order` GROUP BY `user_id` ORDER BY orders_sum DESC"; + assertDataset("integrate/dataset/db/expect/select_groupby/SelectOrderByDesc.xml", shardingDataSource.getConnection(), "t_order", sql); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectShardingDataBasesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectShardingDataBasesOnlyTest.java new file mode 100644 index 0000000000000..40b61ff6ffbfb --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/SelectShardingDataBasesOnlyTest.java @@ -0,0 +1,97 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.db; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class SelectShardingDataBasesOnlyTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectEqualsWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` = ? AND `order_id` = ?"; + assertDataset("integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 1000); + assertDataset("integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", sql, 12, 1201); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 12, 1000); + } + + @Test + public void assertSelectBetweenWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` BETWEEN ? AND ? AND `order_id` BETWEEN ? AND ? ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/db/expect/select/SelectBetweenWithSingleTable.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 1001, 1200); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 1309, 1408); + } + + @Test + public void assertSelectInWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` IN (?, ?, ?) AND `order_id` IN (?, ?) ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/db/expect/select/SelectInWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 15, 1000, 1201); + assertDataset("integrate/dataset/db/expect/select/SelectInWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 15, 1000, 1101); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 15, 1309, 1408); + } + + @Test + public void assertSelectLimitWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? ORDER BY i.item_id DESC LIMIT ?, ?"; + assertDataset("integrate/dataset/db/expect/select/SelectLimitWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 2, 2); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 10000, 2); + } + + @Test + public void assertSelectLimitWithBindingTableWithoutOffset() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? ORDER BY i.item_id DESC LIMIT ?"; + assertDataset("integrate/dataset/db/expect/select/SelectLimitWithBindingTableWithoutOffset.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 2); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 0); + } + + @Test + public void assertSelectGroupByWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count, o.`user_id` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/db/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } + + @Test + public void assertSelectGroupByWithoutGroupedColumn() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/db/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } + + @Test + public void assertSelectNoShardingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id ORDER BY i.item_id"; + assertDataset("integrate/dataset/db/expect/select/SelectNoShardingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementDMLShardingDataBasesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementDMLShardingDataBasesOnlyTest.java new file mode 100644 index 0000000000000..5cb871314dea3 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementDMLShardingDataBasesOnlyTest.java @@ -0,0 +1,105 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.db; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public class StatementDMLShardingDataBasesOnlyTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertInsert() throws SQLException, DatabaseUnitException { + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + connection.setAutoCommit(false); + connection.createStatement().executeUpdate(String.format("INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, '%s')", i, i, "insert")); + connection.commit(); + connection.close(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertUpdate() throws SQLException, DatabaseUnitException { + for (int i = 10; i < 30; i++) { + for (int j = 0; j < 2; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format("UPDATE `t_order` SET `status` = '%s' WHERE `order_id` = %s AND `user_id` = %s", "updated", i * 100 + j, i)), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithoutShardingValue() throws SQLException, DatabaseUnitException { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format("UPDATE `t_order` SET `status` = '%s' WHERE `status` = '%s'", "updated", "init")), is(40)); + } + assertDataset("update", "updated"); + } + + @Test + public void assertDelete() throws SQLException, DatabaseUnitException { + for (int i = 10; i < 30; i++) { + for (int j = 0; j < 2; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format("DELETE `t_order` WHERE `order_id` = %s AND `user_id` = %s AND `status` = '%s'", i * 100 + j, i, "init")), is(1)); + } + } + } + assertDataset("delete", "init"); + } + + @Test + public void assertDeleteWithoutShardingValue() throws SQLException, DatabaseUnitException { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format("DELETE `t_order` WHERE `status` = '%s'", "init")), is(40)); + } + assertDataset("delete", "init"); + } + + private void assertDataset(final String expectedDataSetPattern, final String status) throws SQLException, DatabaseUnitException { + for (int i = 0; i < 10; i++) { + assertDataset(String.format("integrate/dataset/db/expect/%s/db_%s.xml", expectedDataSetPattern, i), + shardingDataSource.getConnection().getConnection(String.format("dataSource_db_%s", i)), "t_order", "SELECT * FROM `t_order` WHERE `status`=?", status); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementSelectAggregateShardingDataBasesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementSelectAggregateShardingDataBasesOnlyTest.java new file mode 100644 index 0000000000000..eefc097920340 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementSelectAggregateShardingDataBasesOnlyTest.java @@ -0,0 +1,43 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.db; + +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +public final class StatementSelectAggregateShardingDataBasesOnlyTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectCountWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `items_count` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s"; + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_0.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909)); + assertDataset("integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_1.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementSelectShardingDataBasesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementSelectShardingDataBasesOnlyTest.java new file mode 100644 index 0000000000000..fb79a1c0646c8 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/db/StatementSelectShardingDataBasesOnlyTest.java @@ -0,0 +1,82 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.db; + +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +public final class StatementSelectShardingDataBasesOnlyTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectEqualsWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` = %s AND `order_id` = %s"; + assertDataset("integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 1000)); + assertDataset("integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 12, 1201)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 12, 1000)); + } + + @Test + public void assertSelectBetweenWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` BETWEEN %s AND %s AND `order_id` BETWEEN %s AND %s ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/db/expect/select/SelectBetweenWithSingleTable.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 1001, 1200)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 1309, 1408)); + } + + @Test + public void assertSelectInWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` IN (%s, %s, %s) AND `order_id` IN (%s, %s) ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/db/expect/select/SelectInWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 15, 1000, 1201)); + assertDataset("integrate/dataset/db/expect/select/SelectInWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 15, 1000, 1101)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 15, 1309, 1408)); + } + + @Test + public void assertSelectLimitWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s ORDER BY i.item_id DESC LIMIT %s, %s"; + assertDataset("integrate/dataset/db/expect/select/SelectLimitWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909, 2, 2)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909, 10000, 2)); + } + + @Test + public void assertSelectGroupByWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count, o.`user_id` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/db/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } + + @Test + public void assertSelectGroupByWithoutGroupedColumn() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/db/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/AbstractShardingBothDataBasesAndTablesDBUnitTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/AbstractShardingBothDataBasesAndTablesDBUnitTest.java new file mode 100644 index 0000000000000..b64af8d17d455 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/AbstractShardingBothDataBasesAndTablesDBUnitTest.java @@ -0,0 +1,98 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.dbtbl; + +import com.dangdang.ddframe.rdb.integrate.AbstractDBUnitTest; +import com.dangdang.ddframe.rdb.integrate.fixture.SingleKeyModuloDatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.integrate.fixture.SingleKeyModuloTableShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +public abstract class AbstractShardingBothDataBasesAndTablesDBUnitTest extends AbstractDBUnitTest { + + private String dataSourceName = "dataSource_%s"; + + @Override + protected List getSchemaFiles() { + return Arrays.asList( + "integrate/schema/dbtbl/dbtbl_0.sql", + "integrate/schema/dbtbl/dbtbl_1.sql", + "integrate/schema/dbtbl/dbtbl_2.sql", + "integrate/schema/dbtbl/dbtbl_3.sql", + "integrate/schema/dbtbl/dbtbl_4.sql", + "integrate/schema/dbtbl/dbtbl_5.sql", + "integrate/schema/dbtbl/dbtbl_6.sql", + "integrate/schema/dbtbl/dbtbl_7.sql", + "integrate/schema/dbtbl/dbtbl_8.sql", + "integrate/schema/dbtbl/dbtbl_9.sql"); + } + + @Override + protected List getDataSetFiles() { + return Arrays.asList( + "integrate/dataset/dbtbl/init/dbtbl_0.xml", + "integrate/dataset/dbtbl/init/dbtbl_1.xml", + "integrate/dataset/dbtbl/init/dbtbl_2.xml", + "integrate/dataset/dbtbl/init/dbtbl_3.xml", + "integrate/dataset/dbtbl/init/dbtbl_4.xml", + "integrate/dataset/dbtbl/init/dbtbl_5.xml", + "integrate/dataset/dbtbl/init/dbtbl_6.xml", + "integrate/dataset/dbtbl/init/dbtbl_7.xml", + "integrate/dataset/dbtbl/init/dbtbl_8.xml", + "integrate/dataset/dbtbl/init/dbtbl_9.xml"); + } + + protected final ShardingDataSource getShardingDataSource() throws SQLException { + DataSourceRule dataSourceRule = new DataSourceRule(createDataSourceMap(dataSourceName)); + TableRule orderTableRule = new TableRule("t_order", Arrays.asList( + "t_order_0", + "t_order_1", + "t_order_2", + "t_order_3", + "t_order_4", + "t_order_5", + "t_order_6", + "t_order_7", + "t_order_8", + "t_order_9"), dataSourceRule); + TableRule orderItemTableRule = new TableRule("t_order_item", Arrays.asList( + "t_order_item_0", + "t_order_item_1", + "t_order_item_2", + "t_order_item_3", + "t_order_item_4", + "t_order_item_5", + "t_order_item_6", + "t_order_item_7", + "t_order_item_8", + "t_order_item_9"), dataSourceRule); + ShardingRule shardingRule = new ShardingRule(dataSourceRule, Arrays.asList(orderTableRule, orderItemTableRule), + Arrays.asList(new BindingTableRule(Arrays.asList(orderTableRule, orderItemTableRule))), + new DatabaseShardingStrategy("user_id", new SingleKeyModuloDatabaseShardingAlgorithm()), + new TableShardingStrategy("order_id", new SingleKeyModuloTableShardingAlgorithm())); + return new ShardingDataSource(shardingRule); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/DMLShardingBothDataBasesAndTablesTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/DMLShardingBothDataBasesAndTablesTest.java new file mode 100644 index 0000000000000..f350930f8d310 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/DMLShardingBothDataBasesAndTablesTest.java @@ -0,0 +1,185 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.dbtbl; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class DMLShardingBothDataBasesAndTablesTest extends AbstractShardingBothDataBasesAndTablesDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertInsertWithAllPlacehloders() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (?, ?, ?)"; + for (int i = 1; i <= 10; i++) { + for (int j = 1; j <= 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setInt(1, i); + pstmt.setInt(2, j); + pstmt.setString(3, "insert"); + pstmt.executeUpdate(); + } + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithoutPlacehloder() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, 'insert')"; + for (int i = 1; i <= 10; i++) { + for (int j = 1; j <= 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, j)); + pstmt.executeUpdate(); + } + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithPlacehlodersForShardingKeys() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, ?)"; + for (int i = 1; i <= 10; i++) { + for (int j = 1; j <= 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, j)); + pstmt.setString(1, "insert"); + pstmt.executeUpdate(); + } + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithPlacehlodersForNotShardingKeys() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, ?)"; + for (int i = 1; i <= 10; i++) { + for (int j = 1; j <= 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, j)); + pstmt.setString(1, "insert"); + pstmt.executeUpdate(); + } + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertUpdateWithoutAlias() throws SQLException, DatabaseUnitException { + String sql = "UPDATE `t_order` SET `status` = ? WHERE `order_id` = ? AND `user_id` = ?"; + for (int i = 10; i < 20; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setInt(2, i * 100 + j); + pstmt.setInt(3, i); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithAlias() throws SQLException, DatabaseUnitException { + String sql = "UPDATE `t_order` AS o SET o.`status` = ? WHERE o.`order_id` = ? AND o.`user_id` = ?"; + for (int i = 10; i < 20; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setInt(2, i * 100 + j); + pstmt.setInt(3, i); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithoutShardingValue() throws SQLException, DatabaseUnitException { + String sql = "UPDATE `t_order` SET `status` = ? WHERE `status` = ?"; + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setString(2, "init"); + assertThat(pstmt.executeUpdate(), is(100)); + } + assertDataset("update", "updated"); + } + + @Test + public void assertDeleteWithoutAlias() throws SQLException, DatabaseUnitException { + String sql = "DELETE `t_order` WHERE `order_id` = ? AND `user_id` = ?"; + for (int i = 10; i < 20; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setInt(1, i * 100 + j); + pstmt.setInt(2, i); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("delete", "init"); + } + + @Test + public void assertDeleteWithoutShardingValue() throws SQLException, DatabaseUnitException { + String sql = "DELETE `t_order` WHERE `status` = ?"; + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "init"); + assertThat(pstmt.executeUpdate(), is(100)); + } + assertDataset("delete", "init"); + } + + private void assertDataset(final String expectedDataSetPattern, final String status) throws SQLException, DatabaseUnitException { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + assertDataset(String.format("integrate/dataset/dbtbl/expect/%s/dbtbl_%s.xml", expectedDataSetPattern, i), + shardingDataSource.getConnection().getConnection(String.format("dataSource_dbtbl_%s", i)), + String.format("t_order_%s", j), String.format("SELECT * FROM `t_order_%s` WHERE `status`=?", j), status); + } + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectAggregateShardingBothDataBasesAndTablesTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectAggregateShardingBothDataBasesAndTablesTest.java new file mode 100644 index 0000000000000..03a69123337fe --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectAggregateShardingBothDataBasesAndTablesTest.java @@ -0,0 +1,75 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.dbtbl; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class SelectAggregateShardingBothDataBasesAndTablesTest extends AbstractShardingBothDataBasesAndTablesDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectCount() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order`"; + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectCount.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectSum() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(`user_id`) AS `user_id_sum` FROM `t_order`"; + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectSum.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMax() throws SQLException, DatabaseUnitException { + String sql = "SELECT MAX(`user_id`) AS `max_user_id` FROM `t_order`"; + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectMax.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMin() throws SQLException, DatabaseUnitException { + String sql = "SELECT MIN(`user_id`) AS `min_user_id` FROM `t_order`"; + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectMin.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + // TODO 改名 avg SHARDING_GEN_2 SHARDING_GEN_3 + public void assertSelectAvg() throws SQLException, DatabaseUnitException { + String sql = "SELECT AVG(`user_id`) AS `user_id_avg` FROM `t_order`"; + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectAvg.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectCountWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `items_count` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ?"; + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909); + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectGroupByShardingBothDataBasesAndTablesTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectGroupByShardingBothDataBasesAndTablesTest.java new file mode 100644 index 0000000000000..9d81e93e4542c --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectGroupByShardingBothDataBasesAndTablesTest.java @@ -0,0 +1,72 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.dbtbl; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class SelectGroupByShardingBothDataBasesAndTablesTest extends AbstractShardingBothDataBasesAndTablesDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectSum() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(order_id) AS `orders_sum`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select_groupby/SelectSum.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectCount() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(order_id) AS `orders_count`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select_groupby/SelectCount.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMax() throws SQLException, DatabaseUnitException { + String sql = "SELECT MAX(order_id) AS `max_order_id`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select_groupby/SelectMax.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMin() throws SQLException, DatabaseUnitException { + String sql = "SELECT MIN(order_id) AS `min_order_id`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select_groupby/SelectMin.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectAvg() throws SQLException, DatabaseUnitException { + String sql = "SELECT AVG(order_id) AS `orders_avg`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select_groupby/SelectAvg.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectOrderByDesc() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(order_id) AS `orders_sum`, `user_id` FROM `t_order` GROUP BY `user_id` ORDER BY orders_sum DESC"; + assertDataset("integrate/dataset/dbtbl/expect/select_groupby/SelectOrderByDesc.xml", shardingDataSource.getConnection(), "t_order", sql); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectShardingBothDataBasesAndTablesTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectShardingBothDataBasesAndTablesTest.java new file mode 100644 index 0000000000000..6aa5d699279a8 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/SelectShardingBothDataBasesAndTablesTest.java @@ -0,0 +1,111 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.dbtbl; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class SelectShardingBothDataBasesAndTablesTest extends AbstractShardingBothDataBasesAndTablesDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectForFullTableNameWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT `t_order`.order_id, `t_order`.user_id, `t_order`.status FROM `t_order` WHERE `t_order`.`user_id` = ? AND `t_order`.`order_id` = ?"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 1000); + } + + @Test + public void assertSelectEqualsWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE 1 = 1 AND `user_id` = ? AND `order_id` = ?"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 1000); + assertDataset("integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", sql, 12, 1201); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 12, 1000); + } + + @Test + public void assertSelectBetweenWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` BETWEEN ? AND ? AND `order_id` BETWEEN ? AND ? ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectBetweenWithSingleTable.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 1009, 1108); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 1309, 1408); + } + + @Test + public void assertSelectInWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` IN (?, ?, ?) AND `order_id` IN (?, ?) ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 15, 1009, 1208); + assertDataset("integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 15, 1009, 1108); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 15, 1309, 1408); + } + + @Test + public void assertSelectLimitWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? ORDER BY i.item_id DESC LIMIT ?, ?"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 2, 2); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 10000, 2); + } + + @Test + public void assertSelectLimitWithBindingTableWithoutOffset() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? ORDER BY i.item_id DESC LIMIT ?"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 2); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 0); + } + + @Test + public void assertSelectGroupByWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count, o.`user_id` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } + + @Test + public void assertSelectGroupByWithoutGroupedColumn() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } + + @Test + public void assertSelectWithBingdingTableAndConfigTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id JOIN t_config c ON o.status = c.status" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? AND c.status = ? ORDER BY i.item_id"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectWithBingdingTableAndConfigTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 11, 1009, 1108, "init"); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 11, 1009, 1108, "none"); + } + + @Test + public void assertSelectNoShardingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id JOIN t_config c ON o.status = c.status ORDER BY i.item_id"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectNoShardingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementDMLShardingBothDataBasesAndTablesTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementDMLShardingBothDataBasesAndTablesTest.java new file mode 100644 index 0000000000000..0699e6f081b3e --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementDMLShardingBothDataBasesAndTablesTest.java @@ -0,0 +1,114 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.dbtbl; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class StatementDMLShardingBothDataBasesAndTablesTest extends AbstractShardingBothDataBasesAndTablesDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertInsert() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, '%s')"; + for (int i = 1; i <= 10; i++) { + for (int j = 1; j <= 10; j++) { + try (Connection connection = shardingDataSource.getConnection("", "")) { + Statement stmt = connection.createStatement(); + stmt.executeUpdate(String.format(sql, i, j, "insert")); + } + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertUpdate() throws SQLException, DatabaseUnitException { + String sql = "UPDATE `t_order` SET `status` = '%s' WHERE `order_id` = %s AND `user_id` = %s"; + for (int i = 10; i < 20; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format(sql, "updated", i * 100 + j, i)), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithoutShardingValue() throws SQLException, DatabaseUnitException { + String sql = "UPDATE `t_order` SET `status` = '%s' WHERE `status` = '%s'"; + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.prepareStatement(sql); + assertThat(stmt.executeUpdate(String.format(sql, "updated", "init")), is(100)); + } + assertDataset("update", "updated"); + } + + + @Test + public void assertDelete() throws SQLException, DatabaseUnitException { + String sql = "DELETE `t_order` WHERE `order_id` = %s AND `user_id` = %s"; + for (int i = 10; i < 20; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.prepareStatement(sql); + assertThat(stmt.executeUpdate(String.format(sql, i * 100 + j, i)), is(1)); + } + } + } + assertDataset("delete", "init"); + } + + @Test + public void assertDeleteWithoutShardingValue() throws SQLException, DatabaseUnitException { + String sql = "DELETE `t_order` WHERE `status` = '%s'"; + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.prepareStatement(sql); + assertThat(stmt.executeUpdate(String.format(sql, "init")), is(100)); + } + assertDataset("delete", "init"); + } + + private void assertDataset(final String expectedDataSetPattern, final String status) throws SQLException, DatabaseUnitException { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + assertDataset(String.format("integrate/dataset/dbtbl/expect/%s/dbtbl_%s.xml", expectedDataSetPattern, i), + shardingDataSource.getConnection().getConnection(String.format("dataSource_dbtbl_%s", i)), + String.format("t_order_%s", j), String.format("SELECT * FROM `t_order_%s` WHERE `status`=?", j), status); + } + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementSelectAggregateShardingBothDataBasesAndTablesTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementSelectAggregateShardingBothDataBasesAndTablesTest.java new file mode 100644 index 0000000000000..983b4354dcf3e --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementSelectAggregateShardingBothDataBasesAndTablesTest.java @@ -0,0 +1,43 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.dbtbl; + +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +public final class StatementSelectAggregateShardingBothDataBasesAndTablesTest extends AbstractShardingBothDataBasesAndTablesDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectCountWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `items_count` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s"; + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909)); + assertDataset("integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementSelectShardingBothDataBasesAndTablesTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementSelectShardingBothDataBasesAndTablesTest.java new file mode 100644 index 0000000000000..5e0af00adfc44 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/dbtbl/StatementSelectShardingBothDataBasesAndTablesTest.java @@ -0,0 +1,91 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.dbtbl; + +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +public final class StatementSelectShardingBothDataBasesAndTablesTest extends AbstractShardingBothDataBasesAndTablesDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectEqualsWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE 1 = 1 AND `user_id` = %s AND `order_id` = %s"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 1000)); + assertDataset("integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 12, 1201)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 12, 1000)); + } + + @Test + public void assertSelectBetweenWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` BETWEEN %s AND %s AND `order_id` BETWEEN %s AND %s ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectBetweenWithSingleTable.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 1009, 1108)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 1309, 1408)); + } + + @Test + public void assertSelectInWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` IN (%s, %s, %s) AND `order_id` IN (%s, %s) ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 15, 1009, 1208)); + assertDataset("integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 15, 1009, 1108)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 15, 1309, 1408)); + } + + @Test + public void assertSelectLimitWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s ORDER BY i.item_id DESC LIMIT %s, %s"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909, 2, 2)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909, 10000, 2)); + } + + @Test + public void assertSelectGroupByWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count, o.`user_id` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } + + @Test + public void assertSelectGroupByWithoutGroupedColumn() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } + + @Test + public void assertSelectWithBingdingTableAndConfigTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id JOIN t_config c ON o.status = c.status" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s AND c.status = '%s' ORDER BY i.item_id"; + assertDataset("integrate/dataset/dbtbl/expect/select/SelectWithBingdingTableAndConfigTable.xml", + shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 11, 1009, 1108, "init")); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 11, 1009, 1108, "none")); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/MultipleKeysModuloDatabaseShardingAlgorithm.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/MultipleKeysModuloDatabaseShardingAlgorithm.java new file mode 100644 index 0000000000000..0ad3878e25d1c --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/MultipleKeysModuloDatabaseShardingAlgorithm.java @@ -0,0 +1,81 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.fixture; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.MultipleKeysDatabaseShardingAlgorithm; +import com.google.common.collect.Range; + +public final class MultipleKeysModuloDatabaseShardingAlgorithm implements MultipleKeysDatabaseShardingAlgorithm { + + @SuppressWarnings("unchecked") + @Override + public Collection doSharding(final Collection availableTargetNames, final Collection> shardingValues) { + ShardingValue shardingValue = shardingValues.iterator().next(); + switch (shardingValue.getType()) { + case SINGLE: + return doEqualSharding(availableTargetNames, (ShardingValue) shardingValue); + case LIST: + return doInSharding(availableTargetNames, (ShardingValue) shardingValue); + case RANGE: + return doBetweenSharding(availableTargetNames, (ShardingValue) shardingValue); + default: + throw new UnsupportedOperationException(); + } + } + + private Collection doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Integer modulo = Integer.parseInt(shardingValue.getValue().toString()) % 10; + for (String each : availableTargetNames) { + if (each.endsWith(modulo.toString())) { + return Arrays.asList(each); + } + } + throw new UnsupportedOperationException(); + } + + private Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new LinkedHashSet<>(availableTargetNames.size()); + Collection values = (Collection) shardingValue.getValues(); + for (Integer value : values) { + for (String dataSourceName : availableTargetNames) { + if (dataSourceName.endsWith(value % 10 + "")) { + result.add(dataSourceName); + } + } + } + return result; + } + + private Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new LinkedHashSet<>(availableTargetNames.size()); + Range range = (Range) shardingValue.getValueRange(); + for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) { + for (String each : availableTargetNames) { + if (each.endsWith(i % 10 + "")) { + result.add(each); + } + } + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/MultipleKeysModuloTableShardingAlgorithm.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/MultipleKeysModuloTableShardingAlgorithm.java new file mode 100644 index 0000000000000..050da15c086af --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/MultipleKeysModuloTableShardingAlgorithm.java @@ -0,0 +1,80 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.fixture; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.MultipleKeysTableShardingAlgorithm; +import com.google.common.collect.Range; + +public final class MultipleKeysModuloTableShardingAlgorithm implements MultipleKeysTableShardingAlgorithm { + + @SuppressWarnings("unchecked") + @Override + public Collection doSharding(final Collection availableTargetNames, final Collection> shardingValues) { + ShardingValue shardingValue = shardingValues.iterator().next(); + switch (shardingValue.getType()) { + case SINGLE: + return doEqualSharding(availableTargetNames, (ShardingValue) shardingValue); + case LIST: + return doInSharding(availableTargetNames, (ShardingValue) shardingValue); + case RANGE: + return doBetweenSharding(availableTargetNames, (ShardingValue) shardingValue); + default: + throw new UnsupportedOperationException(); + } + } + + private Collection doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + for (String each : availableTargetNames) { + if (each.endsWith(shardingValue.getValue() % 10 + "")) { + return Arrays.asList(each); + } + } + throw new UnsupportedOperationException(); + } + + private Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new LinkedHashSet<>(availableTargetNames.size()); + Collection values = shardingValue.getValues(); + for (Integer value : values) { + for (String tableNames : availableTargetNames) { + if (tableNames.endsWith(value % 10 + "")) { + result.add(tableNames); + } + } + } + return result; + } + + private Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new LinkedHashSet<>(availableTargetNames.size()); + Range range = shardingValue.getValueRange(); + for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) { + for (String each : availableTargetNames) { + if (each.endsWith(i % 10 + "")) { + result.add(each); + } + } + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/SingleKeyModuloDatabaseShardingAlgorithm.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/SingleKeyModuloDatabaseShardingAlgorithm.java new file mode 100644 index 0000000000000..9ee6c1f8a3e16 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/SingleKeyModuloDatabaseShardingAlgorithm.java @@ -0,0 +1,65 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.fixture; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm; +import com.google.common.collect.Range; + +public final class SingleKeyModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm { + + @Override + public String doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + for (String each : availableTargetNames) { + if (each.endsWith(shardingValue.getValue() % 10 + "")) { + return each; + } + } + throw new UnsupportedOperationException(); + } + + @Override + public Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new LinkedHashSet<>(availableTargetNames.size()); + for (Integer value : shardingValue.getValues()) { + for (String dataSourceName : availableTargetNames) { + if (dataSourceName.endsWith(value % 10 + "")) { + result.add(dataSourceName); + } + } + } + return result; + } + + @Override + public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new LinkedHashSet<>(availableTargetNames.size()); + Range range = (Range) shardingValue.getValueRange(); + for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) { + for (String each : availableTargetNames) { + if (each.endsWith(i % 10 + "")) { + result.add(each); + } + } + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/SingleKeyModuloTableShardingAlgorithm.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/SingleKeyModuloTableShardingAlgorithm.java new file mode 100644 index 0000000000000..5487c362e66a0 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/fixture/SingleKeyModuloTableShardingAlgorithm.java @@ -0,0 +1,65 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.fixture; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm; +import com.google.common.collect.Range; + +public final class SingleKeyModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm { + + @Override + public String doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + for (String each : availableTargetNames) { + if (each.endsWith(shardingValue.getValue() % 10 + "")) { + return each; + } + } + throw new UnsupportedOperationException(); + } + + @Override + public Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new LinkedHashSet<>(availableTargetNames.size()); + for (Integer value : shardingValue.getValues()) { + for (String tableName : availableTargetNames) { + if (tableName.endsWith(value % 10 + "")) { + result.add(tableName); + } + } + } + return result; + } + + @Override + public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new LinkedHashSet<>(availableTargetNames.size()); + Range range = shardingValue.getValueRange(); + for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) { + for (String each : availableTargetNames) { + if (each.endsWith(i % 10 + "")) { + result.add(each); + } + } + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/AbstractShardingTablesOnlyDBUnitTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/AbstractShardingTablesOnlyDBUnitTest.java new file mode 100644 index 0000000000000..59042bc6bce87 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/AbstractShardingTablesOnlyDBUnitTest.java @@ -0,0 +1,79 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.tbl; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import com.dangdang.ddframe.rdb.integrate.AbstractDBUnitTest; +import com.dangdang.ddframe.rdb.integrate.fixture.SingleKeyModuloTableShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; + +public abstract class AbstractShardingTablesOnlyDBUnitTest extends AbstractDBUnitTest { + + private String dataSourceName = "dataSource_%s"; + + @Override + protected List getSchemaFiles() { + return Arrays.asList("integrate/schema/tbl/db_single.sql"); + } + + @Override + protected List getDataSetFiles() { + return Arrays.asList("integrate/dataset/tbl/init/db_single.xml"); + } + + protected final ShardingDataSource getShardingDataSource() throws SQLException { + DataSourceRule dataSourceRule = new DataSourceRule(createDataSourceMap(dataSourceName)); + TableRule orderTableRule = new TableRule("t_order", Arrays.asList( + "t_order_0", + "t_order_1", + "t_order_2", + "t_order_3", + "t_order_4", + "t_order_5", + "t_order_6", + "t_order_7", + "t_order_8", + "t_order_9"), dataSourceRule); + TableRule orderItemTableRule = new TableRule("t_order_item", Arrays.asList( + "t_order_item_0", + "t_order_item_1", + "t_order_item_2", + "t_order_item_3", + "t_order_item_4", + "t_order_item_5", + "t_order_item_6", + "t_order_item_7", + "t_order_item_8", + "t_order_item_9"), dataSourceRule); + ShardingRule shardingRule = new ShardingRule(dataSourceRule, Arrays.asList(orderTableRule, orderItemTableRule), + Arrays.asList(new BindingTableRule(Arrays.asList(orderTableRule, orderItemTableRule))), + new DatabaseShardingStrategy("user_id", new NoneDatabaseShardingAlgorithm()), + new TableShardingStrategy("order_id", new SingleKeyModuloTableShardingAlgorithm())); + return new ShardingDataSource(shardingRule); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/DMLShardingTablesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/DMLShardingTablesOnlyTest.java new file mode 100644 index 0000000000000..ac087ef6d8d7c --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/DMLShardingTablesOnlyTest.java @@ -0,0 +1,196 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.tbl; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class DMLShardingTablesOnlyTest extends AbstractShardingTablesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertInsert() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (?, ?, ?)"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setInt(1, i); + pstmt.setInt(2, i); + pstmt.setString(3, "insert"); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithAllPlacehloders() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (?, ?, ?)"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setInt(1, i); + pstmt.setInt(2, i); + pstmt.setString(3, "insert"); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithoutPlacehloder() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, 'insert')"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, i)); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithPlacehlodersForShardingKeys() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, ?)"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, i)); + pstmt.setString(1, "insert"); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertInsertWithPlacehlodersForNotShardingKeys() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, ?)"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(String.format(sql, i, i)); + pstmt.setString(1, "insert"); + pstmt.executeUpdate(); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertUpdateWithoutAlias() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "UPDATE `t_order` SET `status` = ? WHERE `order_id` = ? AND `user_id` = ?"; + for (int i = 10; i < 12; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setInt(2, i * 100 + j); + pstmt.setInt(3, i); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithAlias() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "UPDATE `t_order` as o SET o.`status` = ? WHERE o.`order_id` = ? AND o.`user_id` = ?"; + for (int i = 10; i < 12; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setInt(2, i * 100 + j); + pstmt.setInt(3, i); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithoutShardingValue() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "UPDATE `t_order` SET `status` = ? WHERE `status` = ?"; + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "updated"); + pstmt.setString(2, "init"); + assertThat(pstmt.executeUpdate(), is(20)); + } + assertDataset("update", "updated"); + } + + @Test + public void assertDeleteWithoutAlias() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "DELETE `t_order` WHERE `order_id` = ? AND `user_id` = ? AND `status` = ?"; + for (int i = 10; i < 12; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setInt(1, i * 100 + j); + pstmt.setInt(2, i); + pstmt.setString(3, "init"); + assertThat(pstmt.executeUpdate(), is(1)); + } + } + } + assertDataset("delete", "init"); + } + + @Test + public void assertDeleteWithoutShardingValue() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "DELETE `t_order` WHERE `status` = ?"; + try (Connection connection = shardingDataSource.getConnection()) { + PreparedStatement pstmt = connection.prepareStatement(sql); + pstmt.setString(1, "init"); + assertThat(pstmt.executeUpdate(), is(20)); + } + assertDataset("delete", "init"); + } + + private void assertDataset(final String expectedDataSetPattern, final String status) throws SQLException, DatabaseUnitException { + for (int i = 0; i < 10; i++) { + assertDataset(String.format("integrate/dataset/tbl/expect/%s/db_single.xml", expectedDataSetPattern), + shardingDataSource.getConnection().getConnection("dataSource_db_single"), + String.format("t_order_%s", i), String.format("SELECT * FROM `t_order_%s` WHERE `status`=?", i), status); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectAggregateShardingTablesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectAggregateShardingTablesOnlyTest.java new file mode 100644 index 0000000000000..c332057eec44e --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectAggregateShardingTablesOnlyTest.java @@ -0,0 +1,75 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.tbl; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class SelectAggregateShardingTablesOnlyTest extends AbstractShardingTablesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectCount() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order`"; + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectCount.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectSum() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(`user_id`) AS `user_id_sum` FROM `t_order`"; + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectSum.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMax() throws SQLException, DatabaseUnitException { + String sql = "SELECT MAX(`user_id`) AS `max_user_id` FROM `t_order`"; + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectMax.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMin() throws SQLException, DatabaseUnitException { + String sql = "SELECT MIN(`user_id`) AS `min_user_id` FROM `t_order`"; + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectMin.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + // TODO 改名 avg SHARDING_GEN_2 SHARDING_GEN_3 + public void assertSelectAvg() throws SQLException, DatabaseUnitException { + String sql = "SELECT avg(user_id) as user_id_avg FROM `t_order`"; + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectAvg.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectCountWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `items_count` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ?"; + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 11, 1000, 1909); + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectGroupByShardingTablesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectGroupByShardingTablesOnlyTest.java new file mode 100644 index 0000000000000..c54393868d03a --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectGroupByShardingTablesOnlyTest.java @@ -0,0 +1,72 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.tbl; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class SelectGroupByShardingTablesOnlyTest extends AbstractShardingTablesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectSum() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(order_id) AS `orders_sum`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/tbl/expect/select_groupby/SelectSum.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectCount() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(order_id) AS `orders_count`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/tbl/expect/select_groupby/SelectCount.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMax() throws SQLException, DatabaseUnitException { + String sql = "SELECT MAX(order_id) AS `max_order_id`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/tbl/expect/select_groupby/SelectMax.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectMin() throws SQLException, DatabaseUnitException { + String sql = "SELECT MIN(order_id) AS `min_order_id`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/tbl/expect/select_groupby/SelectMin.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectAvg() throws SQLException, DatabaseUnitException { + String sql = "SELECT AVG(order_id) AS `orders_avg`, `user_id` FROM `t_order` GROUP BY `user_id`"; + assertDataset("integrate/dataset/tbl/expect/select_groupby/SelectAvg.xml", shardingDataSource.getConnection(), "t_order", sql); + } + + @Test + public void assertSelectOrderByDesc() throws SQLException, DatabaseUnitException { + String sql = "SELECT SUM(order_id) AS `orders_sum`, `user_id` FROM `t_order` GROUP BY `user_id` ORDER BY orders_sum DESC"; + assertDataset("integrate/dataset/tbl/expect/select_groupby/SelectOrderByDesc.xml", shardingDataSource.getConnection(), "t_order", sql); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectShardingTablesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectShardingTablesOnlyTest.java new file mode 100644 index 0000000000000..dba6b8a9e724e --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/SelectShardingTablesOnlyTest.java @@ -0,0 +1,112 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.tbl; + +import java.sql.SQLException; + +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; + +public final class SelectShardingTablesOnlyTest extends AbstractShardingTablesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectEqualsWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` = ? AND `order_id` = ?"; + assertDataset("integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 1000); + assertDataset("integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", sql, 11, 1109); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 12, 1000); + } + + @Test + public void assertSelectBetweenWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` BETWEEN ? AND ? AND `order_id` BETWEEN ? AND ? ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/tbl/expect/select/SelectBetweenWithSingleTable.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 1009, 1108); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 1309, 1408); + } + + @Test + public void assertSelectInWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` IN (?, ?, ?) AND `order_id` IN (?, ?) ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/tbl/expect/select/SelectInWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 11, 15, 1009, 1108); + assertDataset("integrate/dataset/tbl/expect/select/SelectInWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 15, 1009, 1108); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", sql, 10, 12, 15, 1309, 1408); + } + + @Test + public void assertSelectLimitWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? ORDER BY i.item_id DESC LIMIT ?, ?"; + assertDataset("integrate/dataset/tbl/expect/select/SelectLimitWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 2, 2); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 10000, 2); + } + + @Test + public void assertSelectLimitWithBindingTableWithoutOffset() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? ORDER BY i.item_id DESC LIMIT ?"; + assertDataset("integrate/dataset/tbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 2); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 19, 1000, 1909, 0); + } + + @Test + public void assertSelectGroupByWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count, o.`user_id` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/tbl/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 11, 1000, 1109); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } + + @Test + public void assertSelectGroupByWithoutGroupedColumn() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/tbl/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 11, 1000, 1109); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 1, 9, 1000, 1909); + } + + @Test + public void assertSelectWithBingdingTableAndConfigTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id JOIN `t_config` c ON o.status = c.status" + + " WHERE o.`user_id` IN (?, ?) AND o.`order_id` BETWEEN ? AND ? AND c.status = ? ORDER BY i.item_id"; + assertDataset("integrate/dataset/tbl/expect/select/SelectWithBingdingTableAndConfigTable.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 11, 1009, 1108, "init"); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", sql, 10, 11, 1009, 1108, "none"); + } + + @Test + public void assertSelectNoShardingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id JOIN `t_config` c ON o.status = c.status ORDER BY i.item_id"; + assertDataset("integrate/dataset/tbl/expect/select/SelectNoShardingTable.xml", shardingDataSource.getConnection(), "t_order_item", sql); + } + + @Test(expected = ShardingJdbcException.class) + public void assertSelectConfigTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_config` c"; + assertDataset(null, shardingDataSource.getConnection(), "t_config", sql); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementDMLShardingTablesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementDMLShardingTablesOnlyTest.java new file mode 100644 index 0000000000000..692d588840c49 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementDMLShardingTablesOnlyTest.java @@ -0,0 +1,113 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.tbl; + +import java.sql.Connection; +import java.sql.Statement; +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public final class StatementDMLShardingTablesOnlyTest extends AbstractShardingTablesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertInsert() throws SQLException, DatabaseUnitException { + String sql = "INSERT INTO `t_order` (`order_id`, `user_id`, `status`) VALUES (%s, %s, '%s')"; + for (int i = 1; i <= 10; i++) { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + stmt.executeUpdate(String.format(sql, i, i, "insert")); + } + } + assertDataset("insert", "insert"); + } + + @Test + public void assertUpdate() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "UPDATE `t_order` SET `status` = '%s' WHERE `order_id` = %s AND `user_id` = %s"; + for (int i = 10; i < 12; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format(sql, "updated", i * 100 + j, i)), is(1)); + } + } + } + assertDataset("update", "updated"); + } + + @Test + public void assertUpdateWithoutShardingValue() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "UPDATE `t_order` SET `status` = '%s' WHERE `status` = '%s'"; + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format(sql, "updated", "init")), is(20)); + } + assertDataset("update", "updated"); + } + + + @Test + public void assertDelete() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "DELETE `t_order` WHERE `order_id` = %s AND `user_id` = %s AND `status` = '%s'"; + for (int i = 10; i < 12; i++) { + for (int j = 0; j < 10; j++) { + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format(sql, i * 100 + j, i, "init")), is(1)); + } + } + } + assertDataset("delete", "init"); + } + + @Test + public void assertDeleteWithoutShardingValue() throws SQLException, DatabaseUnitException { + ShardingDataSource shardingDataSource = getShardingDataSource(); + String sql = "DELETE `t_order` WHERE `status` = '%s'"; + try (Connection connection = shardingDataSource.getConnection()) { + Statement stmt = connection.createStatement(); + assertThat(stmt.executeUpdate(String.format(sql, "init")), is(20)); + } + assertDataset("delete", "init"); + } + + private void assertDataset(final String expectedDataSetPattern, final String status) throws SQLException, DatabaseUnitException { + for (int i = 0; i < 10; i++) { + assertDataset(String.format("integrate/dataset/tbl/expect/%s/db_single.xml", expectedDataSetPattern), + shardingDataSource.getConnection().getConnection("dataSource_db_single"), + String.format("t_order_%s", i), String.format("SELECT * FROM `t_order_%s` WHERE `status`=?", i), status); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementSelectAggregateShardingTablesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementSelectAggregateShardingTablesOnlyTest.java new file mode 100644 index 0000000000000..bc5370f4f9247 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementSelectAggregateShardingTablesOnlyTest.java @@ -0,0 +1,43 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.tbl; + +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +public final class StatementSelectAggregateShardingTablesOnlyTest extends AbstractShardingTablesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectCountWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT COUNT(*) AS `items_count` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s"; + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 11, 1000, 1909)); + assertDataset("integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementSelectShardingTablesOnlyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementSelectShardingTablesOnlyTest.java new file mode 100644 index 0000000000000..036d53c68fe2b --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/integrate/tbl/StatementSelectShardingTablesOnlyTest.java @@ -0,0 +1,98 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.integrate.tbl; + +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; +import org.dbunit.DatabaseUnitException; +import org.junit.Before; +import org.junit.Test; + +public final class StatementSelectShardingTablesOnlyTest extends AbstractShardingTablesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSelectEqualsWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` = %s AND `order_id` = %s"; + assertDataset("integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 1000)); + assertDataset("integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 11, 1109)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 12, 1000)); + } + + @Test + public void assertSelectBetweenWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` BETWEEN %s AND %s AND `order_id` BETWEEN %s AND %s ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/tbl/expect/select/SelectBetweenWithSingleTable.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 1009, 1108)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 1309, 1408)); + } + + @Test + public void assertSelectInWithSingleTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_order` WHERE `user_id` IN (%s, %s, %s) AND `order_id` IN (%s, %s) ORDER BY user_id, order_id"; + assertDataset("integrate/dataset/tbl/expect/select/SelectInWithSingleTable_0.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 11, 15, 1009, 1108)); + assertDataset("integrate/dataset/tbl/expect/select/SelectInWithSingleTable_1.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 15, 1009, 1108)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order", String.format(sql, 10, 12, 15, 1309, 1408)); + } + + @Test + public void assertSelectLimitWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s ORDER BY i.item_id DESC LIMIT %s, %s"; + assertDataset("integrate/dataset/tbl/expect/select/SelectLimitWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909, 2, 2)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 19, 1000, 1909, 10000, 2)); + } + + @Test + public void assertSelectGroupByWithBindingTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count, o.`user_id` FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/tbl/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 11, 1000, 1109)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } + + @Test + public void assertSelectGroupByWithoutGroupedColumn() throws SQLException, DatabaseUnitException { + String sql = "SELECT count(*) as items_count FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s GROUP BY o.`user_id`"; + assertDataset("integrate/dataset/tbl/expect/select/SelectGroupByWithBindingTable.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 11, 1000, 1109)); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 1, 9, 1000, 1909)); + } + + @Test + public void assertSelectWithBingdingTableAndConfigTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT i.* FROM `t_order` o JOIN `t_order_item` i ON o.user_id = i.user_id AND o.order_id = i.order_id JOIN `t_config` c ON o.status = c.status" + + " WHERE o.`user_id` IN (%s, %s) AND o.`order_id` BETWEEN %s AND %s AND c.status = '%s' ORDER BY i.item_id"; + assertDataset("integrate/dataset/tbl/expect/select/SelectWithBingdingTableAndConfigTable.xml", + shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 11, 1009, 1108, "init")); + assertDataset("integrate/dataset/Empty.xml", shardingDataSource.getConnection(), "t_order_item", String.format(sql, 10, 11, 1009, 1108, "none")); + } + + @Test(expected = ShardingJdbcException.class) + public void assertSelectConfigTable() throws SQLException, DatabaseUnitException { + String sql = "SELECT * FROM `t_config` c"; + assertDataset(null, shardingDataSource.getConnection(), "t_config", sql); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/AllApiTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/AllApiTest.java new file mode 100644 index 0000000000000..1d7b1b8b578dd --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/AllApiTest.java @@ -0,0 +1,51 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import com.dangdang.ddframe.rdb.sharding.api.config.ShardingConfigurationTest; +import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRuleTest; +import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRuleTest; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRuleTest; +import com.dangdang.ddframe.rdb.sharding.api.rule.TableRuleTest; +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.ShardingStrategyTest; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategyTest; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithmTest; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.NoneTableShardingAlgorithmTest; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategyTest; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + DatabaseTypeTest.class, + ShardingConfigurationTest.class, + ShardingDataSourceTest.class, + ShardingValueTest.class, + DataSourceRuleTest.class, + ShardingRuleTest.class, + TableRuleTest.class, + BindingTableRuleTest.class, + ShardingStrategyTest.class, + DatabaseShardingStrategyTest.class, + NoneDatabaseShardingAlgorithmTest.class, + TableShardingStrategyTest.class, + NoneTableShardingAlgorithmTest.class + }) +public class AllApiTest { +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/DatabaseTypeTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/DatabaseTypeTest.java new file mode 100644 index 0000000000000..383200e9c51e8 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/DatabaseTypeTest.java @@ -0,0 +1,42 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.exception.DatabaseTypeUnsupportedException; + +public final class DatabaseTypeTest { + + @Test + public void assertValueFromSuccess() { + assertThat(DatabaseType.valueFrom("H2"), is(DatabaseType.H2)); + assertThat(DatabaseType.valueFrom("MySQL"), is(DatabaseType.MySQL)); + assertThat(DatabaseType.valueFrom("Oracle"), is(DatabaseType.Oracle)); + assertThat(DatabaseType.valueFrom("SQLServer"), is(DatabaseType.SQLServer)); + assertThat(DatabaseType.valueFrom("DB2"), is(DatabaseType.DB2)); + } + + @Test(expected = DatabaseTypeUnsupportedException.class) + public void assertValueFromFailure() { + DatabaseType.valueFrom("unknown"); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/ShardingDataSourceTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/ShardingDataSourceTest.java new file mode 100644 index 0000000000000..d8ec2377e3976 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/ShardingDataSourceTest.java @@ -0,0 +1,70 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import javax.sql.DataSource; + +import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public final class ShardingDataSourceTest { + + @Test + public void assertGetConnection() throws SQLException { + Connection connection = mockConnection(); + DataSource dataSource = mock(DataSource.class); + when(dataSource.getConnection()).thenReturn(connection); + assertThat(createShardingDataSource(dataSource).getConnection().getConnection("ds"), is(connection)); + } + + @Test + public void assertGetConnectionWithUsername() throws SQLException { + Connection connection = mockConnection(); + DataSource dataSource = mock(DataSource.class); + when(dataSource.getConnection()).thenReturn(connection); + assertThat(createShardingDataSource(dataSource).getConnection().getConnection("ds"), is(connection)); + } + + private Connection mockConnection() throws SQLException { + Connection result = mock(Connection.class); + DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class); + when(result.getMetaData()).thenReturn(databaseMetaData); + when(databaseMetaData.getDatabaseProductName()).thenReturn("H2"); + return result; + } + + private ShardingDataSource createShardingDataSource(final DataSource dataSource) { + Map dataSourceMap = new HashMap<>(1); + dataSourceMap.put("ds", dataSource); + DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap); + return new ShardingDataSource(new ShardingRule(dataSourceRule, Arrays.asList(new TableRule("logicTable", Arrays.asList("table_0", "table_1", "table_2"), dataSourceRule)))); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/ShardingValueTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/ShardingValueTest.java new file mode 100644 index 0000000000000..3eaa824d1b3fa --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/ShardingValueTest.java @@ -0,0 +1,61 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue.ShardingValueType; +import com.google.common.collect.Range; + +public final class ShardingValueTest { + + @Test + public void assertGetTypeWithSingleValue() { + assertThat(new ShardingValue("columnName", "value").getType(), is(ShardingValueType.SINGLE)); + } + + @Test + public void assertGetTypeWithMultipleValue() { + assertThat(new ShardingValue("columnName", Arrays.asList("value")).getType(), is(ShardingValueType.LIST)); + } + + @Test + public void assertGetTypeWithRangeValue() { + assertThat(new ShardingValue("columnName", Range.closed(10, 20)).getType(), is(ShardingValueType.RANGE)); + } + + @Test + public void assertToStringWithSingleValue() { + assertThat(new ShardingValue("columnName", "value").toString(), is("ShardingValue(columnName=columnName, value=value, values=[], valueRange=null)")); + } + + @Test + public void assertToStringWithMultipleValue() { + assertThat(new ShardingValue("columnName", Arrays.asList("value")).toString(), is("ShardingValue(columnName=columnName, value=null, values=[value], valueRange=null)")); + } + + @Test + public void assertToStringWithRangeValue() { + assertThat(new ShardingValue("columnName", Range.closed(10, 20)).toString(), is("ShardingValue(columnName=columnName, value=null, values=[], valueRange=[10‥20])")); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfigurationTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfigurationTest.java new file mode 100644 index 0000000000000..54532683dde56 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/config/ShardingConfigurationTest.java @@ -0,0 +1,90 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.config; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; + +public final class ShardingConfigurationTest { + + private final Properties prop = new Properties(); + + private final ShardingConfiguration shardingConfiguration = new ShardingConfiguration(prop); + + @Before + public void setUp() { + prop.put(ShardingConfigurationConstant.METRICS_SECOND_PERIOD.getKey(), "1000"); + prop.put(ShardingConfigurationConstant.METRICS_ENABLE.getKey(), "true"); + prop.put(ShardingConfigurationConstant.METRICS_PACKAGE_NAME.getKey(), "example"); + } + + @Test + public void assertGetConfigWithDefaultValue() { + assertThat(new ShardingConfiguration(new Properties()).getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD), + is(ShardingConfigurationConstant.METRICS_SECOND_PERIOD.getDefaultValue())); + assertThat(new ShardingConfiguration(new Properties()).getConfig(ShardingConfigurationConstant.METRICS_ENABLE), + is(ShardingConfigurationConstant.METRICS_ENABLE.getDefaultValue())); + assertThat(new ShardingConfiguration(new Properties()).getConfig(ShardingConfigurationConstant.METRICS_PACKAGE_NAME), + is(ShardingConfigurationConstant.METRICS_PACKAGE_NAME.getDefaultValue())); + } + + @Test + public void assertGetConfig() { + assertThat(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD), is("1000")); + } + + @Test + public void assertGetConfigForBoolean() { + assertTrue(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_ENABLE, boolean.class)); + assertTrue(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_ENABLE, Boolean.class)); + } + + @Test + public void assertGetConfigForInteger() { + assertThat(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD, int.class), is(1000)); + assertThat(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD, Integer.class), is(1000)); + } + + @Test + public void assertGetConfigForLong() { + assertThat(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD, long.class), is(1000L)); + assertThat(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD, Long.class), is(1000L)); + } + + @Test + public void assertGetConfigForDouble() { + assertThat(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD, double.class), is(1000D)); + assertThat(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_SECOND_PERIOD, Double.class), is(1000D)); + } + + @Test + public void assertGetConfigForString() { + assertThat(shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_PACKAGE_NAME, String.class), is("example")); + } + + @Test(expected = UnsupportedOperationException.class) + public void assertGetConfigFailure() { + shardingConfiguration.getConfig(ShardingConfigurationConstant.METRICS_PACKAGE_NAME, Object.class); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/BindingTableRuleTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/BindingTableRuleTest.java new file mode 100644 index 0000000000000..e008f02280fb0 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/BindingTableRuleTest.java @@ -0,0 +1,78 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.rule; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; + +public final class BindingTableRuleTest { + + @Test + public void assertHasLogicTable() { + assertTrue(createBindingTableRule().hasLogicTable("logicTable")); + } + + @Test + public void assertNotHasLogicTable() { + assertFalse(createBindingTableRule().hasLogicTable("newTable")); + } + + @Test + public void assertGetBindingActualTablesSuccess() { + assertThat(createBindingTableRule().getBindingActualTable("ds1", "subLogicTable", "table_1"), is("sub_table_1")); + } + + @Test(expected = IllegalStateException.class) + public void assertGetBindingActualTablesFailure() { + createBindingTableRule().getBindingActualTable("no_ds", "subLogicTable", "table_1"); + } + + @Test + public void assertGetAllLogicTables() { + assertThat(createBindingTableRule().getAllLogicTables(), is((Collection) Arrays.asList("logicTable", "subLogicTable"))); + } + + @Test + public void assertGetTableRules() { + assertThat(createBindingTableRule().getTableRules().size(), is(2)); + assertThat(createBindingTableRule().getTableRules().get(0).getLogicTable(), is(createTableRule().getLogicTable())); + assertThat(createBindingTableRule().getTableRules().get(0).getActualTables(), is(createTableRule().getActualTables())); + assertThat(createBindingTableRule().getTableRules().get(1).getLogicTable(), is(createSubTableRule().getLogicTable())); + assertThat(createBindingTableRule().getTableRules().get(1).getActualTables(), is(createSubTableRule().getActualTables())); + } + + private BindingTableRule createBindingTableRule() { + return new BindingTableRule(Arrays.asList(createTableRule(), createSubTableRule())); + } + + private TableRule createTableRule() { + return new TableRule("logicTable", Arrays.asList(new DataNode("ds1", "table_0"), new DataNode("ds1", "table_1"), new DataNode("ds2", "table_0"), new DataNode("ds2", "table_1"))); + } + + private TableRule createSubTableRule() { + return new TableRule("subLogicTable", Arrays.asList( + new DataNode("ds1", "sub_table_0"), new DataNode("ds1", "sub_table_1"), new DataNode("ds2", "sub_table_0"), new DataNode("ds2", "sub_table_1"))); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataSourceRuleTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataSourceRuleTest.java new file mode 100644 index 0000000000000..3994f392f1dfa --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/DataSourceRuleTest.java @@ -0,0 +1,63 @@ +package com.dangdang.ddframe.rdb.sharding.api.rule; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.rule.fixture.TestDataSource; +import com.google.common.collect.Sets; + +public final class DataSourceRuleTest { + + private Map dataSourceMap = new HashMap<>(3); + + private DataSourceRule dataSourceRule; + + @Before + public void setUp() { + dataSourceMap.put("ds0", new TestDataSource("ds0")); + dataSourceMap.put("ds1", new TestDataSource("ds1")); + dataSourceMap.put("ds2", new TestDataSource("ds2")); + dataSourceRule = new DataSourceRule(dataSourceMap); + } + + @Test(expected = NullPointerException.class) + public void assertNewDataSourceFailureWhenDataSourceMapIsNull() { + new DataSourceRule(null); + } + + @Test(expected = IllegalStateException.class) + public void assertNewDataSourceFailureWhenDataSourceMapIsEmpty() { + new DataSourceRule(Collections.emptyMap()); + } + + @Test + public void assertGetDataSource() { + assertDataSource("ds0"); + assertDataSource("ds1"); + assertDataSource("ds2"); + } + + private void assertDataSource(final String dataSourceName) { + assertThat(dataSourceRule.getDataSource(dataSourceName), is((DataSource) new TestDataSource(dataSourceName))); + } + + @Test + public void assertGetDataSourceNames() { + assertThat(dataSourceRule.getDataSourceNames(), is((Collection) Sets.newHashSet("ds0", "ds1", "ds2"))); + } + + @Test + public void assertGetDataSources() { + assertThat(dataSourceRule.getDataSources(), is((Collection) dataSourceMap.values())); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/ShardingRuleTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/ShardingRuleTest.java new file mode 100644 index 0000000000000..c0117f9916afc --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/ShardingRuleTest.java @@ -0,0 +1,207 @@ +package com.dangdang.ddframe.rdb.sharding.api.rule; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.NoneTableShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; + +public final class ShardingRuleTest { + + @Test + public void assertShardingRuleWithoutStrategy() { + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(createTableRule())); + assertTrue(actual.getDatabaseShardingStrategy().getShardingColumns().isEmpty()); + assertTrue(actual.getTableShardingStrategy().getShardingColumns().isEmpty()); + } + + @Test + public void assertShardingRuleWithBindingTableRuleWithoutStrategy() { + ShardingRule actual = createShardingRule(); + assertTrue(actual.getDatabaseShardingStrategy().getShardingColumns().isEmpty()); + assertTrue(actual.getTableShardingStrategy().getShardingColumns().isEmpty()); + } + + @Test + public void assertShardingRuleWithDatabaseStrategy() { + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(createTableRule()), createDatabaseShardingStrategy()); + assertThat(actual.getDatabaseShardingStrategy().getShardingColumns().size(), is(1)); + assertTrue(actual.getTableShardingStrategy().getShardingColumns().isEmpty()); + } + + @Test + public void assertShardingRuleWithTableStrategy() { + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(createTableRule()), createTableShardingStrategy()); + assertThat(actual.getTableShardingStrategy().getShardingColumns().size(), is(1)); + assertTrue(actual.getDatabaseShardingStrategy().getShardingColumns().isEmpty()); + } + + @Test + public void assertShardingRuleWithoutBindingTableRule() { + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(createTableRule()), createDatabaseShardingStrategy(), createTableShardingStrategy()); + assertThat(actual.getDatabaseShardingStrategy().getShardingColumns().size(), is(1)); + assertThat(actual.getTableShardingStrategy().getShardingColumns().size(), is(1)); + } + + @Test + public void assertFindTableRule() { + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(createTableRule()), createDatabaseShardingStrategy(), createTableShardingStrategy()); + assertTrue(actual.findTableRule("logicTable").isPresent()); + assertFalse(actual.findTableRule("null").isPresent()); + } + + @Test + public void assertGetDatabaseShardingStrategyFromTableRule() { + DatabaseShardingStrategy strategy = createDatabaseShardingStrategy(); + TableRule tableRule = createTableRule(strategy); + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(tableRule)); + assertThat(actual.getDatabaseShardingStrategy(tableRule), is(strategy)); + } + + @Test + public void assertGetDatabaseShardingStrategyFromDefault() { + DatabaseShardingStrategy strategy = createDatabaseShardingStrategy(); + TableRule tableRule = createTableRule(); + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(tableRule), strategy); + assertThat(actual.getDatabaseShardingStrategy(tableRule), is(strategy)); + } + + @Test(expected = NullPointerException.class) + public void assertGetDatabaseShardingStrategyFailure() { + DatabaseShardingStrategy strategy = null; + TableRule tableRule = createTableRule(); + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(tableRule), strategy); + assertThat(actual.getDatabaseShardingStrategy(tableRule), is(strategy)); + } + + @Test + public void assertGetTableShardingStrategyFromTableRule() { + TableShardingStrategy strategy = createTableShardingStrategy(); + TableRule tableRule = createTableRule(strategy); + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(tableRule)); + assertThat(actual.getTableShardingStrategy(tableRule), is(strategy)); + } + + @Test + public void assertGetTableShardingStrategyFromDefault() { + TableShardingStrategy strategy = createTableShardingStrategy(); + TableRule tableRule = createTableRule(); + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(tableRule), strategy); + assertThat(actual.getTableShardingStrategy(tableRule), is(strategy)); + } + + @Test(expected = NullPointerException.class) + public void assertGetTableShardingStrategyFailure() { + TableShardingStrategy strategy = null; + TableRule tableRule = createTableRule(); + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(tableRule), strategy); + assertThat(actual.getTableShardingStrategy(tableRule), is(strategy)); + } + + @Test + public void assertGetBindingTableRuleForNotConfig() { + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(createTableRule())); + assertFalse(actual.getBindingTableRule("logicTable").isPresent()); + } + + @Test + public void assertGetBindingTableRuleForNotFound() { + assertFalse(createShardingRule().getBindingTableRule("newTable").isPresent()); + } + + @Test + public void assertGetBindingTableRuleForFound() { + BindingTableRule bindingTableRule = createBindingTableRule(); + ShardingRule actual = new ShardingRule(createDataSourceRule(), Arrays.asList(createTableRule()), Arrays.asList(bindingTableRule)); + assertThat(actual.getBindingTableRule("logicTable").get(), is(bindingTableRule)); + } + + @Test + public void assertFilterAllBindingTablesWhenLogicTablesIsEmpty() { + assertThat(createShardingRule().filterAllBindingTables(Collections.emptyList()), is((Collection) Collections.emptyList())); + } + + @Test + public void assertFilterAllBindingTablesWhenBindingTableRuleIsNotFound() { + assertThat(createShardingRule().filterAllBindingTables(Arrays.asList("newTable")), is((Collection) Collections.emptyList())); + } + + @Test + public void assertFilterAllBindingTables() { + assertThat(createShardingRule().filterAllBindingTables(Arrays.asList("logicTable")), is((Collection) Arrays.asList("logicTable"))); + assertThat(createShardingRule().filterAllBindingTables(Arrays.asList("subLogicTable")), is((Collection) Arrays.asList("subLogicTable"))); + assertThat(createShardingRule().filterAllBindingTables(Arrays.asList("logicTable", "subLogicTable")), is((Collection) Arrays.asList("logicTable", "subLogicTable"))); + assertThat(createShardingRule().filterAllBindingTables(Arrays.asList("logicTable", "newTable", "subLogicTable")), is((Collection) Arrays.asList("logicTable", "subLogicTable"))); + } + + @Test + public void assertIsAllBindingTableWhenLogicTablesIsEmpty() { + assertFalse(createShardingRule().isAllBindingTable(Collections.emptyList())); + } + + @Test + public void assertIsNotAllBindingTable() { + assertFalse(createShardingRule().isAllBindingTable(Arrays.asList("newTable"))); + assertFalse(createShardingRule().isAllBindingTable(Arrays.asList("logicTable", "newTable"))); + } + + @Test + public void assertIsAllBindingTable() { + assertTrue(createShardingRule().isAllBindingTable(Arrays.asList("logicTable"))); + assertTrue(createShardingRule().isAllBindingTable(Arrays.asList("subLogicTable"))); + assertTrue(createShardingRule().isAllBindingTable(Arrays.asList("logicTable", "subLogicTable"))); + } + + private ShardingRule createShardingRule() { + return new ShardingRule(createDataSourceRule(), Arrays.asList(createTableRule()), Arrays.asList(createBindingTableRule())); + } + + private DataSourceRule createDataSourceRule() { + Map result = new HashMap<>(2); + result.put("ds0", null); + result.put("ds1", null); + return new DataSourceRule(result); + } + + private TableRule createTableRule() { + return new TableRule("logicTable", Arrays.asList("table_0", "table_1", "table_2"), createDataSourceRule()); + } + + private TableRule createTableRule(final DatabaseShardingStrategy strategy) { + return new TableRule("logicTable", Arrays.asList("table_0", "table_1", "table_2"), createDataSourceRule(), strategy); + } + + private TableRule createTableRule(final TableShardingStrategy strategy) { + return new TableRule("logicTable", Arrays.asList("table_0", "table_1", "table_2"), createDataSourceRule(), strategy); + } + + private BindingTableRule createBindingTableRule() { + return new BindingTableRule(Arrays.asList(createTableRule(), createSubTableRule())); + } + + private TableRule createSubTableRule() { + return new TableRule("subLogicTable", Arrays.asList("sub_table_0", "sub_table_1", "sub_table_2"), createDataSourceRule()); + } + + private DatabaseShardingStrategy createDatabaseShardingStrategy() { + return new DatabaseShardingStrategy(Arrays.asList("column"), new NoneDatabaseShardingAlgorithm()); + } + + private TableShardingStrategy createTableShardingStrategy() { + return new TableShardingStrategy(Arrays.asList("column"), new NoneTableShardingAlgorithm()); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/TableRuleTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/TableRuleTest.java new file mode 100644 index 0000000000000..1d03acde7e728 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/TableRuleTest.java @@ -0,0 +1,105 @@ +package com.dangdang.ddframe.rdb.sharding.api.rule; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.NoneTableShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; +import com.google.common.collect.Sets; + +public final class TableRuleTest { + + @Test + public void assertTableRuleWithoutDataNode() { + assertActualTable(new TableRule("logicTable", Arrays.asList("table_0", "table_1", "table_2"), createDataSourceRule())); + } + + @Test + public void assertTableRuleWithDatabaseShardingStrategyWithoutDataNode() { + assertActualTable(new TableRule("logicTable", Arrays.asList("table_0", "table_1", "table_2"), createDataSourceRule(), new DatabaseShardingStrategy("", new NoneDatabaseShardingAlgorithm()))); + } + + @Test + public void assertTableRuleWithTableShardingStrategyWithoutDataNode() { + assertActualTable(new TableRule("logicTable", Arrays.asList("table_0", "table_1", "table_2"), createDataSourceRule(), new TableShardingStrategy("", new NoneTableShardingAlgorithm()))); + } + + @Test + public void assertTableRuleWithDataNode() { + assertActualTable(new TableRule("logicTable", Arrays.asList( + new DataNode("ds0", "table_0"), new DataNode("ds0", "table_1"), new DataNode("ds0", "table_2"), + new DataNode("ds1", "table_0"), new DataNode("ds1", "table_1"), new DataNode("ds1", "table_2")))); + } + + @Test + public void assertTableRuleWithDataNodeString() { + assertActualTable(new TableRule("logicTable", Arrays.asList("ds0.table_0", "ds0.table_1", "ds0.table_2", "ds1.table_0", "ds1.table_1", "ds1.table_2"), createDataSourceRule())); + } + + private void assertActualTable(final TableRule actual) { + assertThat(actual.getActualTables().size(), is(6)); + assertTrue(actual.getActualTables().contains(new DataNode("ds0", "table_0"))); + assertTrue(actual.getActualTables().contains(new DataNode("ds0", "table_1"))); + assertTrue(actual.getActualTables().contains(new DataNode("ds0", "table_2"))); + assertTrue(actual.getActualTables().contains(new DataNode("ds1", "table_0"))); + assertTrue(actual.getActualTables().contains(new DataNode("ds1", "table_1"))); + assertTrue(actual.getActualTables().contains(new DataNode("ds1", "table_2"))); + } + + @Test + public void assertGetActualDataNodes() { + TableRule actual = new TableRule("logicTable", Arrays.asList("ds0.table_0", "ds0.table_1", "ds0.table_2", "ds1.table_0", "ds1.table_1", "ds1.table_2"), createDataSourceRule()); + assertThat(actual.getActualDataNodes(Arrays.asList("ds1"), Arrays.asList("table_0", "table_1")), is( + (Collection) Sets.newLinkedHashSet(Arrays.asList(new DataNode("ds1", "table_0"), new DataNode("ds1", "table_1"))))); + } + + @Test + public void assertGetActualTableNames() { + TableRule actual = new TableRule("logicTable", Arrays.asList("ds0.table_0", "ds0.table_1", "ds0.table_2", "ds1.table_0", "ds1.table_1", "ds1.table_2"), createDataSourceRule()); + assertThat(actual.getActualTableNames(Arrays.asList("ds1")), is((Collection) Sets.newLinkedHashSet(Arrays.asList("table_0", "table_1", "table_2")))); + } + + @Test + public void assertFindActualTableIndex() { + TableRule actual = new TableRule("logicTable", Arrays.asList("ds0.table_0", "ds0.table_1", "ds0.table_2", "ds1.table_0", "ds1.table_1", "ds1.table_2"), createDataSourceRule()); + assertThat(actual.findActualTableIndex("ds1", "table_1"), is(4)); + } + + @Test + public void assertFindActualTableIndexForNotFound() { + TableRule actual = new TableRule("logicTable", Arrays.asList("ds0.table_0", "ds0.table_1", "ds0.table_2", "ds1.table_0", "ds1.table_1", "ds1.table_2"), createDataSourceRule()); + assertThat(actual.findActualTableIndex("ds2", "table_2"), is(-1)); + } + + @Test + public void assertToString() { + TableRule actual = new TableRule("logicTable", Arrays.asList("ds0.table_0", "ds0.table_1", "ds0.table_2", "ds1.table_0", "ds1.table_1", "ds1.table_2"), createDataSourceRule()); + assertThat(actual.toString(), is("TableRule(logicTable=logicTable, actualTables=[" + + "DataNode(dataSourceName=ds0, tableName=table_0), " + + "DataNode(dataSourceName=ds0, tableName=table_1), " + + "DataNode(dataSourceName=ds0, tableName=table_2), " + + "DataNode(dataSourceName=ds1, tableName=table_0), " + + "DataNode(dataSourceName=ds1, tableName=table_1), " + + "DataNode(dataSourceName=ds1, tableName=table_2)], " + + "databaseShardingStrategy=null, tableShardingStrategy=null)")); + } + + private DataSourceRule createDataSourceRule() { + Map result = new HashMap<>(2); + result.put("ds0", null); + result.put("ds1", null); + return new DataSourceRule(result); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/fixture/TestDataSource.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/fixture/TestDataSource.java new file mode 100644 index 0000000000000..2077f945a0576 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/rule/fixture/TestDataSource.java @@ -0,0 +1,28 @@ +package com.dangdang.ddframe.rdb.sharding.api.rule.fixture; + +import java.sql.Connection; +import java.sql.SQLException; + +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractDataSourceAdapter; + +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@RequiredArgsConstructor +@EqualsAndHashCode(callSuper = false) +@ToString +public final class TestDataSource extends AbstractDataSourceAdapter { + + private final String name; + + @Override + public Connection getConnection() throws SQLException { + return null; + } + + @Override + public Connection getConnection(final String username, final String password) throws SQLException { + return getConnection(); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingStrategyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingStrategyTest.java new file mode 100644 index 0000000000000..66e037c83eb6f --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/common/ShardingStrategyTest.java @@ -0,0 +1,74 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.common; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.fixture.TestMultipleKeysShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.fixture.TestSingleKeyShardingAlgorithm; +import com.google.common.collect.Range; + +public final class ShardingStrategyTest { + + private final Collection targets = Arrays.asList("1", "2", "3"); + + @Test + public void assertDoShardingWithoutShardingColumns() { + ShardingStrategy strategy = new ShardingStrategy(Arrays.asList("column"), null); + assertThat(strategy.doSharding(targets, Collections.>emptyList()), is(targets)); + } + + @Test + public void assertDoShardingForEqualSingleKey() { + ShardingStrategy strategy = new ShardingStrategy("column", new TestSingleKeyShardingAlgorithm()); + assertThat(strategy.doSharding(targets, createShardingValues(new ShardingValue("column", "1"))), is((Collection) Arrays.asList("1"))); + } + + @Test + public void assertDoShardingForInSingleKey() { + ShardingStrategy strategy = new ShardingStrategy("column", new TestSingleKeyShardingAlgorithm()); + assertThat(strategy.doSharding(targets, createShardingValues(new ShardingValue("column", Arrays.asList("1", "3")))), is((Collection) Arrays.asList("1", "3"))); + } + + @Test + public void assertDoShardingForBetweenSingleKey() { + ShardingStrategy strategy = new ShardingStrategy("column", new TestSingleKeyShardingAlgorithm()); + assertThat(strategy.doSharding(targets, createShardingValues(new ShardingValue("column", Range.open("1", "3")))), is((Collection) Arrays.asList("1", "2", "3"))); + } + + @Test + public void assertDoShardingForMultipleKeys() { + ShardingStrategy strategy = new ShardingStrategy("column", new TestMultipleKeysShardingAlgorithm()); + assertThat(strategy.doSharding(targets, createShardingValues(new ShardingValue("column", "1"))), is((Collection) Arrays.asList("1", "2", "3"))); + } + + private Collection> createShardingValues(final ShardingValue shardingValue) { + Collection> result = new ArrayList<>(1); + result.add(shardingValue); + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingStrategyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingStrategyTest.java new file mode 100644 index 0000000000000..305519b7c524d --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/DatabaseShardingStrategyTest.java @@ -0,0 +1,34 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.database; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; + +public final class DatabaseShardingStrategyTest { + + @Test + public void assertDatabaseShardingStrategyWithSingleShardingColumn() { + assertThat(new DatabaseShardingStrategy("shardingColumn", null).getShardingColumns(), is((Collection) Arrays.asList("shardingColumn"))); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/NoneDatabaseShardingAlgorithmTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/NoneDatabaseShardingAlgorithmTest.java new file mode 100644 index 0000000000000..3b3c8ac28b199 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/database/NoneDatabaseShardingAlgorithmTest.java @@ -0,0 +1,60 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.database; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.junit.Test; + +public final class NoneDatabaseShardingAlgorithmTest { + + private final NoneDatabaseShardingAlgorithm noneDatabaseShardingAlgorithm = new NoneDatabaseShardingAlgorithm(); + + private final Collection targets = Arrays.asList("ds"); + + @Test + public void assertDoSharding() { + assertThat(noneDatabaseShardingAlgorithm.doSharding(targets, null), is(targets)); + } + + @Test + public void assertDoEqualShardingForTargetsEmtpy() { + assertNull(noneDatabaseShardingAlgorithm.doEqualSharding(Collections.emptyList(), null)); + } + + @Test + public void assertDoEqualSharding() { + assertThat(noneDatabaseShardingAlgorithm.doEqualSharding(targets, null), is("ds")); + } + + @Test + public void assertDoInSharding() { + assertThat(noneDatabaseShardingAlgorithm.doInSharding(targets, null), is(targets)); + } + + @Test + public void assertDoBetweenSharding() { + assertThat(noneDatabaseShardingAlgorithm.doBetweenSharding(targets, null), is(targets)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/fixture/TestMultipleKeysShardingAlgorithm.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/fixture/TestMultipleKeysShardingAlgorithm.java new file mode 100644 index 0000000000000..38a5a6de00938 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/fixture/TestMultipleKeysShardingAlgorithm.java @@ -0,0 +1,33 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.fixture; + +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.MultipleKeysShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingAlgorithm; + +public final class TestMultipleKeysShardingAlgorithm implements MultipleKeysShardingAlgorithm, DatabaseShardingAlgorithm, TableShardingAlgorithm { + + @Override + public Collection doSharding(final Collection availableTargetNames, final Collection> shardingValues) { + return availableTargetNames; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/fixture/TestSingleKeyShardingAlgorithm.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/fixture/TestSingleKeyShardingAlgorithm.java new file mode 100644 index 0000000000000..d3ca6d287284c --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/fixture/TestSingleKeyShardingAlgorithm.java @@ -0,0 +1,48 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.fixture; + +import java.util.ArrayList; +import java.util.Collection; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.common.SingleKeyShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingAlgorithm; + +public final class TestSingleKeyShardingAlgorithm implements SingleKeyShardingAlgorithm, DatabaseShardingAlgorithm, TableShardingAlgorithm { + + @Override + public String doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + return shardingValue.getValue(); + } + + @Override + public Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + return shardingValue.getValues(); + } + + @Override + public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new ArrayList<>(); + for (Integer i = Integer.parseInt(shardingValue.getValueRange().lowerEndpoint()); i <= Integer.parseInt(shardingValue.getValueRange().upperEndpoint()); i++) { + result.add(i.toString()); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/NoneTableShardingAlgorithmTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/NoneTableShardingAlgorithmTest.java new file mode 100644 index 0000000000000..60dacc5e18369 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/NoneTableShardingAlgorithmTest.java @@ -0,0 +1,60 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.table; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.junit.Test; + +public final class NoneTableShardingAlgorithmTest { + + private final NoneTableShardingAlgorithm noneTableShardingAlgorithm = new NoneTableShardingAlgorithm(); + + private final Collection targets = Arrays.asList("tbl"); + + @Test + public void assertDoEqualShardingForTargetsEmtpy() { + assertNull(noneTableShardingAlgorithm.doEqualSharding(Collections.emptyList(), null)); + } + + @Test + public void assertDoSharding() { + assertThat(noneTableShardingAlgorithm.doSharding(targets, null), is(targets)); + } + + @Test + public void assertDoEqualSharding() { + assertThat(noneTableShardingAlgorithm.doEqualSharding(targets, null), is("tbl")); + } + + @Test + public void assertDoInSharding() { + assertThat(noneTableShardingAlgorithm.doInSharding(targets, null), is(targets)); + } + + @Test + public void assertDoBetweenSharding() { + assertThat(noneTableShardingAlgorithm.doBetweenSharding(targets, null), is(targets)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingStrategyTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingStrategyTest.java new file mode 100644 index 0000000000000..42a9ead442739 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/api/strategy/table/TableShardingStrategyTest.java @@ -0,0 +1,34 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.api.strategy.table; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; + +public final class TableShardingStrategyTest { + + @Test + public void assertTableShardingStrategyWithSingleShardingColumn() { + assertThat(new TableShardingStrategy("shardingColumn", null).getShardingColumns(), is((Collection) Arrays.asList("shardingColumn"))); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/AllJDBCTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/AllJDBCTest.java new file mode 100644 index 0000000000000..9ec741cd35cfc --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/AllJDBCTest.java @@ -0,0 +1,56 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.ConnectionAdapterTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.DataSourceAdapterTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.PreparedStatementAdapterTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.ResultSetAdapterTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.ResultSetGetterAdapterTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.StatementAdapterTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.ResultSetUpdaterAdapterTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.UnsupportedOperationConnectionTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.UnsupportedOperationDataSourceTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.UnsupportedOperationPreparedStatementTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.UnsupportedOperationResultSetTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.UnsupportedOperationStatementTest; +import com.dangdang.ddframe.rdb.sharding.jdbc.util.JdbcMethodInvocationTest; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ShardingStatementTest.class, + ShardingPreparedStatementTest.class, + UnsupportedOperationDataSourceTest.class, + UnsupportedOperationConnectionTest.class, + UnsupportedOperationStatementTest.class, + UnsupportedOperationPreparedStatementTest.class, + UnsupportedOperationResultSetTest.class, + ResultSetUpdaterAdapterTest.class, + DataSourceAdapterTest.class, + ConnectionAdapterTest.class, + StatementAdapterTest.class, + PreparedStatementAdapterTest.class, + ResultSetGetterAdapterTest.class, + ResultSetAdapterTest.class, + JdbcMethodInvocationTest.class + }) +public class AllJDBCTest { +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingPreparedStatementTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingPreparedStatementTest.java new file mode 100644 index 0000000000000..7e3e8fbb499d5 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingPreparedStatementTest.java @@ -0,0 +1,213 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.mysql.jdbc.Statement; + +public final class ShardingPreparedStatementTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertExecuteQueryWithParameter() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql)) { + pstmt.setString(1, "init"); + ResultSet resultSet = pstmt.executeQuery(); + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteQueryWithoutParameter() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql)) { + ResultSet resultSet = pstmt.executeQuery(); + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteUpdateWithParameter() throws SQLException { + String sql = "DELETE FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql)) { + pstmt.setString(1, "init"); + assertThat(pstmt.executeUpdate(), is(40)); + } + } + + @Test + public void assertExecuteUpdateWithoutParameter() throws SQLException { + String sql = "DELETE FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql)) { + assertThat(pstmt.executeUpdate(), is(40)); + } + } + + @Test + public void assertExecuteWithParameter() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql)) { + pstmt.setString(1, "init"); + assertTrue(pstmt.execute()); + assertTrue(pstmt.getResultSet().next()); + assertThat(pstmt.getResultSet().getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteWithoutParameter() throws SQLException { + String sql = "DELETE FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql)) { + assertFalse(pstmt.execute()); + } + } + + @Test + public void assertExecuteQueryWithResultSetTypeAndRsultSetConcurrency() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) { + pstmt.setString(1, "init"); + ResultSet resultSet = pstmt.executeQuery(); + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteQueryWithResultSetTypeAndRsultSetConcurrencyAndResultSetHoldability() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT)) { + pstmt.setString(1, "init"); + ResultSet resultSet = pstmt.executeQuery(); + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteQueryWithResultSetHoldabilityIsZero() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, 0)) { + pstmt.setString(1, "init"); + ResultSet resultSet = pstmt.executeQuery(); + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteQueryWithAutoGeneratedKeys() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql, Statement.NO_GENERATED_KEYS)) { + pstmt.setString(1, "init"); + ResultSet resultSet = pstmt.executeQuery(); + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteQueryWithColumnIndexes() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql, new int[] {1})) { + pstmt.setNull(1, java.sql.Types.VARCHAR); + ResultSet resultSet = pstmt.executeQuery(); + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(0L)); + } + } + + @Test + public void assertExecuteQueryWithColumnNames() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = ?"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql, new String[] {"orders_count"})) { + pstmt.setNull(1, java.sql.Types.VARCHAR); + ResultSet resultSet = pstmt.executeQuery(); + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(0L)); + } + } + + @Test + public void assertBatch() throws SQLException { + String sql = "INSERT INTO `t_order`(`order_id`, `user_id`, `status`) VALUES (?,?,?)"; + try ( + Connection connection = shardingDataSource.getConnection(); + PreparedStatement pstmt = connection.prepareStatement(sql)) { + pstmt.setInt(1 ,3101); + pstmt.setInt(2 ,11); + pstmt.setString(3 ,"BATCH"); + pstmt.addBatch(); + pstmt.setInt(1 ,3102); + pstmt.setInt(2 ,12); + pstmt.setString(3 ,"BATCH"); + pstmt.addBatch(); + int[] result = pstmt.executeBatch(); + for (int each : result) { + assertThat(each, is(1)); + } + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingStatementTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingStatementTest.java new file mode 100644 index 0000000000000..9e3969e889a22 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/ShardingStatementTest.java @@ -0,0 +1,188 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class ShardingStatementTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertExecuteQuery() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet resultSet = stmt.executeQuery(sql)) { + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteUpdate() throws SQLException { + String sql = "DELETE FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertThat(stmt.executeUpdate(sql), is(40)); + } + } + + @Test + public void assertExecute() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertTrue(stmt.execute(sql)); + assertTrue(stmt.getResultSet().next()); + assertThat(stmt.getResultSet().getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteQueryWithResultSetTypeAndRsultSetConcurrency() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + ResultSet resultSet = stmt.executeQuery(sql)) { + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteQueryWithResultSetTypeAndRsultSetConcurrencyAndResultSetHoldability() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + ResultSet resultSet = stmt.executeQuery(sql)) { + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteQueryWithResultSetHoldabilityIsZero() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, 0); + ResultSet resultSet = stmt.executeQuery(sql)) { + assertTrue(resultSet.next()); + assertThat(resultSet.getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteUpdateWithAutoGeneratedKeys() throws SQLException { + String sql = "DELETE FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertThat(stmt.executeUpdate(sql, Statement.NO_GENERATED_KEYS), is(40)); + } + } + + @Test + public void assertExecuteUpdateWithColumnIndexes() throws SQLException { + String sql = "DELETE FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertThat(stmt.executeUpdate(sql, new int[] {1}), is(40)); + } + } + + @Test + public void assertExecuteUpdateWithColumnNames() throws SQLException { + String sql = "DELETE FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertThat(stmt.executeUpdate(sql, new String[] {"orders_count"}), is(40)); + } + } + + @Test + public void assertExecuteWithAutoGeneratedKeys() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertTrue(stmt.execute(sql, Statement.NO_GENERATED_KEYS)); + assertTrue(stmt.getResultSet().next()); + assertThat(stmt.getResultSet().getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteWithColumnIndexes() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertTrue(stmt.execute(sql, new int[] {1})); + assertTrue(stmt.getResultSet().next()); + assertThat(stmt.getResultSet().getLong(1), is(40L)); + } + } + + @Test + public void assertExecuteWithColumnNames() throws SQLException { + String sql = "SELECT COUNT(*) AS `orders_count` FROM `t_order` WHERE `status` = 'init'"; + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertTrue(stmt.execute(sql, new String[] {"orders_count"})); + assertTrue(stmt.getResultSet().next()); + assertThat(stmt.getResultSet().getLong(1), is(40L)); + } + } + + @Test + public void assertGetConnection() throws SQLException { + try ( + Connection connection = shardingDataSource.getConnection(); + Statement stmt = connection.createStatement()) { + assertThat(stmt.getConnection(), is(connection)); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ConnectionAdapterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ConnectionAdapterTest.java new file mode 100644 index 0000000000000..0afb48543b406 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ConnectionAdapterTest.java @@ -0,0 +1,179 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.AbstractDBUnitTest; +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; + +public final class ConnectionAdapterTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertSetAutoCommit() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + assertTrue(actual.getAutoCommit()); + actual.setAutoCommit(false); + actual.createStatement().executeQuery("SELECT `user_id` FROM `t_order` WHERE `status` = 'init'"); + assertAutoCommit(actual, false); + actual.setAutoCommit(true); + assertAutoCommit(actual, true); + } + } + + private void assertAutoCommit(final ShardingConnection actual, final boolean autoCommit) throws SQLException { + assertThat(actual.getAutoCommit(), is(autoCommit)); + assertThat(actual.getConnections().size(), is(10)); + for (Connection each : actual.getConnections()) { + assertThat(each.getAutoCommit(), is(autoCommit)); + } + } + + @Test + // TODO 缺少断言,做柔性事务时补充 + public void assertCommit() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + actual.setAutoCommit(false); + actual.createStatement().executeQuery("SELECT `user_id` FROM `t_order` WHERE `status` = 'init'"); + actual.commit(); + } + } + + @Test + // TODO 缺少断言,做柔性事务时补充 + public void assertRollback() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + actual.setAutoCommit(false); + actual.createStatement().executeQuery("SELECT `user_id` FROM `t_order` WHERE `status` = 'init'"); + actual.rollback(); + } + } + + @Test + public void assertClose() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + actual.createStatement().executeQuery("SELECT `user_id` FROM `t_order` WHERE `status` = 'init'"); + assertClose(actual, false); + actual.close(); + assertClose(actual, true); + } + } + + private void assertClose(final ShardingConnection actual, final boolean closed) throws SQLException { + assertThat(actual.isClosed(), is(closed)); + assertThat(actual.getConnections().size(), is(10)); + for (Connection each : actual.getConnections()) { + assertThat(each.isClosed(), is(closed)); + } + } + + @Test + public void assertSetReadOnly() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + assertTrue(actual.isReadOnly()); + actual.setReadOnly(false); + actual.createStatement().executeQuery("SELECT `user_id` FROM `t_order` WHERE `status` = 'init'"); + assertReadOnly(actual, false); + actual.setReadOnly(true); + assertReadOnly(actual, true); + } + } + + private void assertReadOnly(final ShardingConnection actual, final boolean readOnly) throws SQLException { + assertThat(actual.isReadOnly(), is(readOnly)); + assertThat(actual.getConnections().size(), is(10)); + for (Connection each : actual.getConnections()) { + // H2数据库未实现setReadOnly方法 + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + assertFalse(each.isReadOnly()); + } else { + assertThat(each.isReadOnly(), is(readOnly)); + } + } + } + + @Test + public void assertSetTransactionIsolation() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + assertThat(actual.getTransactionIsolation(), is(Connection.TRANSACTION_READ_UNCOMMITTED)); + actual.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + actual.createStatement().executeQuery("SELECT `user_id` FROM `t_order` WHERE `status` = 'init'"); + assertTransactionIsolation(actual, Connection.TRANSACTION_SERIALIZABLE); + actual.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + assertTransactionIsolation(actual, Connection.TRANSACTION_READ_COMMITTED); + } + } + + private void assertTransactionIsolation(final ShardingConnection actual, final int transactionIsolation) throws SQLException { + assertThat(actual.getTransactionIsolation(), is(transactionIsolation)); + assertThat(actual.getConnections().size(), is(10)); + for (Connection each : actual.getConnections()) { + assertThat(each.getTransactionIsolation(), is(transactionIsolation)); + } + } + + @Test + public void assertGetWarnings() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + assertNull(actual.getWarnings()); + } + } + + @Test + public void assertClearWarnings() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + actual.clearWarnings(); + } + } + + @Test + public void assertGetHoldability() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + assertThat(actual.getHoldability(), is(ResultSet.CLOSE_CURSORS_AT_COMMIT)); + } + } + + @Test + public void assertSetHoldability() throws SQLException { + try (ShardingConnection actual = shardingDataSource.getConnection()) { + actual.setHoldability(ResultSet.CONCUR_READ_ONLY); + assertThat(actual.getHoldability(), is(ResultSet.CLOSE_CURSORS_AT_COMMIT)); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/DataSourceAdapterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/DataSourceAdapterTest.java new file mode 100644 index 0000000000000..ac5d024caba25 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/DataSourceAdapterTest.java @@ -0,0 +1,96 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.sql.SQLException; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; + +public final class DataSourceAdapterTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test + public void assertUnwrapSuccess() throws SQLException { + assertThat(shardingDataSource.unwrap(Object.class), is((Object) shardingDataSource)); + } + + @Test(expected = SQLException.class) + public void assertUnwrapFaliure() throws SQLException { + shardingDataSource.unwrap(String.class); + } + + @Test + public void assertIsWrapperFor() throws SQLException { + assertTrue(shardingDataSource.isWrapperFor(Object.class)); + } + + @Test + public void assertIsNotWrapperFor() throws SQLException { + assertFalse(shardingDataSource.isWrapperFor(String.class)); + } + + @Test + public void assertRecordMethodInvocationSuccess() throws SQLException { + List list = mock(List.class); + when(list.isEmpty()).thenReturn(true); + shardingDataSource.recordMethodInvocation(List.class, "isEmpty", new Class[] {}, new Object[] {}); + shardingDataSource.replayMethodsInvovation(list); + verify(list).isEmpty(); + } + + @Test(expected = ShardingJdbcException.class) + public void assertRecordMethodInvocationFaliure() throws SQLException { + shardingDataSource.recordMethodInvocation(String.class, "none", new Class[] {}, new Object[] {}); + } + + @Test + public void assertSetLogWriter() throws SQLException { + assertThat(shardingDataSource.getLogWriter(), instanceOf(PrintWriter.class)); + shardingDataSource.setLogWriter(null); + assertNull(shardingDataSource.getLogWriter()); + } + + @Test + public void assertGetParentLogger() throws SQLException { + assertThat(shardingDataSource.getParentLogger().getName(), is(Logger.GLOBAL_LOGGER_NAME)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/PreparedStatementAdapterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/PreparedStatementAdapterTest.java new file mode 100644 index 0000000000000..2963599e3bcaf --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/PreparedStatementAdapterTest.java @@ -0,0 +1,287 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.Serializable; +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.Clob; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingPreparedStatement; +import com.mysql.jdbc.Blob; + +public final class PreparedStatementAdapterTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection shardingConnection; + + private PreparedStatement actual; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + shardingConnection = shardingDataSource.getConnection(); + actual = shardingConnection.prepareStatement("SELECT user_id AS `uid` FROM `t_order` WHERE `status` IN (? ,? ,? ,? ,?)"); + } + + @After + public void close() throws SQLException { + actual.close(); + shardingConnection.close(); + } + + @Test + public void assertSetNull() throws SQLException { + actual.setNull(1, Types.VARCHAR); + actual.setNull(1, Types.VARCHAR, ""); + assertParameter(actual, 1, null); + assertParameter(actual, 2, null); + } + + @Test + public void assertSetBoolean() throws SQLException { + actual.setBoolean(1, true); + assertParameter(actual, 1, true); + } + + @Test + public void assertSetByte() throws SQLException { + actual.setByte(1, (byte) 0); + assertParameter(actual, 1, (byte) 0); + } + + @Test + public void assertSetShort() throws SQLException { + actual.setShort(1, (short) 0); + assertParameter(actual, 1, (short) 0); + } + + @Test + public void assertSetInt() throws SQLException { + actual.setInt(1, 0); + assertParameter(actual, 1, 0); + } + + @Test + public void assertSetLong() throws SQLException { + actual.setLong(1, 0L); + assertParameter(actual, 1, 0L); + } + + @Test + public void assertSetFloat() throws SQLException { + actual.setFloat(1, 0F); + assertParameter(actual, 1, 0F); + } + + @Test + public void assertSetDouble() throws SQLException { + actual.setDouble(1, 0D); + assertParameter(actual, 1, 0D); + } + + @Test + public void assertSetString() throws SQLException { + actual.setString(1, "0"); + assertParameter(actual, 1, "0"); + } + + @Test + public void assertSetBigDecimal() throws SQLException { + actual.setBigDecimal(1, BigDecimal.ZERO); + assertParameter(actual, 1, BigDecimal.ZERO); + } + + @Test + public void assertSetDate() throws SQLException { + Date now = new Date(0L); + actual.setDate(1, now); + actual.setDate(2, now, Calendar.getInstance()); + assertParameter(actual, 1, now); + assertParameter(actual, 2, now); + } + + @Test + public void assertSetTime() throws SQLException { + Time now = new Time(0L); + actual.setTime(1, now); + actual.setTime(2, now, Calendar.getInstance()); + assertParameter(actual, 1, now); + assertParameter(actual, 2, now); + } + + @Test + public void assertSetTimestamp() throws SQLException { + Timestamp now = new Timestamp(0L); + actual.setTimestamp(1, now); + actual.setTimestamp(2, now, Calendar.getInstance()); + assertParameter(actual, 1, now); + assertParameter(actual, 2, now); + } + + @Test + public void assertSetBytes() throws SQLException { + actual.setBytes(1, new byte[] {}); + assertParameter(actual, 1, new byte[] {}); + } + + @Test + public void assertSetBlob() throws SQLException, IOException { + try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) { + actual.setBlob(1, (Blob) null); + actual.setBlob(2, inputStream); + actual.setBlob(3, inputStream, 100L); + assertParameter(actual, 1, null); + assertParameter(actual, 2, inputStream); + assertParameter(actual, 3, inputStream); + } + } + + @Test + public void assertSetClob() throws SQLException { + Reader reader = new SerializableStringReader(); + actual.setClob(1, (Clob) null); + actual.setClob(2, reader); + actual.setClob(3, reader, 100L); + assertParameter(actual, 1, null); + assertParameter(actual, 2, reader); + assertParameter(actual, 3, reader); + } + + @Test + public void assertSetAsciiStream() throws SQLException, IOException { + try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) { + actual.setAsciiStream(1, inputStream); + actual.setAsciiStream(2, inputStream, 100); + actual.setAsciiStream(3, inputStream, 100L); + assertParameter(actual, 1, inputStream); + assertParameter(actual, 2, inputStream); + assertParameter(actual, 3, inputStream); + } + } + + @SuppressWarnings("deprecation") + @Test + public void assertSetUnicodeStream() throws SQLException, IOException { + try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) { + actual.setUnicodeStream(1, inputStream, 100); + assertParameter(actual, 1, inputStream); + } + } + + @Test + public void assertSetBinaryStream() throws SQLException, IOException { + try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) { + actual.setBinaryStream(1, inputStream); + actual.setBinaryStream(2, inputStream, 100); + actual.setBinaryStream(3, inputStream, 100L); + assertParameter(actual, 1, inputStream); + assertParameter(actual, 2, inputStream); + assertParameter(actual, 3, inputStream); + } + } + + @Test + public void assertSetCharacterStream() throws SQLException { + Reader reader = new SerializableStringReader(); + actual.setCharacterStream(1, reader); + actual.setCharacterStream(2, reader, 100); + actual.setCharacterStream(3, reader, 100L); + assertParameter(actual, 1, reader); + assertParameter(actual, 2, reader); + assertParameter(actual, 3, reader); + } + + @Test + public void assertSetURL() throws SQLException { + actual.setURL(1, null); + assertParameter(actual, 1, null); + } + + @Test + public void assertSetSQLxml() throws SQLException { + actual.setSQLXML(1, null); + assertParameter(actual, 1, null); + } + + @Test + public void assertSetRef() throws SQLException { + actual.setRef(1, null); + assertParameter(actual, 1, null); + } + + @Test + public void assertSetObject() throws SQLException { + Object obj = "value"; + actual.setObject(1, obj); + actual.setObject(2, obj, 0); + actual.setObject(5, obj, 0, 0); + assertParameter(actual, 1, obj); + assertParameter(actual, 2, obj); + assertParameter(actual, 3, null); + assertParameter(actual, 4, null); + assertParameter(actual, 5, obj); + } + + @Test + public void assertClearParameters() throws SQLException { + Object obj = new Object(); + actual.setObject(1, obj); + actual.setObject(2, obj, 0); + actual.setObject(5, obj, 0, 0); + assertThat(((ShardingPreparedStatement) actual).getParameters().size(), is(5)); + actual.clearParameters(); + assertTrue(((ShardingPreparedStatement) actual).getParameters().isEmpty()); + } + + private void assertParameter(final PreparedStatement actual, final int index, final Object parameter) { + assertThat(((ShardingPreparedStatement) actual).getParameters().get(index - 1), is(parameter)); + } + + private static class SerializableStringReader extends StringReader implements Serializable { + + private static final long serialVersionUID = 5054305161835171548L; + + SerializableStringReader() { + super("value"); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ResultSetAdapterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ResultSetAdapterTest.java new file mode 100644 index 0000000000000..008476f094dec --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ResultSetAdapterTest.java @@ -0,0 +1,152 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.AbstractDBUnitTest; +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.AbstractShardingResultSet; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; + +public final class ResultSetAdapterTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection shardingConnection; + + private Statement statement; + + private ResultSet actual; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + shardingConnection = shardingDataSource.getConnection(); + statement = shardingConnection.createStatement(); + actual = statement.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + } + + @After + public void close() throws SQLException { + actual.close(); + statement.close(); + shardingConnection.close(); + } + + @Test + public void assertColse() throws SQLException { + actual.close(); + assertClose((AbstractShardingResultSet) actual); + } + + private void assertClose(final AbstractShardingResultSet actual) throws SQLException { + assertTrue(actual.isClosed()); + assertThat(actual.getResultSets().size(), is(10)); + for (ResultSet each : actual.getResultSets()) { + assertTrue(each.isClosed()); + } + } + + @Test + public void assertWasNull() throws SQLException { + assertFalse(actual.wasNull()); + } + + @Test + public void assertSetFetchDirection() throws SQLException { + assertThat(actual.getFetchDirection(), is(ResultSet.FETCH_FORWARD)); + try { + actual.setFetchDirection(ResultSet.FETCH_REVERSE); + } catch (final SQLException ignore) { + } + assertFetchDirection((AbstractShardingResultSet) actual, ResultSet.FETCH_REVERSE); + } + + private void assertFetchDirection(final AbstractShardingResultSet actual, final int fetchDirection) throws SQLException { + // H2数据库未实现getFetchDirection方法 + assertThat(actual.getFetchDirection(), is(DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE ? ResultSet.FETCH_FORWARD : fetchDirection)); + assertThat(actual.getResultSets().size(), is(10)); + for (ResultSet each : actual.getResultSets()) { + assertThat(each.getFetchDirection(), is(DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE ? ResultSet.FETCH_FORWARD : fetchDirection)); + } + } + + @Test + public void assertSetFetchSize() throws SQLException { + assertThat(actual.getFetchSize(), is(0)); + actual.setFetchSize(100); + assertFetchSize((AbstractShardingResultSet) actual, 100); + } + + private void assertFetchSize(final AbstractShardingResultSet actual, final int fetchSize) throws SQLException { + // H2数据库未实现getFetchSize方法 + assertThat(actual.getFetchSize(), is(DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE ? 0 : fetchSize)); + assertThat(actual.getResultSets().size(), is(10)); + for (ResultSet each : actual.getResultSets()) { + assertThat(each.getFetchSize(), is(DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE ? 0 : fetchSize)); + } + } + + @Test + public void assertGetType() throws SQLException { + assertThat(actual.getType(), is(ResultSet.TYPE_FORWARD_ONLY)); + } + + @Test + public void assertGetConcurrency() throws SQLException { + assertThat(actual.getConcurrency(), is(ResultSet.CONCUR_READ_ONLY)); + } + + @Test + public void assertGetStatement() throws SQLException { + assertNotNull(actual.getStatement()); + } + + @Test + public void assertClearWarnings() throws SQLException { + assertNull(actual.getWarnings()); + actual.clearWarnings(); + assertNull(actual.getWarnings()); + } + + @Test + public void assertGetMetaData() throws SQLException { + assertNotNull(actual.getMetaData()); + } + + @Test + public void assertFindColumn() throws SQLException { + assertThat(actual.findColumn("uid"), is(1)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ResultSetGetterAdapterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ResultSetGetterAdapterTest.java new file mode 100644 index 0000000000000..dfc49397cf2b9 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/ResultSetGetterAdapterTest.java @@ -0,0 +1,428 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Calendar; +import java.util.Collections; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.AbstractDBUnitTest; +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; + +public final class ResultSetGetterAdapterTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection shardingConnection; + + private Statement statement; + + private ResultSet actual; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + shardingConnection = shardingDataSource.getConnection(); + statement = shardingConnection.createStatement(); + actual = statement.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init' ORDER BY `uid`"); + actual.next(); + } + + @After + public void close() throws SQLException { + actual.close(); + statement.close(); + shardingConnection.close(); + } + + @Test + public void assertGetBooleanForColumnIndex() throws SQLException { + assertTrue(actual.getBoolean(1)); + } + + @Test + public void assertGetBooleanForColumnLabel() throws SQLException { + assertTrue(actual.getBoolean("uid")); + } + + @Test + public void assertGetByteForColumnIndex() throws SQLException { + assertThat(actual.getByte(1), is((byte) 10)); + } + + @Test + public void assertGetByteForColumnLabel() throws SQLException { + assertThat(actual.getByte("uid"), is((byte) 10)); + } + + @Test + public void assertGetShortForColumnIndex() throws SQLException { + assertThat(actual.getShort(1), is((short) 10)); + } + + @Test + public void assertGetShortForColumnLabel() throws SQLException { + assertThat(actual.getShort("uid"), is((short) 10)); + } + + @Test + public void assertGetIntForColumnIndex() throws SQLException { + assertThat(actual.getInt(1), is(10)); + } + + @Test + public void assertGetIntForColumnLabel() throws SQLException { + assertThat(actual.getInt("uid"), is(10)); + } + + @Test + public void assertGetLongForColumnIndex() throws SQLException { + assertThat(actual.getLong(1), is(10L)); + } + + @Test + public void assertGetLongForColumnLabel() throws SQLException { + assertThat(actual.getLong("uid"), is(10L)); + } + + @Test + public void assertGetFloatForColumnIndex() throws SQLException { + assertThat(actual.getFloat(1), is(10F)); + } + + @Test + public void assertGetFloatForColumnLabel() throws SQLException { + assertThat(actual.getFloat("uid"), is(10F)); + } + + @Test + public void assertGetDoubleForColumnIndex() throws SQLException { + assertThat(actual.getDouble(1), is(10D)); + } + + @Test + public void assertGetDoubleForColumnLabel() throws SQLException { + assertThat(actual.getDouble("uid"), is(10D)); + } + + @Test + public void assertGetStringForColumnIndex() throws SQLException { + assertThat(actual.getString(1), is("10")); + } + + @Test + public void assertGetStringForColumnLabel() throws SQLException { + assertThat(actual.getString("uid"), is("10")); + } + + @Test + public void assertGetBigDecimalForColumnIndex() throws SQLException { + assertThat(actual.getBigDecimal(1), is(new BigDecimal("10"))); + } + + @Test + public void assertGetBigDecimalForColumnLabel() throws SQLException { + assertThat(actual.getBigDecimal("uid"), is(new BigDecimal("10"))); + } + + @SuppressWarnings("deprecation") + @Test + public void assertGetBigDecimalColumnIndexWithScale() throws SQLException { + assertThat(actual.getBigDecimal(1, 2), is(new BigDecimal("10"))); + } + + @SuppressWarnings("deprecation") + @Test + public void assertGetBigDecimalColumnLabelWithScale() throws SQLException { + assertThat(actual.getBigDecimal("uid", 2), is(new BigDecimal("10"))); + } + + @Test + public void assertGetBytesForColumnIndex() throws SQLException { + assertTrue(actual.getBytes(1).length > 0); + } + + @Test + public void assertGetBytesForColumnLabel() throws SQLException { + assertTrue(actual.getBytes("uid").length > 0); + } + + @Test(expected = SQLException.class) + public void assertGetDateForColumnIndex() throws SQLException { + actual.getDate(1); + } + + @Test(expected = SQLException.class) + public void assertGetDateForColumnLabel() throws SQLException { + actual.getDate("uid"); + } + + @Test(expected = SQLException.class) + public void assertGetDateColumnIndexWithCalendar() throws SQLException { + actual.getDate(1, Calendar.getInstance()); + } + + @Test(expected = SQLException.class) + public void assertGetDateColumnLabelWithCalendar() throws SQLException { + actual.getDate("uid", Calendar.getInstance()); + } + + @Test(expected = SQLException.class) + public void assertGetTimeForColumnIndex() throws SQLException { + actual.getTime(1); + } + + @Test(expected = SQLException.class) + public void assertGetTimeForColumnLabel() throws SQLException { + actual.getTime("uid"); + } + + @Test(expected = SQLException.class) + public void assertGetTimeColumnIndexWithCalendar() throws SQLException { + actual.getTime(1, Calendar.getInstance()); + } + + @Test(expected = SQLException.class) + public void assertGetTimeColumnLabelWithCalendar() throws SQLException { + actual.getTime("uid", Calendar.getInstance()); + } + + @Test + public void assertGetTimestampForColumnIndex() throws SQLException { + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getTimestamp(1); + } catch (final SQLException ex) { + assertThat(ex.getCause(), instanceOf(IllegalArgumentException.class)); + } + } else { + assertTrue(actual.getTimestamp(1).getTime() > 0); + } + } + + @Test + public void assertGetTimestampForColumnLabel() throws SQLException { + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getTimestamp("uid"); + } catch (final SQLException ex) { + assertThat(ex.getCause(), instanceOf(IllegalArgumentException.class)); + } + } else { + assertTrue(actual.getTimestamp("uid").getTime() > 0); + } + } + + @Test + public void assertGetTimestampColumnIndexWithCalendar() throws SQLException { + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getTimestamp(1, Calendar.getInstance()); + } catch (final SQLException ex) { + assertThat(ex.getCause(), instanceOf(IllegalArgumentException.class)); + } + } else { + assertTrue(actual.getTimestamp(1, Calendar.getInstance()).getTime() > 0); + } + } + + @Test + public void assertGetTimestampColumnLabelWithCalendar() throws SQLException { + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getTimestamp("uid", Calendar.getInstance()); + } catch (final SQLException ex) { + assertThat(ex.getCause(), instanceOf(IllegalArgumentException.class)); + } + } else { + assertTrue(actual.getTimestamp("uid", Calendar.getInstance()).getTime() > 0); + } + } + + @Test + public void assertGetAsciiStreamForColumnIndex() throws SQLException, IOException { + byte[] b = new byte[1]; + actual.getAsciiStream(1).read(b); + assertThat(new String(b), is("1")); + } + + @Test + public void assertGetAsciiStreamForColumnLabel() throws SQLException, IOException { + byte[] b = new byte[1]; + actual.getAsciiStream("uid").read(b); + assertThat(new String(b), is("1")); + } + + @SuppressWarnings("deprecation") + @Test + public void assertGetUnicodeStreamForColumnIndex() throws SQLException, IOException { + byte[] b = new byte[1]; + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getUnicodeStream(1).read(b); + } catch (final SQLException ignore) { + } + } else { + actual.getUnicodeStream(1).read(b); + assertThat(new String(b), is("1")); + } + } + + @SuppressWarnings("deprecation") + @Test + public void assertGetUnicodeStreamForColumnLabel() throws SQLException, IOException { + byte[] b = new byte[1]; + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getUnicodeStream("uid").read(b); + } catch (final SQLException ignore) { + } + } else { + actual.getUnicodeStream("uid").read(b); + assertThat(new String(b), is("1")); + } + } + + @Test + public void assertGetBinaryStreamForColumnIndex() throws SQLException, IOException { + assertTrue(actual.getBinaryStream(1).read() != -1); + } + + @Test + public void assertGetBinaryStreamForColumnLabel() throws SQLException, IOException { + assertTrue(actual.getBinaryStream("uid").read() != -1); + } + + @Test + public void assertGetCharacterStreamForColumnIndex() throws SQLException, IOException { + char[] c = new char[1]; + actual.getCharacterStream(1).read(c); + assertThat(c[0], is('1')); + } + + @Test + public void assertGetCharacterStreamForColumnLabel() throws SQLException, IOException { + char[] c = new char[1]; + actual.getCharacterStream("uid").read(c); + assertThat(c[0], is('1')); + } + + @Test + public void assertGetBlobForColumnIndex() throws SQLException { + assertTrue(actual.getBlob(1).length() > 0); + } + + @Test + public void assertGetBlobForColumnLabel() throws SQLException { + assertTrue(actual.getBlob("uid").length() > 0); + } + + @Test + public void assertGetClobForColumnIndex() throws SQLException { + assertThat(actual.getClob(1).getSubString(1, 2), is("10")); + } + + @Test + public void assertGetClobForColumnLabel() throws SQLException { + assertThat(actual.getClob("uid").getSubString(1, 2), is("10")); + } + + @Test(expected = SQLException.class) + public void assertGetURLForColumnIndex() throws SQLException { + actual.getURL(1); + } + + @Test(expected = SQLException.class) + public void assertGetURLForColumnLabel() throws SQLException { + actual.getURL("uid"); + } + + @Test + public void assertGetSQLxmlForColumnIndex() throws SQLException { + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getSQLXML(1); + } catch (final SQLException ignore) { + } + } else { + assertThat(actual.getSQLXML(1).getString(), is("10")); + } + } + + @Test + public void assertGetSQLxmlForColumnLabel() throws SQLException { + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getSQLXML("uid"); + } catch (final SQLException ignore) { + } + } else { + assertThat(actual.getSQLXML("uid").getString(), is("10")); + } + } + + @Test + public void assertGetObjectForColumnIndex() throws SQLException { + assertThat(actual.getObject(1).toString(), is("10")); + } + + @Test + public void assertGetObjectForColumnLabel() throws SQLException { + assertThat(actual.getObject("uid").toString(), is("10")); + } + + @Test + public void assertGetObjectForColumnIndexWithMap() throws SQLException { + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getObject("1", Collections.>emptyMap()); + } catch (final SQLException ignore) { + } + } else { + assertThat(actual.getObject("uid", Collections.>emptyMap()).toString(), is("10")); + } + } + + @Test + public void assertGetObjectForColumnLabelWithMap() throws SQLException { + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + try { + actual.getObject("uid", Collections.>emptyMap()); + } catch (final SQLException ignore) { + } + } else { + assertThat(actual.getObject("uid", Collections.>emptyMap()).toString(), is("10")); + } + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/StatementAdapterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/StatementAdapterTest.java new file mode 100644 index 0000000000000..d8beda4f59b37 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/adapter/StatementAdapterTest.java @@ -0,0 +1,153 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.adapter; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.AbstractDBUnitTest; +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingStatement; + +public final class StatementAdapterTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection shardingConnection; + + private Statement actual; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + shardingConnection = shardingDataSource.getConnection(); + actual = shardingConnection.createStatement(); + } + + @After + public void close() throws SQLException { + actual.close(); + shardingConnection.close(); + } + + @Test + public void assertColse() throws SQLException { + actual.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + actual.close(); + assertTrue(actual.isClosed()); + assertTrue(((ShardingStatement) actual).getRoutedStatements().isEmpty()); + } + + @Test + public void assertSetPoolable() throws SQLException { + actual.setPoolable(true); + actual.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + assertPoolable((ShardingStatement) actual, true); + actual.setPoolable(false); + assertPoolable((ShardingStatement) actual, false); + } + + private void assertPoolable(final ShardingStatement actual, final boolean poolable) throws SQLException { + assertThat(actual.isPoolable(), is(poolable)); + assertThat(actual.getRoutedStatements().size(), is(10)); + for (Statement each : actual.getRoutedStatements()) { + // H2数据库未实现setPoolable方法 + if (DatabaseType.H2 == AbstractDBUnitTest.CURRENT_DB_TYPE) { + assertFalse(each.isPoolable()); + } else { + assertThat(each.isPoolable(), is(poolable)); + } + } + } + + @Test + public void assertSetFetchSize() throws SQLException { + actual.setFetchSize(10); + actual.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + assertFetchSize((ShardingStatement) actual, 10); + actual.setFetchSize(100); + assertFetchSize((ShardingStatement) actual, 100); + } + + private void assertFetchSize(final ShardingStatement actual, final int fetchSize) throws SQLException { + assertThat(actual.getFetchSize(), is(fetchSize)); + assertThat(actual.getRoutedStatements().size(), is(10)); + for (Statement each : actual.getRoutedStatements()) { + assertThat(each.getFetchSize(), is(fetchSize)); + } + } + + @Test + public void assertSetEscapeProcessing() throws SQLException { + actual.setEscapeProcessing(true); + actual.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + actual.setEscapeProcessing(false); + } + + @Test + public void assertCancel() throws SQLException { + actual.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + actual.cancel(); + } + + @Test + public void assertSetCursorName() throws SQLException { + actual.setCursorName("cursorName"); + actual.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + actual.setCursorName("cursorName"); + } + + @Test + public void assertGetUpdateCount() throws SQLException { + actual.execute("DELETE FROM `t_order` WHERE `status` = 'init'"); + assertThat(actual.getUpdateCount(), is(40)); + } + + @Test + public void assertGetWarnings() throws SQLException { + assertNull(actual.getWarnings()); + } + + @Test + public void assertClearWarnings() throws SQLException { + actual.clearWarnings(); + } + + @Test + public void assertGetMoreResults() throws SQLException { + assertFalse(actual.getMoreResults()); + } + + @Test + public void assertGetMoreResultsWithCurrent() throws SQLException { + assertFalse(actual.getMoreResults(Statement.KEEP_CURRENT_RESULT)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/ResultSetUpdaterAdapterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/ResultSetUpdaterAdapterTest.java new file mode 100644 index 0000000000000..478c6408e9605 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/ResultSetUpdaterAdapterTest.java @@ -0,0 +1,478 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ResultSet; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; + +public final class ResultSetUpdaterAdapterTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection shardingConnection; + + private Statement statement; + + private ResultSet actual; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + shardingConnection = shardingDataSource.getConnection(); + statement = shardingConnection.createStatement(); + actual = statement.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + } + + @After + public void close() throws SQLException { + actual.close(); + statement.close(); + shardingConnection.close(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNullForColumnIndex() throws SQLException { + actual.updateNull(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNullForColumnLabel() throws SQLException { + actual.updateNull("label"); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBooleanForColumnIndex() throws SQLException { + actual.updateBoolean(1, false); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBooleanForColumnLabel() throws SQLException { + actual.updateBoolean("label", false); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateByteForColumnIndex() throws SQLException { + actual.updateByte(1, (byte) 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateByteForColumnLabel() throws SQLException { + actual.updateByte("label", (byte) 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateShortForColumnIndex() throws SQLException { + actual.updateShort(1, (short) 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateShortForColumnLabel() throws SQLException { + actual.updateShort("label", (short) 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateIntForColumnIndex() throws SQLException { + actual.updateInt(1, 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateIntForColumnLabel() throws SQLException { + actual.updateInt("label", 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateLongForColumnIndex() throws SQLException { + actual.updateLong(1, 1L); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateLongForColumnLabel() throws SQLException { + actual.updateLong("label", 1L); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateFloatForColumnIndex() throws SQLException { + actual.updateFloat(1, 1F); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateFloatForColumnLabel() throws SQLException { + actual.updateFloat("label", 1F); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateDoubleForColumnIndex() throws SQLException { + actual.updateDouble(1, 1D); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateDoubleForColumnLabel() throws SQLException { + actual.updateDouble("label", 1D); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBigDecimalForColumnIndex() throws SQLException { + actual.updateBigDecimal(1, new BigDecimal("1")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBigDecimalForColumnLabel() throws SQLException { + actual.updateBigDecimal("label", new BigDecimal("1")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateStringForColumnIndex() throws SQLException { + actual.updateString(1, "1"); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateStringForColumnLabel() throws SQLException { + actual.updateString("label", "1"); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNStringForColumnIndex() throws SQLException { + actual.updateNString(1, ""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNStringForColumnLabel() throws SQLException { + actual.updateNString("label", ""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBytesForColumnIndex() throws SQLException { + actual.updateBytes(1, new byte[] {}); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBytesForColumnLabel() throws SQLException { + actual.updateBytes("label", new byte[] {}); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateDateForColumnIndex() throws SQLException { + actual.updateDate(1, new Date(0L)); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateDateForColumnLabel() throws SQLException { + actual.updateDate("label", new Date(0L)); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateTimeForColumnIndex() throws SQLException { + actual.updateTime(1, new Time(0L)); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateTimeForColumnLabel() throws SQLException { + actual.updateTime("label", new Time(0L)); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateTimestampForColumnIndex() throws SQLException { + actual.updateTimestamp(1, new Timestamp(0L)); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateTimestampForColumnLabel() throws SQLException { + actual.updateTimestamp("label", new Timestamp(0L)); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateAsciiStreamForColumnIndex() throws SQLException { + actual.updateAsciiStream(1, System.in); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateAsciiStreamForColumnLabel() throws SQLException { + actual.updateAsciiStream("label", System.in); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateAsciiStreamForColumnIndexWithIntegerLength() throws SQLException { + actual.updateAsciiStream(1, System.in, 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateAsciiStreamForColumnLabelWithIntegerLength() throws SQLException { + actual.updateAsciiStream("label", System.in, 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateAsciiStreamForColumnIndexWithLongLength() throws SQLException { + actual.updateAsciiStream(1, System.in, 1L); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateAsciiStreamForColumnLabelWithLongLength() throws SQLException { + actual.updateAsciiStream("label", System.in, 1L); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBinaryStreamForColumnIndex() throws SQLException { + actual.updateBinaryStream(1, System.in); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBinaryStreamForColumnLabel() throws SQLException { + actual.updateBinaryStream("label", System.in); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBinaryStreamForColumnIndexWithIntegerLength() throws SQLException { + actual.updateBinaryStream(1, System.in, 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBinaryStreamForColumnLabelWithIntegerLength() throws SQLException { + actual.updateBinaryStream("label", System.in, 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBinaryStreamForColumnIndexWithLongLength() throws SQLException { + actual.updateBinaryStream(1, System.in, 1L); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBinaryStreamForColumnLabelWithLongLength() throws SQLException { + actual.updateBinaryStream("label", System.in, 1L); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateCharacterStreamForColumnIndex() throws SQLException { + actual.updateCharacterStream(1, new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateCharacterStreamForColumnLabel() throws SQLException { + actual.updateCharacterStream("label", new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateCharacterStreamForColumnIndexWithIntegerLength() throws SQLException { + actual.updateCharacterStream(1, new StringReader(""), 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateCharacterStreamForColumnLabelWithIntegerLength() throws SQLException { + actual.updateCharacterStream("label", new StringReader(""), 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateCharacterStreamForColumnIndexWithLongLength() throws SQLException { + actual.updateCharacterStream(1, new StringReader(""), 1L); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateCharacterStreamForColumnLabelWithLongLength() throws SQLException { + actual.updateCharacterStream("label", new StringReader(""), 1L); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNCharacterStreamForColumnIndex() throws SQLException { + actual.updateNCharacterStream(1, new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNCharacterStreamForColumnLabel() throws SQLException { + actual.updateNCharacterStream("label", new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNCharacterStreamForColumnIndexWithLength() throws SQLException { + actual.updateNCharacterStream(1, new StringReader(""), 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNCharacterStreamForColumnLabelWithLength() throws SQLException { + actual.updateNCharacterStream("label", new StringReader(""), 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateObjectForColumnIndex() throws SQLException { + actual.updateObject(1, new Object()); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateObjectForColumnLabel() throws SQLException { + actual.updateObject("label", new Object()); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateObjectForColumnIndexWithScaleOrLength() throws SQLException { + actual.updateObject(1, new Object(), 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateObjectForColumnLabelWithScaleOrLength() throws SQLException { + actual.updateObject("label", new Object(), 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateRefForColumnIndex() throws SQLException { + actual.updateRef(1, null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateRefForColumnLabel() throws SQLException { + actual.updateRef("label", null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBlobForColumnIndex() throws SQLException { + actual.updateBlob(1, (Blob) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBlobForColumnLabel() throws SQLException { + actual.updateBlob("label", (Blob) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBlobForColumnIndexWithInputStream() throws SQLException { + actual.updateBlob(1, System.in); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBlobForColumnLabelWithInputStream() throws SQLException { + actual.updateBlob("label", System.in); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBlobForColumnIndexWithInputStreamAndLength() throws SQLException { + actual.updateBlob(1, System.in, 100); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateBlobForColumnLabelWithInputStreamAndLength() throws SQLException { + actual.updateBlob("label", System.in, 100); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateClobForColumnIndex() throws SQLException { + actual.updateClob(1, (Clob) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateClobForColumnLabel() throws SQLException { + actual.updateClob("label", (Clob) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateClobForColumnIndexWithInputStream() throws SQLException { + actual.updateClob(1, new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateClobForColumnLabelWithInputStream() throws SQLException { + actual.updateClob("label", new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateClobForColumnIndexWithInputStreamAndLength() throws SQLException { + actual.updateClob(1, new StringReader(""), 100); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateClobForColumnLabelWithInputStreamAndLength() throws SQLException { + actual.updateClob("label", new StringReader(""), 100); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNClobForColumnIndex() throws SQLException { + actual.updateNClob(1, (NClob) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNClobForColumnLabel() throws SQLException { + actual.updateNClob("label", (NClob) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNClobForColumnIndexWithInputStream() throws SQLException { + actual.updateNClob(1, new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNClobForColumnLabelWithInputStream() throws SQLException { + actual.updateNClob("label", new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNClobForColumnIndexWithInputStreamAndLength() throws SQLException { + actual.updateNClob(1, new StringReader(""), 100); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateNClobForColumnLabelWithInputStreamAndLength() throws SQLException { + actual.updateNClob("label", new StringReader(""), 100); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateArrayForColumnIndex() throws SQLException { + actual.updateArray(1, (Array) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateArrayForColumnLabel() throws SQLException { + actual.updateArray("label", (Array) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateRowIdForColumnIndex() throws SQLException { + actual.updateRowId(1, (RowId) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateRowIdForColumnLabel() throws SQLException { + actual.updateRowId("label", (RowId) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateSQLxmlForColumnIndex() throws SQLException { + actual.updateSQLXML(1, (SQLXML) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateSQLxmlForColumnLabel() throws SQLException { + actual.updateSQLXML("label", (SQLXML) null); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationConnectionTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationConnectionTest.java new file mode 100644 index 0000000000000..0a7ba60717110 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationConnectionTest.java @@ -0,0 +1,189 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Properties; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; + +public final class UnsupportedOperationConnectionTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection actual; + + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + actual = shardingDataSource.getConnection(); + } + + @After + public void close() throws SQLException { + actual.close(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertPrepareCall() throws SQLException { + actual.prepareCall(""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertPrepareCallWithResultSetTypeAndResultSetConcurrency() throws SQLException { + actual.prepareCall("", 0, 0); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertPrepareCallWithResultSetTypeAndResultSetConcurrencyAndResultSetHoldability() throws SQLException { + actual.prepareCall("", 0, 0, 0); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertNativeSQL() throws SQLException { + actual.nativeSQL(""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetSavepoint() throws SQLException { + actual.setSavepoint(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetSavepointWithName() throws SQLException { + actual.setSavepoint(""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertReleaseSavepoint() throws SQLException { + actual.releaseSavepoint(null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertRollback() throws SQLException { + actual.rollback(null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertAbort() throws SQLException { + actual.abort(null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetCatalog() throws SQLException { + actual.getCatalog(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetCatalog() throws SQLException { + actual.setCatalog(""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetSchema() throws SQLException { + actual.getSchema(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetSchema() throws SQLException { + actual.setSchema(""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetTypeMap() throws SQLException { + actual.getTypeMap(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetTypeMap() throws SQLException { + actual.setTypeMap(null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetNetworkTimeout() throws SQLException { + actual.getNetworkTimeout(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetNetworkTimeout() throws SQLException { + actual.setNetworkTimeout(null, 0); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertCreateClob() throws SQLException { + actual.createClob(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertCreateBlob() throws SQLException { + actual.createBlob(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertCreateNClob() throws SQLException { + actual.createNClob(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertCreateSQLxml() throws SQLException { + actual.createSQLXML(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertCreateArrayOf() throws SQLException { + actual.createArrayOf("", null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertCreateStruct() throws SQLException { + actual.createStruct("", null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertIsValid() throws SQLException { + actual.isValid(0); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetClientInfo() throws SQLException { + actual.getClientInfo(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetClientInfoWithName() throws SQLException { + actual.getClientInfo(""); + } + + @Test(expected = UnsupportedOperationException.class) + public void assertSetClientInfo() throws SQLException { + actual.setClientInfo("", ""); + } + + @Test(expected = UnsupportedOperationException.class) + public void assertSetClientInfoWithProperties() throws SQLException { + actual.setClientInfo(new Properties()); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationDataSourceTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationDataSourceTest.java new file mode 100644 index 0000000000000..9666ddb1b94f5 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationDataSourceTest.java @@ -0,0 +1,47 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; + +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; + +public final class UnsupportedOperationDataSourceTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetLoginTimeout() throws SQLException { + shardingDataSource.getLoginTimeout(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetLoginTimeout() throws SQLException { + shardingDataSource.setLoginTimeout(0); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationPreparedStatementTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationPreparedStatementTest.java new file mode 100644 index 0000000000000..bec6da1b29086 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationPreparedStatementTest.java @@ -0,0 +1,104 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.io.StringReader; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; + +public final class UnsupportedOperationPreparedStatementTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection shardingConnection; + + private PreparedStatement actual; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + shardingConnection = shardingDataSource.getConnection(); + actual = shardingConnection.prepareStatement("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + } + + @After + public void close() throws SQLException { + actual.close(); + shardingConnection.close(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetMetaData() throws SQLException { + actual.getMetaData(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetParameterMetaData() throws SQLException { + actual.getParameterMetaData(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetNString() throws SQLException { + actual.setNString(1, ""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetNClob() throws SQLException { + actual.setNClob(1, (NClob) null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetNClobForReader() throws SQLException { + actual.setNClob(1, new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetNClobForReaderAndLength() throws SQLException { + actual.setNClob(1, new StringReader(""), 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetNCharacterStream() throws SQLException { + actual.setNCharacterStream(1, new StringReader("")); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetNCharacterStreamWithLength() throws SQLException { + actual.setNCharacterStream(1, new StringReader(""), 1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetArray() throws SQLException { + actual.setArray(1, null); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetRowId() throws SQLException { + actual.setRowId(1, null); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationResultSetTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationResultSetTest.java new file mode 100644 index 0000000000000..852a67349d0c5 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationResultSetTest.java @@ -0,0 +1,248 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; + +public final class UnsupportedOperationResultSetTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection shardingConnection; + + private Statement statement; + + private ResultSet actual; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + shardingConnection = shardingDataSource.getConnection(); + statement = shardingConnection.createStatement(); + actual = statement.executeQuery("SELECT user_id AS `uid` FROM `t_order` WHERE `status` = 'init'"); + } + + @After + public void close() throws SQLException { + actual.close(); + statement.close(); + shardingConnection.close(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertPrevious() throws SQLException { + actual.previous(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertIsBeforeFirst() throws SQLException { + actual.isBeforeFirst(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertIsAfterLast() throws SQLException { + actual.isAfterLast(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertisFirst() throws SQLException { + actual.isFirst(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertIsLast() throws SQLException { + actual.isLast(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertBeforeFirst() throws SQLException { + actual.beforeFirst(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertAfterLast() throws SQLException { + actual.afterLast(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertFirst() throws SQLException { + actual.first(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertLast() throws SQLException { + actual.last(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertAbsolute() throws SQLException { + actual.absolute(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertRelative() throws SQLException { + actual.relative(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetRow() throws SQLException { + actual.getRow(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertInsertRow() throws SQLException { + actual.insertRow(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertUpdateRow() throws SQLException { + actual.updateRow(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertDeleteRow() throws SQLException { + actual.deleteRow(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertRefreshRow() throws SQLException { + actual.refreshRow(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertCancelRowUpdates() throws SQLException { + actual.cancelRowUpdates(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertMoveToInsertRow() throws SQLException { + actual.moveToInsertRow(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertMoveToCurrentRow() throws SQLException { + actual.moveToCurrentRow(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertRowInserted() throws SQLException { + actual.rowInserted(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertrowUpdateed() throws SQLException { + actual.rowUpdated(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertRowDeleted() throws SQLException { + actual.rowDeleted(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetCursorName() throws SQLException { + actual.getCursorName(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetHoldability() throws SQLException { + actual.getHoldability(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void getNStringForColumnIndex() throws SQLException { + actual.getNString(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void getNStringForColumnLabel() throws SQLException { + actual.getNString("label"); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetNClobForColumnIndex() throws SQLException { + actual.getNClob(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetNClobForColumnLabel() throws SQLException { + actual.getNClob("label"); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void getNCharacterStreamForColumnIndex() throws SQLException { + actual.getNCharacterStream(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void getNCharacterStreamForColumnLabel() throws SQLException { + actual.getNCharacterStream("label"); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetRefForColumnIndex() throws SQLException { + actual.getRef(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetRefForColumnLabel() throws SQLException { + actual.getRef("label"); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetArrayForColumnIndex() throws SQLException { + actual.getArray(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetArrayForColumnLabel() throws SQLException { + actual.getArray("label"); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetRowIdForColumnIndex() throws SQLException { + actual.getRowId(1); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetRowIdForColumnLabel() throws SQLException { + actual.getRowId("label"); + } + + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertObjectForColumnIndexWithType() throws SQLException { + actual.getObject(1, String.class); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertObjectForColumnLabelWithType() throws SQLException { + actual.getObject("label", String.class); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationStatementTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationStatementTest.java new file mode 100644 index 0000000000000..06cc7a73dd002 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/unsupported/UnsupportedOperationStatementTest.java @@ -0,0 +1,122 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.unsupported; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest; +import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSource; +import com.dangdang.ddframe.rdb.sharding.jdbc.ShardingConnection; + +public final class UnsupportedOperationStatementTest extends AbstractShardingDataBasesOnlyDBUnitTest { + + private ShardingDataSource shardingDataSource; + + private ShardingConnection shardingConnection; + + private Statement actual; + + @Before + public void init() throws SQLException { + shardingDataSource = getShardingDataSource(); + shardingConnection = shardingDataSource.getConnection(); + actual = shardingConnection.createStatement(); + } + + @After + public void close() throws SQLException { + actual.close(); + shardingConnection.close(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetMaxFieldSize() throws SQLException { + actual.getMaxFieldSize(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetMaxFieldSize() throws SQLException { + actual.setMaxFieldSize(0); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetMaxRows() throws SQLException { + actual.getMaxRows(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetMaxRows() throws SQLException { + actual.setMaxRows(0); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetQueryTimeout() throws SQLException { + actual.getQueryTimeout(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetQueryTimeout() throws SQLException { + actual.setQueryTimeout(0); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetFetchDirection() throws SQLException { + actual.getFetchDirection(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertSetFetchDirection() throws SQLException { + actual.setFetchDirection(0); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertGetGeneratedKeys() throws SQLException { + actual.getGeneratedKeys(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertAddBatch() throws SQLException { + actual.addBatch(""); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertClearBatch() throws SQLException { + actual.clearBatch(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertExecuteBatch() throws SQLException { + actual.executeBatch(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertCloseOnCompletion() throws SQLException { + actual.closeOnCompletion(); + } + + @Test(expected = SQLFeatureNotSupportedException.class) + public void assertIsCloseOnCompletion() throws SQLException { + actual.isCloseOnCompletion(); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/util/JdbcMethodInvocationTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/util/JdbcMethodInvocationTest.java new file mode 100644 index 0000000000000..b4ebfb6ce2422 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/jdbc/util/JdbcMethodInvocationTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.jdbc.util; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; + +public final class JdbcMethodInvocationTest { + + @Test + public void assertInvokeSuccess() throws NoSuchMethodException, SecurityException { + JdbcMethodInvocation actual = new JdbcMethodInvocation(String.class.getMethod("length"), new Object[] {}); + actual.invoke(""); + } + + @Test(expected = ShardingJdbcException.class) + public void assertInvokeFailure() throws NoSuchMethodException, SecurityException { + JdbcMethodInvocation actual = new JdbcMethodInvocation(String.class.getDeclaredMethod("indexOfSupplementary", int.class, int.class), new Object[] {1, 1}); + actual.invoke(""); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/AllMergerTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/AllMergerTest.java new file mode 100644 index 0000000000000..c29e965268835 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/AllMergerTest.java @@ -0,0 +1,48 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.AccumulationAggregationUnitTest; +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.AggregationResultSetTest; +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.AvgAggregationUnitTest; +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.ComparableAggregationUnitTest; +import com.dangdang.ddframe.rdb.sharding.merger.aggregation.ResultSetAggregationValueTest; +import com.dangdang.ddframe.rdb.sharding.merger.groupby.GroupByValueTest; +import com.dangdang.ddframe.rdb.sharding.merger.iterator.IteratorResultSetTest; +import com.dangdang.ddframe.rdb.sharding.merger.orderby.OrderByResultSetTest; +import com.dangdang.ddframe.rdb.sharding.merger.orderby.OrderByValueTest; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ResultSetUtilTest.class, + ResultSetQueryIndexTest.class, + IteratorResultSetTest.class, + OrderByResultSetTest.class, + OrderByValueTest.class, + AggregationResultSetTest.class, + ResultSetAggregationValueTest.class, + AccumulationAggregationUnitTest.class, + ComparableAggregationUnitTest.class, + AvgAggregationUnitTest.class, + GroupByValueTest.class + }) +public class AllMergerTest { +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetQueryIndexTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetQueryIndexTest.java new file mode 100644 index 0000000000000..c098d9ac3ca17 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetQueryIndexTest.java @@ -0,0 +1,47 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThat; +import static org.hamcrest.CoreMatchers.is; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; + +public final class ResultSetQueryIndexTest { + + @Test + public void assertIsQueryBySequence() { + assertTrue(new ResultSetQueryIndex(1).isQueryBySequence()); + assertFalse(new ResultSetQueryIndex("name").isQueryBySequence()); + } + + @Test + public void assertGetRawQueryIndex() { + assertThat(new ResultSetQueryIndex(1).getRawQueryIndex(), is((Object) 1)); + assertThat(new ResultSetQueryIndex("name").getRawQueryIndex(), is((Object) "name")); + } + + @Test(expected = IllegalArgumentException.class) + public void assertNewResultSetQueryIndexFailure() { + new ResultSetQueryIndex(1L); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetUtilTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetUtilTest.java new file mode 100644 index 0000000000000..dc449f2fddf8f --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/ResultSetUtilTest.java @@ -0,0 +1,116 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Date; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetUtil; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.GroupByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; + +public final class ResultSetUtilTest { + + @Test + public void assertGetValueFromGroupByColumn() throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.getObject("Name")).thenReturn(null); + when(resultSet.getObject("NAME")).thenReturn(null); + when(resultSet.getObject("name")).thenReturn("fromName"); + assertThat(ResultSetUtil.getValue(new GroupByColumn("Name_Column", "Name", OrderByType.ASC), resultSet), is((Object) "fromName")); + verify(resultSet).getObject("Name"); + verify(resultSet).getObject("NAME"); + verify(resultSet).getObject("name"); + } + + @Test(expected = NullPointerException.class) + public void assertCannotGetValueFromGroupByColumn() throws SQLException { + ResultSetUtil.getValue(new GroupByColumn("Name_Column", "none", OrderByType.ASC), mock(ResultSet.class)); + } + + @Test + public void assertGetValueFromGroupByColumnForIndex() throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.getObject(1)).thenReturn("fromIndex"); + assertThat(ResultSetUtil.getValue(new OrderByColumn(1, OrderByType.ASC), resultSet), is((Object) "fromIndex")); + verify(resultSet).getObject(1); + } + + @Test + public void assertGetValueFromGroupByColumnForName() throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.getObject("Name")).thenReturn(null); + when(resultSet.getObject("NAME")).thenReturn(null); + when(resultSet.getObject("name")).thenReturn("fromName"); + assertThat(ResultSetUtil.getValue(new OrderByColumn("Name", OrderByType.ASC), resultSet), is((Object) "fromName")); + verify(resultSet).getObject("Name"); + verify(resultSet).getObject("NAME"); + verify(resultSet).getObject("name"); + } + + @Test(expected = NullPointerException.class) + public void assertCannotGetValueFromGroupByColumnForIndex() throws SQLException { + ResultSetUtil.getValue(new OrderByColumn(1, OrderByType.ASC), mock(ResultSet.class)); + } + + @Test(expected = NullPointerException.class) + public void assertCannotGetValueFromGroupByColumnForName() throws SQLException { + ResultSetUtil.getValue(new OrderByColumn("none", OrderByType.ASC), mock(ResultSet.class)); + } + + @Test + public void assertConvertValueSuccess() { + assertThat((String) ResultSetUtil.convertValue("1", String.class), is("1")); + assertThat((int) ResultSetUtil.convertValue(new BigDecimal("1"), int.class), is(1)); + assertThat((long) ResultSetUtil.convertValue(new BigDecimal("1"), long.class), is(1L)); + assertThat((double) ResultSetUtil.convertValue(new BigDecimal("1"), double.class), is(1d)); + assertThat((float) ResultSetUtil.convertValue(new BigDecimal("1"), float.class), is(1f)); + assertThat((BigDecimal) ResultSetUtil.convertValue(new BigDecimal("1"), BigDecimal.class), is(new BigDecimal("1"))); + assertThat((BigDecimal) ResultSetUtil.convertValue((short) 1, BigDecimal.class), is(new BigDecimal("1"))); + assertThat((Date) ResultSetUtil.convertValue(new Date(0L), Date.class), is(new Date(0L))); + } + + @Test(expected = ShardingJdbcException.class) + public void assertConvertValueFailure() { + ResultSetUtil.convertValue((short) 1, short.class); + } + + @Test + public void assertcompareToForAsc() { + assertTrue(ResultSetUtil.compareTo(1, 2, OrderByType.ASC) < 0); + } + + @Test + public void assertcompareToForDesc() { + assertFalse(ResultSetUtil.compareTo(1, 2, OrderByType.DESC) < 0); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AccumulationAggregationUnitTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AccumulationAggregationUnitTest.java new file mode 100644 index 0000000000000..382127710296b --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AccumulationAggregationUnitTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public final class AccumulationAggregationUnitTest { + + @Test + public void assertAccumulationAggregation() { + AccumulationAggregationUnit accumulationAggregationUnit = new AccumulationAggregationUnit(int.class); + accumulationAggregationUnit.doMerge(1); + accumulationAggregationUnit.doMerge(1); + accumulationAggregationUnit.doMerge(10); + assertThat((Integer) accumulationAggregationUnit.getResult(), is(12)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationResultSetTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationResultSetTest.java new file mode 100644 index 0000000000000..c8fd9c2ab3d37 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AggregationResultSetTest.java @@ -0,0 +1,107 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.merger.ResultSetFactory; +import com.dangdang.ddframe.rdb.sharding.merger.fixture.MockResultSet; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn.AggregationType; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.google.common.base.Optional; + +public final class AggregationResultSetTest { + + @Test + public void assertNextForSum() throws SQLException { + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(6), new MockResultSet(2), new MockResultSet()), createMergeContext(AggregationType.SUM)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(8)); + assertFalse(resultSet.next()); + } + + @Test + public void assertNextForCount() throws SQLException { + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(6), new MockResultSet(2), new MockResultSet()), createMergeContext(AggregationType.COUNT)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(8)); + assertFalse(resultSet.next()); + } + + @Test + public void assertNextForMax() throws SQLException { + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(6), new MockResultSet(2), new MockResultSet()), createMergeContext(AggregationType.MAX)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(6)); + assertFalse(resultSet.next()); + } + + @Test + public void assertNextForMin() throws SQLException { + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(6), new MockResultSet(2), new MockResultSet()), createMergeContext(AggregationType.MIN)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(2)); + assertFalse(resultSet.next()); + } + + @Test + public void assertNextForAvg() throws SQLException { + Map map1 = new LinkedHashMap<>(2); + map1.put("sharding_gen_1", 5); + map1.put("sharding_gen_2", 10); + Map map2 = new LinkedHashMap<>(2); + map2.put("sharding_gen_1", 10); + map2.put("sharding_gen_2", 100); + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(Arrays.asList(map1)), new MockResultSet(Arrays.asList(map2)), new MockResultSet()), + createMergeContext(AggregationType.AVG, createDerivedColumn(1, AggregationType.COUNT), createDerivedColumn(2, AggregationType.SUM))); + assertTrue(resultSet.next()); + assertThat(resultSet.getDouble(1), is(7.3333D)); + assertFalse(resultSet.next()); + } + + private MergeContext createMergeContext(final AggregationType aggregationType, final AggregationColumn... derivedColumns) { + AggregationColumn column = new AggregationColumn("column", aggregationType, Optional.absent(), Optional.absent(), 1); + for (AggregationColumn each : derivedColumns) { + column.getDerivedColumns().add(each); + } + MergeContext result = new MergeContext(); + result.getAggregationColumns().add(column); + return result; + } + + private AggregationColumn createDerivedColumn(final int index, final AggregationType aggregationType) { + return new AggregationColumn("column", aggregationType, Optional.of("sharding_gen_" + index), Optional.absent()); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AvgAggregationUnitTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AvgAggregationUnitTest.java new file mode 100644 index 0000000000000..8af997c0096c8 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/AvgAggregationUnitTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.math.BigDecimal; + +import org.junit.Test; + +public final class AvgAggregationUnitTest { + + @Test + public void assertAvgAggregation() { + AvgAggregationUnit avgAggregationUnit = new AvgAggregationUnit(BigDecimal.class); + avgAggregationUnit.doMerge(10, 50); + avgAggregationUnit.doMerge(10, 20); + avgAggregationUnit.doMerge(5, 40); + assertThat((BigDecimal) avgAggregationUnit.getResult(), is(new BigDecimal("4.4000"))); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ComparableAggregationUnitTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ComparableAggregationUnitTest.java new file mode 100644 index 0000000000000..1321e2f417976 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ComparableAggregationUnitTest.java @@ -0,0 +1,44 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public final class ComparableAggregationUnitTest { + + @Test + public void assertComparableAggregationForAsc() { + ComparableAggregationUnit comparableAggregation = new ComparableAggregationUnit(true); + comparableAggregation.doMerge(1); + comparableAggregation.doMerge(10); + comparableAggregation.doMerge(5); + assertThat((Integer) comparableAggregation.getResult(), is(1)); + } + + @Test + public void assertComparableAggregationForDesc() { + ComparableAggregationUnit comparableAggregation = new ComparableAggregationUnit(false); + comparableAggregation.doMerge(1); + comparableAggregation.doMerge(10); + comparableAggregation.doMerge(5); + assertThat((Integer) comparableAggregation.getResult(), is(10)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ResultSetAggregationValueTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ResultSetAggregationValueTest.java new file mode 100644 index 0000000000000..285f828d76ef5 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/aggregation/ResultSetAggregationValueTest.java @@ -0,0 +1,50 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.aggregation; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; + +public final class ResultSetAggregationValueTest { + + @Test + public void assertGetValueForQueryIndex() throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.getObject(1)).thenReturn(10); + assertThat(new ResultSetAggregationValue(resultSet).getValue(new ResultSetQueryIndex(1)), is((Object) 10)); + verify(resultSet).getObject(1); + } + + @Test + public void assertGetValueForQueryName() throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.getObject("name")).thenReturn("value"); + assertThat(new ResultSetAggregationValue(resultSet).getValue(new ResultSetQueryIndex("name")), is((Object) "value")); + verify(resultSet).getObject("name"); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/fixture/AbstractUnsupportedOperationMockResultSet.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/fixture/AbstractUnsupportedOperationMockResultSet.java new file mode 100644 index 0000000000000..e9999ed234891 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/fixture/AbstractUnsupportedOperationMockResultSet.java @@ -0,0 +1,346 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.fixture; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Map; + +import com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.AbstractUnsupportedOperationResultSet; + +public abstract class AbstractUnsupportedOperationMockResultSet extends AbstractUnsupportedOperationResultSet { + + @Override + public final void close() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final boolean wasNull() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final ResultSetMetaData getMetaData() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final boolean getBoolean(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final boolean getBoolean(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final byte getByte(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final byte getByte(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final short getShort(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final short getShort(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final long getLong(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final long getLong(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final float getFloat(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final float getFloat(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final double getDouble(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final double getDouble(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final BigDecimal getBigDecimal(final int columnIndex, final int scale) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final BigDecimal getBigDecimal(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final BigDecimal getBigDecimal(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final BigDecimal getBigDecimal(final String columnLabel, final int scale) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final byte[] getBytes(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final byte[] getBytes(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Date getDate(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Date getDate(final int columnIndex, final Calendar cal) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Date getDate(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Date getDate(final String columnLabel, final Calendar cal) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Time getTime(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Time getTime(final int columnIndex, final Calendar cal) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Time getTime(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Time getTime(final String columnLabel, final Calendar cal) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Timestamp getTimestamp(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Timestamp getTimestamp(final int columnIndex, final Calendar cal) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Timestamp getTimestamp(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Timestamp getTimestamp(final String columnLabel, final Calendar cal) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final InputStream getAsciiStream(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final InputStream getAsciiStream(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final InputStream getUnicodeStream(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final InputStream getUnicodeStream(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final InputStream getBinaryStream(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final InputStream getBinaryStream(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final SQLWarning getWarnings() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final void clearWarnings() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Reader getCharacterStream(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Reader getCharacterStream(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final void setFetchDirection(final int direction) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final int getFetchDirection() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final void setFetchSize(final int rows) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final int getFetchSize() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final int getType() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final int getConcurrency() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Statement getStatement() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Object getObject(final int columnIndex, final Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Object getObject(final String columnLabel, final Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Blob getBlob(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Blob getBlob(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Clob getClob(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final Clob getClob(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final URL getURL(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final URL getURL(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final boolean isClosed() throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final SQLXML getSQLXML(final int columnIndex) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public final SQLXML getSQLXML(final String columnLabel) throws SQLException { + throw new SQLFeatureNotSupportedException(); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/fixture/MockResultSet.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/fixture/MockResultSet.java new file mode 100644 index 0000000000000..5dece2d3a2498 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/fixture/MockResultSet.java @@ -0,0 +1,115 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.fixture; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public final class MockResultSet extends AbstractUnsupportedOperationMockResultSet { + + private final List columnNamesMetaData; + + private final Iterator> data; + + private Map currentValue; + + public MockResultSet(@SuppressWarnings("unchecked") final T... data) { + columnNamesMetaData = new ArrayList<>(1); + List> list = new ArrayList<>(data.length); + for (T each : data) { + columnNamesMetaData.add("name"); + Map map = new LinkedHashMap<>(1); + map.put("name", each); + list.add(map); + } + this.data = list.iterator(); + } + + public MockResultSet(final List> data) { + columnNamesMetaData = new ArrayList<>(); + if (!data.isEmpty()) { + columnNamesMetaData.addAll(data.get(0).keySet()); + } + this.data = data.iterator(); + } + + public MockResultSet() { + this(Collections.>emptyList()); + } + + @Override + public boolean next() throws SQLException { + boolean result = data.hasNext(); + if (result) { + currentValue = data.next(); + } + return result; + } + + @Override + public int getInt(final int columnIndex) throws SQLException { + return (Integer) find(columnIndex); + } + + @Override + public int getInt(final String columnLabel) throws SQLException { + return (Integer) currentValue.get(columnLabel); + } + + @Override + public String getString(final int columnIndex) throws SQLException { + return (String) find(columnIndex); + } + + @Override + public String getString(final String columnLabel) throws SQLException { + return (String) currentValue.get(columnLabel); + } + + @Override + public Object getObject(final int columnIndex) throws SQLException { + return find(columnIndex); + } + + @Override + public Object getObject(final String columnLabel) throws SQLException { + return currentValue.get(columnLabel); + } + + @Override + public int findColumn(final String columnLabel) throws SQLException { + return columnNamesMetaData.indexOf(columnLabel) + 1; + } + + private T find(final int columnIndex) { + int count = 1; + for (Entry entry : currentValue.entrySet()) { + if (count == columnIndex) { + return entry.getValue(); + } + count++; + } + return null; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByValueTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByValueTest.java new file mode 100644 index 0000000000000..e754af60418c1 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/groupby/GroupByValueTest.java @@ -0,0 +1,95 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.groupby; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.merger.common.ResultSetQueryIndex; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; + +public final class GroupByValueTest { + + @Test + public void assertPutSuccess() { + GroupByValue groupByValue = new GroupByValue(); + groupByValue.put(1, "name1", 1); + assertThat(groupByValue.getValue(new ResultSetQueryIndex(1)), is((Object) 1)); + assertThat(groupByValue.getValue(new ResultSetQueryIndex("name1")), is((Object) 1)); + } + + @Test + public void assertPutForDuplicatedKey() { + GroupByValue groupByValue = new GroupByValue(); + groupByValue.put(1, "name1", 1); + groupByValue.put(1, "name1", 2); + assertThat(groupByValue.getValue(new ResultSetQueryIndex(1)), is((Object) 1)); + assertThat(groupByValue.getValue(new ResultSetQueryIndex("name1")), is((Object) 1)); + } + + @Test + public void assertCompareToForOtherValueIsNull() { + GroupByValue groupByValue = new GroupByValue(); + assertThat(groupByValue.compareTo(null), is(-1)); + } + + @Test + public void assertCompareToForLess() { + GroupByValue groupByValue = new GroupByValue(); + groupByValue.put(1, "name1", 1); + groupByValue.put(2, "name2", 2); + groupByValue.addOrderColumns(Arrays.asList(new OrderByColumn("name1", OrderByType.ASC), new OrderByColumn("name2", OrderByType.ASC))); + GroupByValue otherGroupByValue = new GroupByValue(); + otherGroupByValue.put(1, "name1", 1); + otherGroupByValue.put(2, "name2", 3); + otherGroupByValue.addOrderColumns(Arrays.asList(new OrderByColumn("name1", OrderByType.ASC), new OrderByColumn("name2", OrderByType.ASC))); + assertTrue(groupByValue.compareTo(otherGroupByValue) < 0); + } + + @Test + public void assertCompareToForGreat() { + GroupByValue groupByValue = new GroupByValue(); + groupByValue.put(1, "name1", 1); + groupByValue.put(2, "name2", 2); + groupByValue.addOrderColumns(Arrays.asList(new OrderByColumn("name1", OrderByType.ASC), new OrderByColumn("name2", OrderByType.ASC))); + GroupByValue otherGroupByValue = new GroupByValue(); + otherGroupByValue.put(1, "name1", 1); + otherGroupByValue.put(2, "name2", 1); + otherGroupByValue.addOrderColumns(Arrays.asList(new OrderByColumn("name1", OrderByType.ASC), new OrderByColumn("name2", OrderByType.ASC))); + assertTrue(groupByValue.compareTo(otherGroupByValue) > 0); + } + + @Test + public void assertCompareToForEqual() { + GroupByValue groupByValue = new GroupByValue(); + groupByValue.put(1, "name1", 1); + groupByValue.put(2, "name2", 2); + groupByValue.addOrderColumns(Arrays.asList(new OrderByColumn("name1", OrderByType.ASC), new OrderByColumn("name2", OrderByType.ASC))); + GroupByValue otherGroupByValue = new GroupByValue(); + otherGroupByValue.put(1, "name1", 1); + otherGroupByValue.put(2, "name2", 2); + otherGroupByValue.addOrderColumns(Arrays.asList(new OrderByColumn("name1", OrderByType.ASC), new OrderByColumn("name2", OrderByType.ASC))); + assertTrue(groupByValue.compareTo(otherGroupByValue) == 0); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/iterator/IteratorResultSetTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/iterator/IteratorResultSetTest.java new file mode 100644 index 0000000000000..3ee981f8fdc26 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/iterator/IteratorResultSetTest.java @@ -0,0 +1,72 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.iterator; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.merger.ResultSetFactory; +import com.dangdang.ddframe.rdb.sharding.merger.fixture.MockResultSet; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.Limit; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; + +public final class IteratorResultSetTest { + + @Test + public void assertNext() throws SQLException { + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(1), new MockResultSet(2, 4), new MockResultSet()), new MergeContext()); + int count = 0; + while (resultSet.next()) { + count++; + } + assertThat(count, is(3)); + } + + @Test + public void assertNextWithLimitForAllData() throws SQLException { + MergeContext mergeContext = new MergeContext(); + mergeContext.setLimit(new Limit(1, 10)); + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(1), new MockResultSet(2, 4), new MockResultSet()), mergeContext); + int count = 0; + while (resultSet.next()) { + count++; + } + assertThat(count, is(2)); + } + + @Test + public void assertNextWithLimitForPartData() throws SQLException { + MergeContext mergeContext = new MergeContext(); + mergeContext.setLimit(new Limit(1, 1)); + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(1), new MockResultSet(2, 4), new MockResultSet()), mergeContext); + int count = 0; + while (resultSet.next()) { + count++; + } + assertThat(count, is(1)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByResultSetTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByResultSetTest.java new file mode 100644 index 0000000000000..bd5e095919c8d --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByResultSetTest.java @@ -0,0 +1,74 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.orderby; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.merger.ResultSetFactory; +import com.dangdang.ddframe.rdb.sharding.merger.fixture.MockResultSet; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; + +public final class OrderByResultSetTest { + + @Test + public void assertNextForAsc() throws SQLException { + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(1, 4), new MockResultSet(2, 4), new MockResultSet()), createMergeContext(OrderByType.ASC)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(1)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(2)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(4)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(4)); + assertFalse(resultSet.next()); + } + + @Test + public void assertNextForDesc() throws SQLException { + ResultSet resultSet = ResultSetFactory.getResultSet(Arrays.asList( + new MockResultSet(4, 1), new MockResultSet(4, 2), new MockResultSet()), createMergeContext(OrderByType.DESC)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(4)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(4)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(2)); + assertTrue(resultSet.next()); + assertThat(resultSet.getInt(1), is(1)); + assertFalse(resultSet.next()); + } + + private MergeContext createMergeContext(final OrderByType orderType) { + MergeContext result = new MergeContext(); + result.getOrderByColumns().add(new OrderByColumn("name", orderType)); + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByValueTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByValueTest.java new file mode 100644 index 0000000000000..fb2abfdb2bbc7 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/merger/orderby/OrderByValueTest.java @@ -0,0 +1,83 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.merger.orderby; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; + +public final class OrderByValueTest { + + @Test + public void assertcompareToWithSame() { + List columns = Arrays.asList(new OrderByColumn("col1", OrderByType.ASC), new OrderByColumn("col2", OrderByType.DESC)); + List> values = createValues(1, 2); + OrderByValue.Value orderByValue1 = new OrderByValue.Value(columns, values); + OrderByValue.Value orderByValue2 = new OrderByValue.Value(columns, values); + assertThat(orderByValue1.compareTo(orderByValue2), is(0)); + } + + @Test + public void assertcompareToWithAscForFirstValue() { + List columns = Arrays.asList(new OrderByColumn("col1", OrderByType.ASC), new OrderByColumn("col2", OrderByType.DESC)); + OrderByValue.Value orderByValue1 = new OrderByValue.Value(columns, createValues(1, 2)); + OrderByValue.Value orderByValue2 = new OrderByValue.Value(columns, createValues(2, 2)); + assertTrue(orderByValue1.compareTo(orderByValue2) < 0); + } + + @Test + public void assertcompareToWithDescForFirstValue() { + List columns = Arrays.asList(new OrderByColumn("col1", OrderByType.ASC), new OrderByColumn("col2", OrderByType.DESC)); + OrderByValue.Value orderByValue1 = new OrderByValue.Value(columns, createValues(1, 2)); + OrderByValue.Value orderByValue2 = new OrderByValue.Value(columns, createValues(2, 2)); + assertTrue(orderByValue1.compareTo(orderByValue2) < 0); + } + + @Test + public void assertcompareToWithAscForSecondValue() { + List columns = Arrays.asList(new OrderByColumn("col1", OrderByType.ASC), new OrderByColumn("col2", OrderByType.DESC)); + OrderByValue.Value orderByValue1 = new OrderByValue.Value(columns, createValues(2, 1)); + OrderByValue.Value orderByValue2 = new OrderByValue.Value(columns, createValues(2, 2)); + assertTrue(orderByValue1.compareTo(orderByValue2) > 0); + } + + @Test + public void assertcompareToWithDescForSecondValue() { + List columns = Arrays.asList(new OrderByColumn("col1", OrderByType.ASC), new OrderByColumn("col2", OrderByType.DESC)); + OrderByValue.Value orderByValue1 = new OrderByValue.Value(columns, createValues(2, 1)); + OrderByValue.Value orderByValue2 = new OrderByValue.Value(columns, createValues(2, 2)); + assertTrue(orderByValue1.compareTo(orderByValue2) > 0); + } + + private List> createValues(final int... values) { + List> result = new ArrayList<>(values.length); + for (int each : values) { + result.add(each); + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/metrics/AllMetricsTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/metrics/AllMetricsTest.java new file mode 100644 index 0000000000000..3c791aa9f1654 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/metrics/AllMetricsTest.java @@ -0,0 +1,26 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.metrics; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses(MetricsContextTest.class) +public class AllMetricsTest { +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/metrics/MetricsContextTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/metrics/MetricsContextTest.java new file mode 100644 index 0000000000000..d4b8bd95ebe29 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/metrics/MetricsContextTest.java @@ -0,0 +1,42 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.metrics; + +import org.junit.Test; + +import com.codahale.metrics.Timer.Context; + +public final class MetricsContextTest { + + @Test + public void assertMetricsContextEnable() { + run(true); + } + + @Test + public void assertMetricsContextDisable() { + run(false); + } + + private void run(final boolean enable) { + MetricsContext metricsContext = new MetricsContext(enable, 1000000L, "example"); + metricsContext.register(); + Context context = MetricsContext.start("example"); + MetricsContext.stop(context); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/AbstractBaseParseTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/AbstractBaseParseTest.java new file mode 100644 index 0000000000000..e5a089cc4b2a6 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/AbstractBaseParseTest.java @@ -0,0 +1,244 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; + +import org.mockito.internal.matchers.apachecommons.ReflectionEquals; + +import com.dangdang.ddframe.rdb.sharding.parser.jaxb.Assert; +import com.dangdang.ddframe.rdb.sharding.parser.jaxb.Asserts; +import com.dangdang.ddframe.rdb.sharding.parser.jaxb.Value; +import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResult; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn.AggregationType; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.GroupByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.Limit; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.BinaryOperator; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.Column; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; + +import lombok.AccessLevel; +import lombok.Getter; + +public abstract class AbstractBaseParseTest { + + @Getter(AccessLevel.PROTECTED) + private final String testCaseName; + + @Getter(AccessLevel.PROTECTED) + private final String sql; + + private final String expectedSQL; + + private final Iterator
expectedTables; + + private final Iterator expectedConditionContexts; + + private final Iterator orderByColumns; + + private final Iterator groupByColumns; + + private final Iterator aggregationColumns; + + private final Limit limit; + + public AbstractBaseParseTest(final String testCaseName, final String sql, final String expectedSQL, + final Collection
expectedTables, final Collection expectedConditionContext, final MergeContext expectedMergeContext) { + this.testCaseName = testCaseName; + this.sql = sql; + this.expectedSQL = expectedSQL; + this.expectedTables = expectedTables.iterator(); + this.expectedConditionContexts = expectedConditionContext.iterator(); + this.orderByColumns = expectedMergeContext.getOrderByColumns().iterator(); + this.groupByColumns = expectedMergeContext.getGroupByColumns().iterator(); + this.aggregationColumns = expectedMergeContext.getAggregationColumns().iterator(); + this.limit = expectedMergeContext.getLimit(); + } + + public static Collection dataParameters(final String path) { + Collection result = new ArrayList<>(); + for (File each : new File(AbstractBaseParseTest.class.getClassLoader().getResource(path).getPath()).listFiles()) { + result.addAll(dataParameters(each)); + } + return result; + } + + private static Collection dataParameters(final File file) { + Asserts asserts = loadAsserts(file); + Object[][] result = new Object[asserts.getAsserts().size()][6]; + for (int i = 0; i < asserts.getAsserts().size(); i++) { + result[i] = getDataParameter(asserts.getAsserts().get(i)); + } + return Arrays.asList(result); + } + + private static Asserts loadAsserts(final File file) { + try { + return (Asserts) JAXBContext.newInstance(Asserts.class).createUnmarshaller().unmarshal(file); + } catch (final JAXBException ex) { + throw new RuntimeException(ex); + } + } + + private static Object[] getDataParameter(final Assert assertObj) { + Object[] result = new Object[6]; + result[0] = assertObj.getId(); + result[1] = assertObj.getSql(); + result[2] = assertObj.getExpectedSQL(); + result[3] = Lists.transform(assertObj.getTables(), new Function() { + + @Override + public Table apply(final com.dangdang.ddframe.rdb.sharding.parser.jaxb.Table input) { + return new Table(input.getName(), input.getAlias()); + } + }); + if (null == assertObj.getConditionContexts()) { + result[4] = Collections.emptyList(); + } else { + result[4] = Lists.transform(assertObj.getConditionContexts(), new Function() { + + @Override + public ConditionContext apply(final com.dangdang.ddframe.rdb.sharding.parser.jaxb.ConditionContext input) { + ConditionContext result = new ConditionContext(); + if (null == input.getConditions()) { + return result; + } + for (com.dangdang.ddframe.rdb.sharding.parser.jaxb.Condition each : input.getConditions()) { + Condition condition = new Condition(new Column(each.getColumnName(), each.getTableName()), BinaryOperator.valueOf(each.getOperator().toUpperCase())); + condition.getValues().addAll(Lists.transform(each.getValues(), new Function>() { + + @Override + public Comparable apply(final Value input) { + return input.getValueWithType(); + } + })); + result.add(condition); + } + return result; + } + }); + } + MergeContext mergeContext = new MergeContext(); + if (null != assertObj.getOrderByColumns()) { + mergeContext.getOrderByColumns().addAll(Lists.transform(assertObj.getOrderByColumns(), new Function() { + + @Override + public OrderByColumn apply(final com.dangdang.ddframe.rdb.sharding.parser.jaxb.OrderByColumn input) { + return Strings.isNullOrEmpty(input.getName()) ? new OrderByColumn(input.getIndex(), OrderByType.valueOf(input.getOrderByType().toUpperCase())) + : new OrderByColumn(input.getName(), OrderByType.valueOf(input.getOrderByType().toUpperCase())); + } + })); + } + if (null != assertObj.getGroupByColumns()) { + mergeContext.getGroupByColumns().addAll(Lists.transform(assertObj.getGroupByColumns(), new Function() { + + @Override + public GroupByColumn apply(final com.dangdang.ddframe.rdb.sharding.parser.jaxb.GroupByColumn input) { + return new GroupByColumn(input.getName(), input.getAlias(), OrderByType.valueOf(input.getOrderByType().toUpperCase())); + } + })); + } + if (null != assertObj.getAggregationColumns()) { + mergeContext.getAggregationColumns().addAll(Lists.transform(assertObj.getAggregationColumns(), + new Function() { + + @Override + public AggregationColumn apply(final com.dangdang.ddframe.rdb.sharding.parser.jaxb.AggregationColumn input) { + AggregationColumn result = new AggregationColumn(input.getExpression(), + AggregationType.valueOf(input.getAggregationType().toUpperCase()), Optional.fromNullable(input.getAlias()), Optional.fromNullable(input.getOption())); + if (null != input.getIndex()) { + result.setIndex(input.getIndex()); + } + for (com.dangdang.ddframe.rdb.sharding.parser.jaxb.AggregationColumn each : input.getDerivedColumns()) { + result.getDerivedColumns().add(new AggregationColumn(each.getExpression(), + AggregationType.valueOf(each.getAggregationType().toUpperCase()), Optional.fromNullable(each.getAlias()), Optional.fromNullable(each.getOption()))); + } + return result; + } + })); + } + if (null != assertObj.getLimit()) { + mergeContext.setLimit(new Limit(assertObj.getLimit().getOffset(), assertObj.getLimit().getRowCount())); + } + result[5] = mergeContext; + return result; + } + + protected final void assertSQLParsedResult(final SQLParsedResult actual) { + assertRouteContext(actual); + assertConditionContexts(actual); + assertMergeContext(actual); + } + + private void assertRouteContext(final SQLParsedResult actual) { + assertThat(actual.getRouteContext().getSqlBuilder().toString(), is(expectedSQL)); + for (Table each : actual.getRouteContext().getTables()) { + assertThat(each, new ReflectionEquals(expectedTables.next())); + } + assertFalse(expectedTables.hasNext()); + } + + private void assertConditionContexts(final SQLParsedResult actual) { + for (ConditionContext each : actual.getConditionContexts()) { + assertThat(each, is(new ReflectionEquals(expectedConditionContexts.next()))); + } + assertFalse(expectedConditionContexts.hasNext()); + } + + private void assertMergeContext(final SQLParsedResult actual) { + for (OrderByColumn each : actual.getMergeContext().getOrderByColumns()) { + assertThat(each, new ReflectionEquals(orderByColumns.next())); + } + assertFalse(orderByColumns.hasNext()); + for (GroupByColumn each : actual.getMergeContext().getGroupByColumns()) { + assertThat(each, new ReflectionEquals(groupByColumns.next())); + } + assertFalse(groupByColumns.hasNext()); + for (AggregationColumn each : actual.getMergeContext().getAggregationColumns()) { + AggregationColumn expected = aggregationColumns.next(); + assertThat(each, new ReflectionEquals(expected, "derivedColumns")); + for (int i = 0; i < each.getDerivedColumns().size(); i++) { + assertThat(each.getDerivedColumns().get(i), new ReflectionEquals(expected.getDerivedColumns().get(i))); + } + } + assertFalse(aggregationColumns.hasNext()); + assertThat(actual.getMergeContext().getLimit(), new ReflectionEquals(limit)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/AllParserTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/AllParserTest.java new file mode 100644 index 0000000000000..038bd6a9e638a --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/AllParserTest.java @@ -0,0 +1,41 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import com.dangdang.ddframe.rdb.sharding.parser.mysql.MySQLPreparedStatementForOneParameterTest; +import com.dangdang.ddframe.rdb.sharding.parser.mysql.MySQLPreparedStatementForTowParametersTest; +import com.dangdang.ddframe.rdb.sharding.parser.mysql.MySQLStatementTest; +import com.dangdang.ddframe.rdb.sharding.parser.mysql.OrParseTest; +import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResultTest; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContextTest; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + SQLParsedResultTest.class, + MergeContextTest.class, + MySQLStatementTest.class, + MySQLPreparedStatementForOneParameterTest.class, + MySQLPreparedStatementForTowParametersTest.class, + OrParseTest.class, + UnsupportedParseTest.class + }) +public class AllParserTest { +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/UnsupportedParseTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/UnsupportedParseTest.java new file mode 100644 index 0000000000000..0e2e6428c5fd3 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/UnsupportedParseTest.java @@ -0,0 +1,48 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser; + +import java.util.Collections; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; + +public final class UnsupportedParseTest { + + @Test(expected = SQLParserException.class) + public void assertCreate() throws SQLParserException { + SQLParserFactory.create(DatabaseType.MySQL, "CREATE TABLE `order` (id BIGINT(10))", Collections.emptyList(), Collections.emptyList()); + } + + @Test(expected = SQLParserException.class) + public void assertDrop() throws SQLParserException { + SQLParserFactory.create(DatabaseType.MySQL, "DROP TABLE `order`", Collections.emptyList(), Collections.emptyList()); + } + + @Test(expected = SQLParserException.class) + public void assertTruncate() throws SQLParserException { + SQLParserFactory.create(DatabaseType.MySQL, "TRUNCATE `order`", Collections.emptyList(), Collections.emptyList()); + } + + @Test(expected = SQLParserException.class) + public void assertAlter() throws SQLParserException { + SQLParserFactory.create(DatabaseType.MySQL, "ALTER TABLE `order` ADD COLUMN `other` VARCHAR(45)", Collections.emptyList(), Collections.emptyList()); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/AggregationColumn.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/AggregationColumn.java new file mode 100644 index 0000000000000..b92439a96f11e --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/AggregationColumn.java @@ -0,0 +1,53 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@XmlAccessorType(XmlAccessType.FIELD) +public final class AggregationColumn { + + @XmlAttribute + private String expression; + + @XmlAttribute(name = "aggregation-type") + private String aggregationType; + + @XmlAttribute + private String alias; + + @XmlAttribute + private String option; + + @XmlAttribute + private Integer index; + + @XmlElement(name = "derived-column") + private List derivedColumns = new ArrayList<>(2); +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Assert.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Assert.java new file mode 100644 index 0000000000000..d32ee93a70c1b --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Assert.java @@ -0,0 +1,67 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@XmlAccessorType(XmlAccessType.FIELD) +public final class Assert { + + @XmlAttribute + private String id; + + @XmlAttribute + private String sql; + + @XmlAttribute(name = "expected-sql") + private String expectedSQL; + + @XmlElementWrapper(name = "tables") + @XmlElement(name = "table") + private List
tables; + + @XmlElementWrapper(name = "condition-contexts") + @XmlElement(name = "condition-context") + private List conditionContexts; + + @XmlElementWrapper(name = "order-by-columns") + @XmlElement(name = "order-by-column") + private List orderByColumns; + + @XmlElementWrapper(name = "group-by-columns") + @XmlElement(name = "group-by-column") + private List groupByColumns; + + @XmlElementWrapper(name = "aggregation-columns") + @XmlElement(name = "aggregation-column") + private List aggregationColumns; + + @XmlElement + private Limit limit; +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Asserts.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Asserts.java new file mode 100644 index 0000000000000..6a1775e8c9539 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Asserts.java @@ -0,0 +1,34 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import lombok.Getter; + +@XmlRootElement(name = "asserts") +@Getter +public final class Asserts { + + @XmlElement(name = "assert") + private List asserts = new ArrayList<>(); +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Condition.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Condition.java new file mode 100644 index 0000000000000..ef9657cb4c53f --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Condition.java @@ -0,0 +1,46 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@XmlAccessorType(XmlAccessType.FIELD) +public final class Condition { + + @XmlAttribute(name = "column-name") + private String columnName; + + @XmlAttribute(name = "table-name") + private String tableName; + + @XmlAttribute + private String operator; + + @XmlElement(name = "value") + private List values; +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/ConditionContext.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/ConditionContext.java new file mode 100644 index 0000000000000..7284df6b8245c --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/ConditionContext.java @@ -0,0 +1,34 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import lombok.Getter; + +@Getter +@XmlAccessorType(XmlAccessType.FIELD) +public final class ConditionContext { + + @XmlElement(name = "condition") + private List conditions; +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/GroupByColumn.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/GroupByColumn.java new file mode 100644 index 0000000000000..5b72bbc7f8906 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/GroupByColumn.java @@ -0,0 +1,40 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@XmlAccessorType(XmlAccessType.FIELD) +public final class GroupByColumn { + + @XmlAttribute + private String name; + + @XmlAttribute + private String alias; + + @XmlAttribute(name = "order-by-type") + private String orderByType; +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Limit.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Limit.java new file mode 100644 index 0000000000000..b0d0c5e447c89 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Limit.java @@ -0,0 +1,37 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@XmlAccessorType(XmlAccessType.FIELD) +public final class Limit { + + @XmlAttribute + private Integer offset; + + @XmlAttribute(name = "row-count") + private Integer rowCount; +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/OrderByColumn.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/OrderByColumn.java new file mode 100644 index 0000000000000..04de054732054 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/OrderByColumn.java @@ -0,0 +1,40 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@XmlAccessorType(XmlAccessType.FIELD) +public final class OrderByColumn { + + @XmlAttribute + private String name; + + @XmlAttribute + private Integer index; + + @XmlAttribute(name = "order-by-type") + private String orderByType; +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Table.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Table.java new file mode 100644 index 0000000000000..25dcbb6eb4fba --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Table.java @@ -0,0 +1,37 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +import lombok.Getter; +import lombok.Setter; + +@XmlAccessorType(XmlAccessType.FIELD) +@Getter +@Setter +public final class Table { + + @XmlAttribute + private String name; + + @XmlAttribute + private String alias; +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Value.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Value.java new file mode 100644 index 0000000000000..f035259193bdf --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/jaxb/Value.java @@ -0,0 +1,53 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.jaxb; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@XmlAccessorType(XmlAccessType.FIELD) +public final class Value { + + @XmlAttribute + private String value; + + @XmlAttribute + private String type; + + public Comparable getValueWithType() { + if (boolean.class.getName().equals(type) || Boolean.class.getName().equals(type)) { + return Boolean.valueOf(value); + } + if (int.class.getName().equals(type) || Integer.class.getName().equals(type)) { + return Integer.parseInt(value); + } + if (long.class.getName().equals(type) || Long.class.getName().equals(type)) { + return Long.parseLong(value); + } + if (double.class.getName().equals(type) || Double.class.getName().equals(type)) { + return Double.parseDouble(value); + } + return value; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLPreparedStatementForOneParameterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLPreparedStatementForOneParameterTest.java new file mode 100644 index 0000000000000..88d063eb047f4 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLPreparedStatementForOneParameterTest.java @@ -0,0 +1,52 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.mysql; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.parser.AbstractBaseParseTest; +import com.dangdang.ddframe.rdb.sharding.parser.SQLParserFactory; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; + +@RunWith(Parameterized.class) +public final class MySQLPreparedStatementForOneParameterTest extends AbstractBaseParseTest { + + public MySQLPreparedStatementForOneParameterTest(final String testCaseName, final String sql, final String expectedSQL, + final Collection
expectedTables, final Collection expectedConditionContext, final MergeContext expectedMergeContext) { + super(testCaseName, sql, expectedSQL, expectedTables, expectedConditionContext, expectedMergeContext); + } + + @Parameters(name = "{0}") + public static Collection dataParameters() { + return AbstractBaseParseTest.dataParameters("com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/"); + } + + @Test + public void assertParse() { + assertSQLParsedResult(SQLParserFactory.create(DatabaseType.MySQL, getSql(), Arrays.asList(1), Arrays.asList("user_id", "order_id", "state")).parse()); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLPreparedStatementForTowParametersTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLPreparedStatementForTowParametersTest.java new file mode 100644 index 0000000000000..e52115eb50b33 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLPreparedStatementForTowParametersTest.java @@ -0,0 +1,52 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.mysql; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.parser.AbstractBaseParseTest; +import com.dangdang.ddframe.rdb.sharding.parser.SQLParserFactory; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; + +@RunWith(Parameterized.class) +public final class MySQLPreparedStatementForTowParametersTest extends AbstractBaseParseTest { + + public MySQLPreparedStatementForTowParametersTest(final String testCaseName, final String sql, final String expectedSQL, + final Collection
expectedTables, final Collection expectedConditionContext, final MergeContext expectedMergeContext) { + super(testCaseName, sql, expectedSQL, expectedTables, expectedConditionContext, expectedMergeContext); + } + + @Parameters(name = "{0}") + public static Collection dataParameters() { + return AbstractBaseParseTest.dataParameters("com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/"); + } + + @Test + public void assertParse() { + assertSQLParsedResult(SQLParserFactory.create(DatabaseType.MySQL, getSql(), Arrays.asList(1, 2), Arrays.asList("user_id", "order_id", "state")).parse()); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLStatementTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLStatementTest.java new file mode 100644 index 0000000000000..4ba6e0f7f0f10 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/MySQLStatementTest.java @@ -0,0 +1,53 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.mysql; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.parser.AbstractBaseParseTest; +import com.dangdang.ddframe.rdb.sharding.parser.SQLParserFactory; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; + +@RunWith(Parameterized.class) +public final class MySQLStatementTest extends AbstractBaseParseTest { + + public MySQLStatementTest(final String testCaseName, final String sql, final String expectedSQL, + final Collection
expectedTables, final Collection expectedConditionContext, final MergeContext expectedMergeContext) { + super(testCaseName, sql, expectedSQL, expectedTables, expectedConditionContext, expectedMergeContext); + } + + @Parameters(name = "{0}") + public static Collection dataParameters() { + return AbstractBaseParseTest.dataParameters("com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/"); + } + + @Test + public void assertParse() { + assertSQLParsedResult(SQLParserFactory.create(DatabaseType.MySQL, getSql(), Collections.emptyList(), Arrays.asList("user_id", "order_id", "state")).parse()); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/OrParseTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/OrParseTest.java new file mode 100644 index 0000000000000..06613ca4bf053 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/mysql/OrParseTest.java @@ -0,0 +1,55 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.parser.mysql; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.parser.AbstractBaseParseTest; +import com.dangdang.ddframe.rdb.sharding.parser.SQLParserFactory; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; + +@RunWith(Parameterized.class) +public final class OrParseTest extends AbstractBaseParseTest { + + public OrParseTest(final String testCaseName, final String sql, final String expectedSQL, + final Collection
expectedTables, final Collection expectedConditionContext, final MergeContext expectedMergeContext) { + super(testCaseName, sql, expectedSQL, expectedTables, expectedConditionContext, expectedMergeContext); + } + + @Parameters(name = "{0}") + public static Collection dataParameters() { + return AbstractBaseParseTest.dataParameters("com/dangdang/ddframe/rdb/sharding/parser/mysql/or/"); + } + + // TODO 归并字段,整合进mySQL测试 + @Test + public void assertParse() { + assertSQLParsedResult(SQLParserFactory.create(DatabaseType.MySQL, getSql(), Collections.emptyList(), + Arrays.asList("id", "user_id", "name", "age", "days", "fee", "traveldate", "long")).parse()); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/result/SQLParsedResultTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/result/SQLParsedResultTest.java new file mode 100644 index 0000000000000..442f33f3f2ecf --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/result/SQLParsedResultTest.java @@ -0,0 +1,75 @@ +package com.dangdang.ddframe.rdb.sharding.parser.result; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn.AggregationType; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.GroupByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.Limit; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.BinaryOperator; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.Column; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.RouteContext; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLBuilder; +import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table; +import com.google.common.base.Optional; + +public final class SQLParsedResultTest { + + @Test + public void assertToString() throws IOException { + SQLParsedResult actual = new SQLParsedResult(); + generateRouteContext(actual.getRouteContext()); + actual.getConditionContexts().add(generateConditionContext()); + generateMergeContext(actual.getMergeContext()); + assertThat(actual.toString(), is("SQLParsedResult(" + + "routeContext=RouteContext(" + + "tables=[Table(name=order, alias=Optional.of(o)), Table(name=order_item, alias=Optional.absent())], " + + "sqlBuilder=SELECT * FROM [Token(order)]), " + + "conditionContexts=[ConditionContext(conditions={Condition.Column(columnName=id, tableName=order)=Condition(column=Condition.Column(columnName=id, tableName=order), " + + "operator=IN, values=[1, 2, 3])})], " + + "mergeContext=MergeContext(" + + "orderByColumns=[OrderByColumn(name=Optional.of(id), index=Optional.absent(), orderByType=DESC)], " + + "groupByColumns=[GroupByColumn(name=id, alias=d, orderByType=ASC)], " + + "aggregationColumns=[AggregationColumn(expression=COUNT(id), aggregationType=COUNT, alias=Optional.of(c), option=Optional.absent(), derivedColumns=[], index=-1)], " + + "limit=Limit(offset=0, rowCount=10)))")); + } + + private void generateRouteContext(final RouteContext routeContext) throws IOException { + routeContext.getTables().add(new Table("order", Optional.of("o"))); + routeContext.getTables().add(new Table("order_item", Optional.absent())); + routeContext.setSqlBuilder(generateSqlBuilder()); + } + + private SQLBuilder generateSqlBuilder() throws IOException { + SQLBuilder result = new SQLBuilder(); + result.append("SELECT * FROM "); + result.appendToken("order"); + return result; + } + + private ConditionContext generateConditionContext() { + ConditionContext result = new ConditionContext(); + Condition condition = new Condition(new Column("id", "order"), BinaryOperator.IN); + condition.getValues().addAll(Arrays.asList(1, 2, 3)); + result.add(condition); + return result; + } + + private void generateMergeContext(final MergeContext mergeContext) { + mergeContext.getAggregationColumns().add(new AggregationColumn("COUNT(id)", AggregationType.COUNT, Optional.of("c"), Optional.absent())); + mergeContext.getOrderByColumns().add(new OrderByColumn("id", OrderByType.DESC)); + mergeContext.getGroupByColumns().add(new GroupByColumn("id", "d", OrderByType.ASC)); + mergeContext.setLimit(new Limit(0, 10)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/MergeContextTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/MergeContextTest.java new file mode 100644 index 0000000000000..2840b5e5c868d --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/parser/result/merger/MergeContextTest.java @@ -0,0 +1,41 @@ +package com.dangdang.ddframe.rdb.sharding.parser.result.merger; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn.AggregationType; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext.ResultSetType; +import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; +import com.google.common.base.Optional; + +public final class MergeContextTest { + + @Test + public void assertGetResultSetTypeWhenGroupBy() { + MergeContext actual = new MergeContext(); + actual.getGroupByColumns().add(new GroupByColumn("name", "alias", OrderByType.DESC)); + assertThat(actual.getResultSetType(), is(ResultSetType.GroupBy)); + } + + @Test + public void assertGetResultSetTypeWhenAggregate() { + MergeContext actual = new MergeContext(); + actual.getAggregationColumns().add(new AggregationColumn("COUNT(name)", AggregationType.COUNT, Optional.absent(), Optional.absent())); + assertThat(actual.getResultSetType(), is(ResultSetType.Aggregate)); + } + + @Test + public void assertGetResultSetTypeWhenOrderBy() { + MergeContext actual = new MergeContext(); + actual.getOrderByColumns().add(new OrderByColumn(1, OrderByType.DESC)); + assertThat(actual.getResultSetType(), is(ResultSetType.OrderBy)); + } + + @Test + public void assertGetResultSetTypeWhenIterator() { + MergeContext actual = new MergeContext(); + assertThat(actual.getResultSetType(), is(ResultSetType.Iterator)); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/AbstractBaseRouteSqlTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/AbstractBaseRouteSqlTest.java new file mode 100644 index 0000000000000..f6c00bb5cc4dd --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/AbstractBaseRouteSqlTest.java @@ -0,0 +1,107 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.sql.DataSource; + +import org.junit.Before; + +import com.dangdang.ddframe.rdb.sharding.api.DatabaseType; +import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; +import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; +import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; +import com.dangdang.ddframe.rdb.sharding.router.fixture.OrderAttrShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.router.fixture.OrderShardingAlgorithm; +import com.google.common.base.Function; +import com.google.common.collect.Lists; + +import lombok.AccessLevel; +import lombok.Getter; + +public abstract class AbstractBaseRouteSqlTest { + + @Getter(AccessLevel.PROTECTED) + private ShardingRule shardingRule; + + @Before + public void setRouteRuleContext() { + Map dataSourceMap = new HashMap<>(); + dataSourceMap.put("ds_0", null); + dataSourceMap.put("ds_1", null); + DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap); + TableRule orderTableRule = new TableRule("order", Lists.newArrayList("order_0", "order_1"), dataSourceRule); + TableRule orderItemTableRule = new TableRule("order_item", Lists.newArrayList("order_item_0", "order_item_1"), dataSourceRule); + TableRule orderAttrTableRule = new TableRule("order_attr", Lists.newArrayList("ds_0.order_attr_a", "ds_1.order_attr_b"), dataSourceRule, + new TableShardingStrategy("order_id", new OrderAttrShardingAlgorithm())); + shardingRule = new ShardingRule(dataSourceRule, Lists.newArrayList(orderTableRule, orderItemTableRule, orderAttrTableRule), + Arrays.asList(new BindingTableRule(Arrays.asList(orderTableRule, orderItemTableRule))), + new DatabaseShardingStrategy("order_id", new OrderShardingAlgorithm()), new TableShardingStrategy("order_id", new OrderShardingAlgorithm())); + } + + protected void assertSingleTarget(final String originSql, final String targetDataSource, final String targetSQL) throws SQLParserException { + assertSingleTarget(originSql, Collections.emptyList(), targetDataSource, targetSQL); + } + + protected void assertSingleTarget(final String originSql, final List parameters, final String targetDataSource, final String targetSQL) throws SQLParserException { + assertMultipleTargets(originSql, parameters, 1, Arrays.asList(targetDataSource), Arrays.asList(targetSQL)); + } + + protected void assertMultipleTargets(final String originSql, final int expectedSize, + final Collection targetDataSources, final Collection targetSQLs) throws SQLParserException { + assertMultipleTargets(originSql, Collections.emptyList(), expectedSize, targetDataSources, targetSQLs); + } + + protected void assertMultipleTargets(final String originSql, final List parameters, final int expectedSize, + final Collection targetDataSources, final Collection targetSQLs) throws SQLParserException { + SQLRouteResult actual = new SQLRouteEngine(getShardingRule(), DatabaseType.MySQL).route(originSql, parameters); + assertThat(actual.getExecutionUnits().size(), is(expectedSize)); + Set actualDdataSources = new HashSet(Lists.transform(actual.getExecutionUnits(), new Function() { + + @Override + public String apply(final SQLExecutionUnit input) { + return input.getDataSource(); + } + })); + assertThat(actualDdataSources, hasItems(targetDataSources.toArray(new String[0]))); + List actualSQLs = Lists.transform(actual.getExecutionUnits(), new Function() { + + @Override + public String apply(final SQLExecutionUnit input) { + return input.getSql(); + } + }); + assertThat(actualSQLs, hasItems(targetSQLs.toArray(new String[0]))); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/AllRouterTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/AllRouterTest.java new file mode 100644 index 0000000000000..868c5a4f5afbc --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/AllRouterTest.java @@ -0,0 +1,38 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import com.dangdang.ddframe.rdb.sharding.router.binding.BindingRoutingResultTest; +import com.dangdang.ddframe.rdb.sharding.router.mixed.CartesianResultTest; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingResultTest; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + SelectSingleTableTest.class, + SelectBindingTableTest.class, + SelectMixedTablesTest.class, + DMLTest.class, + SingleRoutingResultTest.class, + BindingRoutingResultTest.class, + CartesianResultTest.class + }) +public class AllRouterTest { +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/DMLTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/DMLTest.java new file mode 100644 index 0000000000000..2772897a23847 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/DMLTest.java @@ -0,0 +1,45 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; + +public final class DMLTest extends AbstractBaseRouteSqlTest { + + @Test + public void assertInsert() throws SQLParserException { + assertSingleTarget("insert into `order` (order_id, name) value (1,'test')", "ds_1", "INSERT INTO order_1 (order_id, name) VALUES (1, 'test')"); + assertSingleTarget("insert into `order` (order_id, name) value (?,?)", Arrays.asList(2, "test"), "ds_0", "INSERT INTO order_0 (order_id, name) VALUES (?, ?)"); + } + + @Test + public void assertUpdate() throws SQLParserException { + assertSingleTarget("update `order` set name = 'test' where order_id = 1", "ds_1", "UPDATE order_1 SET name = 'test' WHERE order_id = 1"); + assertSingleTarget("update `order` set name = ? where order_id = ?", Arrays.asList("test", 2), "ds_0", "UPDATE order_0 SET name = ? WHERE order_id = ?"); + } + + @Test + public void assertDelete() throws SQLParserException { + assertSingleTarget("delete from `order` where order_id = 1", "ds_1", "DELETE FROM order_1 WHERE order_id = 1"); + assertSingleTarget("delete from `order` where order_id = ?", Arrays.asList(2), "ds_0", "DELETE FROM order_0 WHERE order_id = ?"); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectBindingTableTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectBindingTableTest.java new file mode 100644 index 0000000000000..cfc350dcf2ba3 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectBindingTableTest.java @@ -0,0 +1,47 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; + +public final class SelectBindingTableTest extends AbstractBaseRouteSqlTest { + + @Test + public void assertSelectWithBindingJoin() throws SQLParserException { + assertSingleTarget("select * from order o inner join order_item i on o.order_id = i.order_id where o.order_id = 1", "ds_1", + "SELECT * FROM order_1 o INNER JOIN order_item_1 i ON o.order_id = i.order_id WHERE o.order_id = 1"); + assertSingleTarget("select * from order o join order_item i on o.order_id = i.order_id where o.order_id = 1", "ds_1", + "SELECT * FROM order_1 o JOIN order_item_1 i ON o.order_id = i.order_id WHERE o.order_id = 1"); + assertSingleTarget("select * from order o join order_item i using (order_id) where o.order_id = 1", "ds_1", + "SELECT * FROM order_1 o JOIN order_item_1 i USING (order_id) WHERE o.order_id = 1"); + assertSingleTarget("select * from order o, order_item i WHERE o.order_id = i.order_id and o.order_id = 1", "ds_1", + "SELECT * FROM order_1 o, order_item_1 i WHERE o.order_id = i.order_id AND o.order_id = 1"); + assertSingleTarget("select * from order o, order_item i WHERE o.order_id = i.order_id and o.order_id = ?", Arrays.asList(1), "ds_1", + "SELECT * FROM order_1 o, order_item_1 i WHERE o.order_id = i.order_id AND o.order_id = ?"); + } + + @Test + public void assertSelectWithRouteAllPartitions() throws SQLParserException { + assertMultipleTargets("select * from order o inner join order_item i on o.order_id = i.order_id", 4, Arrays.asList("ds_0", "ds_1"), + Arrays.asList("SELECT * FROM order_0 o INNER JOIN order_item_0 i ON o.order_id = i.order_id", "SELECT * FROM order_1 o INNER JOIN order_item_1 i ON o.order_id = i.order_id")); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectMixedTablesTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectMixedTablesTest.java new file mode 100644 index 0000000000000..1ed64c65f3625 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectMixedTablesTest.java @@ -0,0 +1,58 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; +import com.dangdang.ddframe.rdb.sharding.exception.ShardingJdbcException; + +public final class SelectMixedTablesTest extends AbstractBaseRouteSqlTest { + + @Test + public void assertBindingTableWithUnBoundTable() throws SQLParserException { + assertSingleTarget("select * from order o join order_item i join order_attr a using(order_id) where o.order_id = 1", "ds_1", + "SELECT * FROM order_1 o JOIN order_item_1 i JOIN order_attr_b a USING (order_id) WHERE o.order_id = 1"); + } + + @Test + public void assertConditionFromRelationship() throws SQLParserException { + assertSingleTarget("select * from order o join order_attr a using(order_id) where o.order_id = 1", "ds_1", + "SELECT * FROM order_1 o JOIN order_attr_b a USING (order_id) WHERE o.order_id = 1"); + } + + @Test + public void assertSelectWithCartesianProductAllPartitions() throws SQLParserException { + assertMultipleTargets("select * from order o, order_attr a", 4, Arrays.asList("ds_0", "ds_1"), + Arrays.asList("SELECT * FROM order_0 o, order_attr_a a", "SELECT * FROM order_1 o, order_attr_a a", + "SELECT * FROM order_0 o, order_attr_b a", "SELECT * FROM order_1 o, order_attr_b a")); + } + + @Test + public void assertSelectWithoutTableRule() throws SQLParserException { + assertSingleTarget("select * from order o join product p using(prod_id) where o.order_id = 1", "ds_1", + "SELECT * FROM order_1 o JOIN product p USING (prod_id) WHERE o.order_id = 1"); + } + + @Test(expected = ShardingJdbcException.class) + public void assertSelectTableWithoutRules() throws SQLParserException { + assertSingleTarget("select * from aaa, bbb, ccc", null, null); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectSingleTableTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectSingleTableTest.java new file mode 100644 index 0000000000000..c867413657eb7 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/SelectSingleTableTest.java @@ -0,0 +1,57 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router; + +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; + +public final class SelectSingleTableTest extends AbstractBaseRouteSqlTest { + + @Test + public void assertSingleSelect() throws SQLParserException { + assertSingleTarget("select * from order where order_id = 1", "ds_1", "SELECT * FROM order_1 WHERE order_id = 1"); + assertSingleTarget("select * from order where order_id = ?", Arrays.asList(2), "ds_0", "SELECT * FROM order_0 WHERE order_id = ?"); + } + + @Test + public void assertSelectWithAlias() throws SQLParserException { + assertSingleTarget("select * from order a where a.order_id = 2", "ds_0", "SELECT * FROM order_0 a WHERE a.order_id = 2"); + assertSingleTarget("select * from order A where a.order_id = 2", "ds_0", "SELECT * FROM order_0 A WHERE a.order_id = 2"); + assertSingleTarget("select * from order a where A.order_id = 2", "ds_0", "SELECT * FROM order_0 a WHERE A.order_id = 2"); + } + + @Test + public void assertSelectWithTableNameAsAlias() throws SQLParserException { + assertSingleTarget("select * from order where order.order_id = 10", "ds_0", "SELECT * FROM order_0 WHERE order_0.order_id = 10"); + } + + @Test + public void assertSelectWithIn() throws SQLParserException { + assertMultipleTargets("select * from order where order_id in (?,?,?)", Arrays.asList(1, 2, 100), 4, + Arrays.asList("ds_0", "ds_1"), Arrays.asList("SELECT * FROM order_0 WHERE order_id IN (?, ?, ?)", "SELECT * FROM order_1 WHERE order_id IN (?, ?, ?)")); + } + + @Test + public void assertSelectWithBetween() throws SQLParserException { + assertMultipleTargets("select * from order where order_id between ? and ?", Arrays.asList(1, 100), 4, + Arrays.asList("ds_0", "ds_1"), Arrays.asList("SELECT * FROM order_0 WHERE order_id BETWEEN ? AND ?", "SELECT * FROM order_1 WHERE order_id BETWEEN ? AND ?")); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingResultTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingResultTest.java new file mode 100644 index 0000000000000..21c4425d1554f --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/binding/BindingRoutingResultTest.java @@ -0,0 +1,42 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.binding; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingDataSource; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingResult; +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingTableFactor; + +public final class BindingRoutingResultTest { + + @Test + public void assertToString() { + SingleRoutingResult singleRoutingResult = new SingleRoutingResult(); + SingleRoutingDataSource dataSource = new SingleRoutingDataSource("ds"); + dataSource.getRoutingTableFactors().add(new SingleRoutingTableFactor("logic", "actual")); + singleRoutingResult.getRoutingDataSources().add(dataSource); + BindingRoutingResult actual = new BindingRoutingResult(singleRoutingResult); + assertThat(actual.toString(), is("BindingRoutingResult(super=SingleRoutingResult(routingDataSources=[" + + "BindingRoutingDataSource(super=SingleRoutingDataSource(dataSource=ds, routingTableFactors=[" + + "BindingRoutingTableFactor(super=SingleRoutingTableFactor(logicTable=logic, actualTable=actual), bindingRoutingTableFactors=[])]))]))")); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/fixture/OrderAttrShardingAlgorithm.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/fixture/OrderAttrShardingAlgorithm.java new file mode 100644 index 0000000000000..b065878df30b8 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/fixture/OrderAttrShardingAlgorithm.java @@ -0,0 +1,57 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.fixture; + +import java.util.Collection; +import java.util.HashSet; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm; + +public final class OrderAttrShardingAlgorithm implements SingleKeyTableShardingAlgorithm { + + @Override + public String doEqualSharding(final Collection tables, final ShardingValue shardingValue) { + String suffix = shardingValue.getValue() % 2 == 0 ? "_a" : "_b"; + for (String each : tables) { + if (each.endsWith(suffix)) { + return each; + } + } + return null; + } + + @Override + public Collection doInSharding(final Collection tables, final ShardingValue shardingValue) { + Collection result = new HashSet(tables.size()); + for (int value : shardingValue.getValues()) { + String suffix = value % 2 == 0 ? "_a" : "_b"; + for (String table : tables) { + if (table.endsWith(suffix)) { + result.add(table); + } + } + } + return result; + } + + @Override + public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + throw new UnsupportedOperationException(); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/fixture/OrderShardingAlgorithm.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/fixture/OrderShardingAlgorithm.java new file mode 100644 index 0000000000000..cabd63301bdc6 --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/fixture/OrderShardingAlgorithm.java @@ -0,0 +1,64 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.fixture; + +import java.util.Collection; +import java.util.HashSet; + +import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; +import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm; +import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm; + +public final class OrderShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm, SingleKeyTableShardingAlgorithm { + + @Override + public String doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + for (String each : availableTargetNames) { + if (each.endsWith(String.valueOf(shardingValue.getValue() % 2))) { + return each; + } + } + return null; + } + + @Override + public Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new HashSet<>(2); + for (String each : availableTargetNames) { + for (int value : shardingValue.getValues()) { + if (each.endsWith(String.valueOf(value % 2))) { + result.add(each); + } + } + } + return result; + } + + @Override + public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) { + Collection result = new HashSet<>(2); + for (int i = shardingValue.getValueRange().lowerEndpoint(); i <= shardingValue.getValueRange().upperEndpoint(); i++) { + for (String each : availableTargetNames) { + if (each.endsWith(String.valueOf(i % 2))) { + result.add(each); + } + } + } + return result; + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianResultTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianResultTest.java new file mode 100644 index 0000000000000..3ede1a53d803c --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/mixed/CartesianResultTest.java @@ -0,0 +1,40 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.mixed; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; + +import org.junit.Test; + +import com.dangdang.ddframe.rdb.sharding.router.single.SingleRoutingTableFactor; + +public final class CartesianResultTest { + + @Test + public void assertToString() { + CartesianResult actual = new CartesianResult(); + CartesianTableReference tableReference = new CartesianTableReference(Arrays.asList(new SingleRoutingTableFactor("logic", "actual"))); + CartesianDataSource dataSource = new CartesianDataSource("ds", tableReference); + actual.getRoutingDataSources().add(dataSource); + assertThat(actual.toString(), is("CartesianResult(routingDataSources=[CartesianDataSource(dataSource=ds, " + + "routingTableReferences=[CartesianTableReference(routingTableFactors=[SingleRoutingTableFactor(logicTable=logic, actualTable=actual)])])])")); + } +} diff --git a/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingResultTest.java b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingResultTest.java new file mode 100644 index 0000000000000..32100bf258ffc --- /dev/null +++ b/sharding-jdbc-core/src/test/java/com/dangdang/ddframe/rdb/sharding/router/single/SingleRoutingResultTest.java @@ -0,0 +1,36 @@ +/** + * Copyright 1999-2015 dangdang.com. + *

+ * 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 com.dangdang.ddframe.rdb.sharding.router.single; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; + +public final class SingleRoutingResultTest { + + @Test + public void assertToString() { + SingleRoutingResult actual = new SingleRoutingResult(); + SingleRoutingDataSource dataSource = new SingleRoutingDataSource("ds"); + dataSource.getRoutingTableFactors().add(new SingleRoutingTableFactor("logic", "actual")); + actual.getRoutingDataSources().add(dataSource); + assertThat(actual.toString(), is("SingleRoutingResult(routingDataSources=[" + + "SingleRoutingDataSource(dataSource=ds, routingTableFactors=[SingleRoutingTableFactor(logicTable=logic, actualTable=actual)])])")); + } +} diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/or/select_or.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/or/select_or.xml new file mode 100644 index 0000000000000..34ffa36166cc5 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/or/select_or.xml @@ -0,0 +1,123 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/delete.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/delete.xml new file mode 100644 index 0000000000000..5ac03b7fc8432 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/delete.xml @@ -0,0 +1,15 @@ + + + + +
+ + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/insert.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/insert.xml new file mode 100644 index 0000000000000..759bf7ab5eac9 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/insert.xml @@ -0,0 +1,18 @@ + + + + +
+ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/select.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/select.xml new file mode 100644 index 0000000000000..5a5f346dd67eb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/select.xml @@ -0,0 +1,17 @@ + + + + +
+ + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/select_limit.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/select_limit.xml new file mode 100644 index 0000000000000..757c903d2fdcf --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/select_limit.xml @@ -0,0 +1,12 @@ + + + + +
+ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/update.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/update.xml new file mode 100644 index 0000000000000..af707e59911b7 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/one_param/update.xml @@ -0,0 +1,15 @@ + + + + +
+ + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/select.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/select.xml new file mode 100644 index 0000000000000..d693b5f5b0981 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/select.xml @@ -0,0 +1,16 @@ + + + + +
+ + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/select_limit.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/select_limit.xml new file mode 100644 index 0000000000000..78360cb2a93c2 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/preparedstatement/two_params/select_limit.xml @@ -0,0 +1,12 @@ + + + + +
+ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/delete.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/delete.xml new file mode 100644 index 0000000000000..ab3276e80d530 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/delete.xml @@ -0,0 +1,31 @@ + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/insert.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/insert.xml new file mode 100644 index 0000000000000..7accb08dd7fb1 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/insert.xml @@ -0,0 +1,50 @@ + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/multiple_tables_select.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/multiple_tables_select.xml new file mode 100644 index 0000000000000..d19ca3cd21250 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/multiple_tables_select.xml @@ -0,0 +1,73 @@ + + + + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + +
+
+ + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_aggregate.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_aggregate.xml new file mode 100644 index 0000000000000..dc25a7b5dea0b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_aggregate.xml @@ -0,0 +1,76 @@ + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_group_by.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_group_by.xml new file mode 100644 index 0000000000000..af43e049a41b4 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_group_by.xml @@ -0,0 +1,27 @@ + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_limit.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_limit.xml new file mode 100644 index 0000000000000..05df2b4686261 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_limit.xml @@ -0,0 +1,22 @@ + + + + +
+ + + + + + + + + +
+ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_order_by.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_order_by.xml new file mode 100644 index 0000000000000..cb6daaf3171ec --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/select_order_by.xml @@ -0,0 +1,33 @@ + + + + +
+ + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/simple_select.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/simple_select.xml new file mode 100644 index 0000000000000..1d4eda95e341c --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/simple_select.xml @@ -0,0 +1,151 @@ + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/update.xml b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/update.xml new file mode 100644 index 0000000000000..60108296b6a15 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/com/dangdang/ddframe/rdb/sharding/parser/mysql/statement/update.xml @@ -0,0 +1,17 @@ + + + + +
+ + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/Empty.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/Empty.xml new file mode 100644 index 0000000000000..0fe5c585864c2 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/Empty.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_0.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_0.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_1.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_2.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_2.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_2.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_3.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_3.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_3.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_4.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_4.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_4.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_5.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_5.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_5.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_6.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_6.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_6.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_7.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_7.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_7.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_8.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_8.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_8.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_9.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_9.xml new file mode 100644 index 0000000000000..2f869d80f7309 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/delete/db_9.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_0.xml new file mode 100644 index 0000000000000..fbd9bb376e0da --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_0.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_1.xml new file mode 100644 index 0000000000000..eefe1e616c195 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_2.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_2.xml new file mode 100644 index 0000000000000..db5111310a58b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_2.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_3.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_3.xml new file mode 100644 index 0000000000000..2fb4c216791cb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_3.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_4.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_4.xml new file mode 100644 index 0000000000000..d3966adf68b9d --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_4.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_5.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_5.xml new file mode 100644 index 0000000000000..642e5f3513067 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_5.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_6.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_6.xml new file mode 100644 index 0000000000000..84c9713088c86 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_6.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_7.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_7.xml new file mode 100644 index 0000000000000..b8edf8667d92d --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_7.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_8.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_8.xml new file mode 100644 index 0000000000000..c01494e7a98a3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_8.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_9.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_9.xml new file mode 100644 index 0000000000000..ac84ea46ba6cc --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/insert/db_9.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectBetweenWithSingleTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectBetweenWithSingleTable.xml new file mode 100644 index 0000000000000..8b90f72c23b0e --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectBetweenWithSingleTable.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_0.xml new file mode 100644 index 0000000000000..f4ab559d2163f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_0.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_1.xml new file mode 100644 index 0000000000000..f3fa772e35d58 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectEqualsWithSingleTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectGroupByWithBindingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectGroupByWithBindingTable.xml new file mode 100644 index 0000000000000..a6e4403dd68fd --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectGroupByWithBindingTable.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectGroupByWithoutGroupedColumn.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectGroupByWithoutGroupedColumn.xml new file mode 100644 index 0000000000000..5c6a7740223b6 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectGroupByWithoutGroupedColumn.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectInWithSingleTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectInWithSingleTable_0.xml new file mode 100644 index 0000000000000..4639baa94157a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectInWithSingleTable_0.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectInWithSingleTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectInWithSingleTable_1.xml new file mode 100644 index 0000000000000..f4ab559d2163f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectInWithSingleTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectLimitWithBindingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectLimitWithBindingTable.xml new file mode 100644 index 0000000000000..93d2d10da4ebe --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectLimitWithBindingTable.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectLimitWithBindingTableWithoutOffset.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectLimitWithBindingTableWithoutOffset.xml new file mode 100644 index 0000000000000..e52fab5844de3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectLimitWithBindingTableWithoutOffset.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectNoShardingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectNoShardingTable.xml new file mode 100644 index 0000000000000..4456625fc2560 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select/SelectNoShardingTable.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectAvg.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectAvg.xml new file mode 100644 index 0000000000000..2e1d95587aa49 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectAvg.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCount.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCount.xml new file mode 100644 index 0000000000000..e3077004e6b8f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCount.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_0.xml new file mode 100644 index 0000000000000..a1bd0fb916876 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_0.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_1.xml new file mode 100644 index 0000000000000..274769a5ca44a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectCountWithBindingTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectMax.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectMax.xml new file mode 100644 index 0000000000000..31d2a2e9b3db3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectMax.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectMin.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectMin.xml new file mode 100644 index 0000000000000..e8e750fc317c2 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectMin.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectSum.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectSum.xml new file mode 100644 index 0000000000000..6b2b9c6f62f67 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_aggregate/SelectSum.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectAvg.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectAvg.xml new file mode 100644 index 0000000000000..426c25a271053 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectAvg.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectCount.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectCount.xml new file mode 100644 index 0000000000000..28a7205c06425 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectCount.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectMax.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectMax.xml new file mode 100644 index 0000000000000..7e571b860d513 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectMax.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectMin.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectMin.xml new file mode 100644 index 0000000000000..838cb1b5f50eb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectMin.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectOrderByDesc.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectOrderByDesc.xml new file mode 100644 index 0000000000000..d17207dcb0ed6 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectOrderByDesc.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectSum.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectSum.xml new file mode 100644 index 0000000000000..e487c6e9d3531 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/select_groupby/SelectSum.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_0.xml new file mode 100644 index 0000000000000..8150abc2cc29f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_0.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_1.xml new file mode 100644 index 0000000000000..49a3f9ae018f6 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_1.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_2.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_2.xml new file mode 100644 index 0000000000000..1b9afb83e0036 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_2.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_3.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_3.xml new file mode 100644 index 0000000000000..2ea70928ad4dc --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_3.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_4.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_4.xml new file mode 100644 index 0000000000000..131bbbac928c3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_4.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_5.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_5.xml new file mode 100644 index 0000000000000..ddc96586a07a2 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_5.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_6.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_6.xml new file mode 100644 index 0000000000000..b44328b08c34e --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_6.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_7.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_7.xml new file mode 100644 index 0000000000000..aa36986a79548 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_7.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_8.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_8.xml new file mode 100644 index 0000000000000..ece7f83e59ff9 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_8.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_9.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_9.xml new file mode 100644 index 0000000000000..4e5a189d8338a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/expect/update/db_9.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_0.xml new file mode 100644 index 0000000000000..15f6fa1c93b01 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_0.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_1.xml new file mode 100644 index 0000000000000..6158de4411011 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_1.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_2.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_2.xml new file mode 100644 index 0000000000000..793a38944eb45 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_2.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_3.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_3.xml new file mode 100644 index 0000000000000..55c92ab9ed682 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_3.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_4.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_4.xml new file mode 100644 index 0000000000000..53a4af4713aaa --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_4.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_5.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_5.xml new file mode 100644 index 0000000000000..1485b0d027898 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_5.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_6.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_6.xml new file mode 100644 index 0000000000000..ae40b1d134800 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_6.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_7.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_7.xml new file mode 100644 index 0000000000000..6ceeaa1df698a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_7.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_8.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_8.xml new file mode 100644 index 0000000000000..e1b6fa3dd605c --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_8.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_9.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_9.xml new file mode 100644 index 0000000000000..eb30aedf493e6 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/db/init/db_9.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_0.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_0.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_1.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_2.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_2.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_3.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_3.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_3.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_4.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_4.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_4.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_5.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_5.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_5.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_6.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_6.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_6.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_7.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_7.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_7.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_8.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_8.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_8.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_9.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_9.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/delete/dbtbl_9.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_0.xml new file mode 100644 index 0000000000000..61ec6821149f0 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_0.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_1.xml new file mode 100644 index 0000000000000..206b0fd784496 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_2.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_2.xml new file mode 100644 index 0000000000000..c97bfd8653f49 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_3.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_3.xml new file mode 100644 index 0000000000000..e26b3080583b4 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_3.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_4.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_4.xml new file mode 100644 index 0000000000000..2dda0e732e362 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_4.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_5.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_5.xml new file mode 100644 index 0000000000000..6882c732fafc7 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_5.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_6.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_6.xml new file mode 100644 index 0000000000000..a59992a387c1a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_6.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_7.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_7.xml new file mode 100644 index 0000000000000..a04027411edf2 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_7.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_8.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_8.xml new file mode 100644 index 0000000000000..b929573433077 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_8.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_9.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_9.xml new file mode 100644 index 0000000000000..92f7ea15db5b9 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/insert/dbtbl_9.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectBetweenWithSingleTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectBetweenWithSingleTable.xml new file mode 100644 index 0000000000000..ff6fe247c2cd3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectBetweenWithSingleTable.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_0.xml new file mode 100644 index 0000000000000..f4ab559d2163f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_0.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_1.xml new file mode 100644 index 0000000000000..f3fa772e35d58 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectEqualsWithSingleTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectGroupByWithBindingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectGroupByWithBindingTable.xml new file mode 100644 index 0000000000000..dec4249f32917 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectGroupByWithBindingTable.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectGroupByWithoutGroupedColumn.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectGroupByWithoutGroupedColumn.xml new file mode 100644 index 0000000000000..b616a9539ea09 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectGroupByWithoutGroupedColumn.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_0.xml new file mode 100644 index 0000000000000..2c72619b02000 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_0.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_1.xml new file mode 100644 index 0000000000000..2f243edf50d07 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectInWithSingleTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTable.xml new file mode 100644 index 0000000000000..72027f47e06b9 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTable.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml new file mode 100644 index 0000000000000..0665779836872 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectNoShardingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectNoShardingTable.xml new file mode 100644 index 0000000000000..5c86910889386 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectNoShardingTable.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectWithBingdingTableAndConfigTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectWithBingdingTableAndConfigTable.xml new file mode 100644 index 0000000000000..7e36105d1a20b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select/SelectWithBingdingTableAndConfigTable.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectAvg.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectAvg.xml new file mode 100644 index 0000000000000..27d56faefed5a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectAvg.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCount.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCount.xml new file mode 100644 index 0000000000000..f353f964a0746 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCount.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml new file mode 100644 index 0000000000000..fc93d44cead44 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml new file mode 100644 index 0000000000000..274769a5ca44a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectMax.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectMax.xml new file mode 100644 index 0000000000000..448f99ef437f3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectMax.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectMin.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectMin.xml new file mode 100644 index 0000000000000..e8e750fc317c2 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectMin.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectSum.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectSum.xml new file mode 100644 index 0000000000000..d7db6d76e0701 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_aggregate/SelectSum.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectAvg.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectAvg.xml new file mode 100644 index 0000000000000..2f8111eff3b3e --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectAvg.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectCount.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectCount.xml new file mode 100644 index 0000000000000..7f843b51a3f76 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectCount.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectMax.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectMax.xml new file mode 100644 index 0000000000000..ab9ceff96ed08 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectMax.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectMin.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectMin.xml new file mode 100644 index 0000000000000..b4e3880f6963c --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectMin.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectOrderByDesc.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectOrderByDesc.xml new file mode 100644 index 0000000000000..4609cc38d18c2 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectOrderByDesc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectSum.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectSum.xml new file mode 100644 index 0000000000000..aad76734c22fb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/select_groupby/SelectSum.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_0.xml new file mode 100644 index 0000000000000..5fc8de77186d0 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_0.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_1.xml new file mode 100644 index 0000000000000..f906c7dab9ac9 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_2.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_2.xml new file mode 100644 index 0000000000000..503a652992d23 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_2.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_3.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_3.xml new file mode 100644 index 0000000000000..81bde7c65847f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_3.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_4.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_4.xml new file mode 100644 index 0000000000000..7ca484ef55a3f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_4.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_5.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_5.xml new file mode 100644 index 0000000000000..a1a7ae1cd6edb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_5.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_6.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_6.xml new file mode 100644 index 0000000000000..eac3ab2e57952 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_6.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_7.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_7.xml new file mode 100644 index 0000000000000..91be04228c64b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_7.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_8.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_8.xml new file mode 100644 index 0000000000000..3c882767ebaed --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_8.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_9.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_9.xml new file mode 100644 index 0000000000000..f740372b3a44b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/expect/update/dbtbl_9.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_0.xml new file mode 100644 index 0000000000000..c0be821b96c7b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_0.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_1.xml new file mode 100644 index 0000000000000..fa1d68b502421 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_1.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_2.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_2.xml new file mode 100644 index 0000000000000..7fac7c7a5aad9 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_2.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_3.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_3.xml new file mode 100644 index 0000000000000..bddff42b11d07 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_3.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_4.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_4.xml new file mode 100644 index 0000000000000..c4099713071a3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_4.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_5.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_5.xml new file mode 100644 index 0000000000000..e6cbd74d04aa1 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_5.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_6.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_6.xml new file mode 100644 index 0000000000000..09b1515b10dc7 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_6.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_7.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_7.xml new file mode 100644 index 0000000000000..328f1ab399807 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_7.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_8.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_8.xml new file mode 100644 index 0000000000000..0b4885bb5b35e --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_8.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_9.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_9.xml new file mode 100644 index 0000000000000..0108530fe71da --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/dbtbl/init/dbtbl_9.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/delete/db_single.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/delete/db_single.xml new file mode 100644 index 0000000000000..82a36eff2e155 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/delete/db_single.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/insert/db_single.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/insert/db_single.xml new file mode 100644 index 0000000000000..172c551d32a6a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/insert/db_single.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectBetweenWithSingleTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectBetweenWithSingleTable.xml new file mode 100644 index 0000000000000..ff6fe247c2cd3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectBetweenWithSingleTable.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_0.xml new file mode 100644 index 0000000000000..f4ab559d2163f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_0.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_1.xml new file mode 100644 index 0000000000000..67760b43f8c5f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectEqualsWithSingleTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectGroupByWithBindingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectGroupByWithBindingTable.xml new file mode 100644 index 0000000000000..3d72965acc6f8 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectGroupByWithBindingTable.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectGroupByWithoutGroupedColumn.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectGroupByWithoutGroupedColumn.xml new file mode 100644 index 0000000000000..b616a9539ea09 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectGroupByWithoutGroupedColumn.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectInWithSingleTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectInWithSingleTable_0.xml new file mode 100644 index 0000000000000..2aaf74fb5f9f7 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectInWithSingleTable_0.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectInWithSingleTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectInWithSingleTable_1.xml new file mode 100644 index 0000000000000..2f243edf50d07 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectInWithSingleTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectLimitWithBindingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectLimitWithBindingTable.xml new file mode 100644 index 0000000000000..172edd66b13f3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectLimitWithBindingTable.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml new file mode 100644 index 0000000000000..a87e2aa8d13a9 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectLimitWithBindingTableWithoutOffset.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectNoShardingTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectNoShardingTable.xml new file mode 100644 index 0000000000000..724a59d671037 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectNoShardingTable.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectWithBingdingTableAndConfigTable.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectWithBingdingTableAndConfigTable.xml new file mode 100644 index 0000000000000..7e36105d1a20b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select/SelectWithBingdingTableAndConfigTable.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectAvg.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectAvg.xml new file mode 100644 index 0000000000000..36f5cb1f5c350 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectAvg.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCount.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCount.xml new file mode 100644 index 0000000000000..5900c298f4dae --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCount.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml new file mode 100644 index 0000000000000..fc93d44cead44 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_0.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml new file mode 100644 index 0000000000000..274769a5ca44a --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectCountWithBindingTable_1.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectMax.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectMax.xml new file mode 100644 index 0000000000000..96cce5306b621 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectMax.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectMin.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectMin.xml new file mode 100644 index 0000000000000..e8e750fc317c2 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectMin.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectSum.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectSum.xml new file mode 100644 index 0000000000000..7af8d8a69a5cb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_aggregate/SelectSum.xml @@ -0,0 +1,3 @@ + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectAvg.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectAvg.xml new file mode 100644 index 0000000000000..4a4e715c3cba8 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectAvg.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectCount.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectCount.xml new file mode 100644 index 0000000000000..c29446fc828bb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectCount.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectMax.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectMax.xml new file mode 100644 index 0000000000000..4eb1a3bd8dc34 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectMax.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectMin.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectMin.xml new file mode 100644 index 0000000000000..39e4ab7f61aeb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectMin.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectOrderByDesc.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectOrderByDesc.xml new file mode 100644 index 0000000000000..809a9a00a895b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectOrderByDesc.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectSum.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectSum.xml new file mode 100644 index 0000000000000..29748856aa9ee --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/select_groupby/SelectSum.xml @@ -0,0 +1,4 @@ + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/update/db_single.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/update/db_single.xml new file mode 100644 index 0000000000000..b5cbddcb19b4d --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/expect/update/db_single.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/init/db_single.xml b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/init/db_single.xml new file mode 100644 index 0000000000000..d55e7cd9758d5 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/dataset/tbl/init/db_single.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/all_schema.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/all_schema.sql new file mode 100644 index 0000000000000..952d794a7bf47 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/all_schema.sql @@ -0,0 +1,295 @@ +/** + * 分库分表. + */ +CREATE SCHEMA IF NOT EXISTS `dbtbl_0`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_1`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_2`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_3`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_4`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_5`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_6`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_7`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_8`; +CREATE SCHEMA IF NOT EXISTS `dbtbl_9`; + +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_0`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_1`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_2`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_3`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_4`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_5`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_6`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_7`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_8`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `dbtbl_9`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); + +/** + * 仅分库. + */ +CREATE SCHEMA IF NOT EXISTS `db_0`; +CREATE SCHEMA IF NOT EXISTS `db_1`; +CREATE SCHEMA IF NOT EXISTS `db_2`; +CREATE SCHEMA IF NOT EXISTS `db_3`; +CREATE SCHEMA IF NOT EXISTS `db_4`; +CREATE SCHEMA IF NOT EXISTS `db_5`; +CREATE SCHEMA IF NOT EXISTS `db_6`; +CREATE SCHEMA IF NOT EXISTS `db_7`; +CREATE SCHEMA IF NOT EXISTS `db_8`; +CREATE SCHEMA IF NOT EXISTS `db_9`; + +CREATE TABLE IF NOT EXISTS `db_0`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_1`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_2`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_3`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_4`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_5`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_6`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_7`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_8`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_9`.`t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_0`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_1`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_2`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_3`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_4`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_5`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_6`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_7`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_8`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_9`.`t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); + +/** + * 仅分表. + */ +CREATE SCHEMA IF NOT EXISTS `db_single`; + +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `db_single`.`t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_0.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_0.sql new file mode 100644 index 0000000000000..e4e1b7c5cb5b4 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_0.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_0`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_1.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_1.sql new file mode 100644 index 0000000000000..ab632a2fa7740 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_1.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_1`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_2.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_2.sql new file mode 100644 index 0000000000000..0e89876744e0e --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_2.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_2`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_3.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_3.sql new file mode 100644 index 0000000000000..13e0bc5d7c0aa --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_3.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_3`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_4.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_4.sql new file mode 100644 index 0000000000000..5ad02210db837 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_4.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_4`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_5.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_5.sql new file mode 100644 index 0000000000000..3cbbd0b6befa3 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_5.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_5`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_6.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_6.sql new file mode 100644 index 0000000000000..e874e00deaca8 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_6.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_6`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_7.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_7.sql new file mode 100644 index 0000000000000..723ef5929fd9b --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_7.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_7`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_8.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_8.sql new file mode 100644 index 0000000000000..d50792315e7c4 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_8.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_8`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_9.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_9.sql new file mode 100644 index 0000000000000..b161981145a5c --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/db/db_9.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS `db_9`; + +CREATE TABLE IF NOT EXISTS `t_order` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_0.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_0.sql new file mode 100644 index 0000000000000..9aed820a825f7 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_0.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_0`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_1.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_1.sql new file mode 100644 index 0000000000000..eebcae25f270f --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_1.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_1`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_2.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_2.sql new file mode 100644 index 0000000000000..c16ada25af729 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_2.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_2`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_3.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_3.sql new file mode 100644 index 0000000000000..1891b166eb691 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_3.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_3`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_4.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_4.sql new file mode 100644 index 0000000000000..621e9b9794612 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_4.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_4`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_5.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_5.sql new file mode 100644 index 0000000000000..ae08fedbcc4fb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_5.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_5`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_6.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_6.sql new file mode 100644 index 0000000000000..edee231eec516 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_6.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_6`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_7.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_7.sql new file mode 100644 index 0000000000000..dfbf9febf45a9 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_7.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_7`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_8.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_8.sql new file mode 100644 index 0000000000000..cc2d5cb780ddb --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_8.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_8`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_9.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_9.sql new file mode 100644 index 0000000000000..5f7e59acbdb97 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/dbtbl/dbtbl_9.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `dbtbl_9`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/integrate/schema/tbl/db_single.sql b/sharding-jdbc-core/src/test/resources/integrate/schema/tbl/db_single.sql new file mode 100644 index 0000000000000..8701eccd0d51e --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/integrate/schema/tbl/db_single.sql @@ -0,0 +1,23 @@ +CREATE SCHEMA IF NOT EXISTS `db_single`; + +CREATE TABLE IF NOT EXISTS `t_order_0` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_1` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_2` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_3` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_4` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_5` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_6` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_7` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_8` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_9` (`order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`order_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_0` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_1` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_2` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_3` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_4` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_5` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_6` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_7` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_8` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_order_item_9` (`item_id` INT NOT NULL, `order_id` INT NOT NULL, `user_id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`item_id`)); +CREATE TABLE IF NOT EXISTS `t_config` (`id` INT NOT NULL, `status` VARCHAR(45) NULL, PRIMARY KEY (`id`)); diff --git a/sharding-jdbc-core/src/test/resources/logback-test.xml b/sharding-jdbc-core/src/test/resources/logback-test.xml new file mode 100644 index 0000000000000..49040440f6d08 --- /dev/null +++ b/sharding-jdbc-core/src/test/resources/logback-test.xml @@ -0,0 +1,23 @@ + + + + + + [%-5level] %d{HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n + + + + + + + + + + + + + + + + + diff --git a/sharding-jdbc-doc/config.toml b/sharding-jdbc-doc/config.toml new file mode 100644 index 0000000000000..34824a7cf12af --- /dev/null +++ b/sharding-jdbc-doc/config.toml @@ -0,0 +1,24 @@ +baseurl = "http://dangdangdotcom.github.io" +languageCode = "en-us" +title = "Sharding-JDBC" + +[params] + description = "Sharding-JDBC project." + author_url = "https://github.com/dangdangdotcom" + project_url = "https://github.com/dangdangdotcom/sharding-jdbc" + project_documentation = "http://dangdangdotcom.github.io/sharding-jdbc" + github_project_name = "Sharding-JDBC" + github_user_name = "dangdangdotcom" + + first_color="#f8f8f8" + first_border_color="#e7e7e7" + first_text_color="#333" + + second_color="white" + second_text_color="#333" + + header_color="#f8f8f8" + header_text_color="rgb(51, 51, 51)" + + header_link_color="#777" + header_link_hover_color="rgb(51, 51, 51)" diff --git a/sharding-jdbc-doc/content/img/AlgorithmClass.900.png b/sharding-jdbc-doc/content/img/AlgorithmClass.900.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca80c9d0035ce8be3e9771c407d0a4aec5bae4d GIT binary patch literal 67255 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�KHa)+tGcVZ*PdP7HG6%({Vlim|0<={T8xv` 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 literal 0 HcmV?d00001 diff --git a/sharding-jdbc-doc/content/img/AlgorithmClass.png b/sharding-jdbc-doc/content/img/AlgorithmClass.png new file mode 100644 index 0000000000000000000000000000000000000000..a22ac2f38e8bd14fbc648d44843eee23213fc251 GIT binary patch literal 28716 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 literal 0 HcmV?d00001 diff --git a/sharding-jdbc-doc/content/img/StrategyClass.900.png b/sharding-jdbc-doc/content/img/StrategyClass.900.png new file mode 100644 index 0000000000000000000000000000000000000000..731d8d4af380fcf8881de2b76bb6cd51b85803e2 GIT binary patch literal 63651 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 literal 0 HcmV?d00001 diff --git a/sharding-jdbc-doc/content/img/architecture.png b/sharding-jdbc-doc/content/img/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..56d266efa8daf1ed09f98faf7afc69d375afdab4 GIT binary patch literal 188679 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

EiFvv1ORm6Kr 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

    s)G*#|+sJIIBI2!l*0(-yWI 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