diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/TextProtocolBackendHandlerFactory.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/TextProtocolBackendHandlerFactory.java index 2e68a8649a31c..c0929d29e6dbd 100644 --- a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/TextProtocolBackendHandlerFactory.java +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/TextProtocolBackendHandlerFactory.java @@ -82,6 +82,10 @@ public static TextProtocolBackendHandler newInstance(final DatabaseType database if (sqlStatement instanceof DistSQLStatement) { return DistSQLBackendHandlerFactory.newInstance(databaseType, (DistSQLStatement) sqlStatement, backendConnection); } + Optional databaseAdminBackendHandler = DatabaseAdminBackendHandlerFactory.newInstance(databaseType, sqlStatement, backendConnection, sql); + if (databaseAdminBackendHandler.isPresent()) { + return databaseAdminBackendHandler.get(); + } SQLStatementContext sqlStatementContext = SQLStatementContextFactory.newInstance( ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaDataMap(), Collections.emptyList(), sqlStatement, backendConnection.getDefaultSchemaName()); // TODO optimize SQLStatementSchemaHolder @@ -101,7 +105,7 @@ public static TextProtocolBackendHandler newInstance(final DatabaseType database if (sqlStatement instanceof CreateDatabaseStatement || sqlStatement instanceof DropDatabaseStatement) { return DatabaseOperateBackendHandlerFactory.newInstance(sqlStatement, backendConnection); } - Optional databaseAdminBackendHandler = DatabaseAdminBackendHandlerFactory.newInstance(databaseType, sqlStatement, backendConnection); + databaseAdminBackendHandler = DatabaseAdminBackendHandlerFactory.newInstance(databaseType, sqlStatement, backendConnection); return databaseAdminBackendHandler.orElseGet(() -> DatabaseBackendHandlerFactory.newInstance(sqlStatementContext, sql, backendConnection)); } diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/DatabaseAdminBackendHandlerFactory.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/DatabaseAdminBackendHandlerFactory.java index 24433abbb96df..9a954420b0b15 100644 --- a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/DatabaseAdminBackendHandlerFactory.java +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/DatabaseAdminBackendHandlerFactory.java @@ -43,8 +43,9 @@ public final class DatabaseAdminBackendHandlerFactory { } /** - * Create new instance of database admin backend handler. - * + * Create new instance of database admin backend handler, + * and this handler requires a connection containing a schema to be used. + * * @param databaseType database type * @param sqlStatement SQL statement * @param backendConnection backend connection @@ -55,7 +56,26 @@ public static Optional newInstance(final DatabaseTyp if (!executorFactory.isPresent()) { return Optional.empty(); } - Optional executor = executorFactory.get().newInstance(backendConnection.getSchemaName(), sqlStatement); + Optional executor = executorFactory.get().newInstance(sqlStatement); + return executor.map(optional -> createTextProtocolBackendHandler(sqlStatement, backendConnection, optional)); + } + + /** + * Create new instance of database admin backend handler. + * + * @param databaseType database type + * @param sqlStatement SQL statement + * @param backendConnection backend connection + * @param sql SQL being executed + * @return new instance of database admin backend handler + */ + public static Optional newInstance(final DatabaseType databaseType, final SQLStatement sqlStatement, + final BackendConnection backendConnection, final String sql) { + Optional executorFactory = TypedSPIRegistry.findRegisteredService(DatabaseAdminExecutorFactory.class, databaseType.getName(), new Properties()); + if (!executorFactory.isPresent()) { + return Optional.empty(); + } + Optional executor = executorFactory.get().newInstance(sqlStatement, sql); return executor.map(optional -> createTextProtocolBackendHandler(sqlStatement, backendConnection, optional)); } diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/DatabaseAdminUpdateBackendHandler.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/DatabaseAdminUpdateBackendHandler.java index efc6b4a9175a4..e9a73f48bad2c 100644 --- a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/DatabaseAdminUpdateBackendHandler.java +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/DatabaseAdminUpdateBackendHandler.java @@ -25,6 +25,8 @@ import org.apache.shardingsphere.proxy.backend.text.admin.executor.DatabaseAdminExecutor; import org.apache.shardingsphere.sql.parser.sql.common.statement.SQLStatement; +import java.sql.SQLException; + /** * Database admin update backend handler. */ @@ -38,7 +40,7 @@ public final class DatabaseAdminUpdateBackendHandler implements TextProtocolBack private final DatabaseAdminExecutor executor; @Override - public ResponseHeader execute() { + public ResponseHeader execute() throws SQLException { executor.execute(backendConnection); return new UpdateResponseHeader(sqlStatement); } diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/executor/DatabaseAdminExecutor.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/executor/DatabaseAdminExecutor.java index 7ad54813664a1..6f7ae194fcbc5 100644 --- a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/executor/DatabaseAdminExecutor.java +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/executor/DatabaseAdminExecutor.java @@ -19,6 +19,8 @@ import org.apache.shardingsphere.proxy.backend.communication.jdbc.connection.BackendConnection; +import java.sql.SQLException; + /** * Database admin executor. */ @@ -28,6 +30,7 @@ public interface DatabaseAdminExecutor { * Execute. * * @param backendConnection backend connection + * @throws SQLException SQLException */ - void execute(BackendConnection backendConnection); + void execute(BackendConnection backendConnection) throws SQLException; } diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/executor/DatabaseAdminExecutorFactory.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/executor/DatabaseAdminExecutorFactory.java index 92fa95c2916b1..b3265d606a122 100644 --- a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/executor/DatabaseAdminExecutorFactory.java +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/executor/DatabaseAdminExecutorFactory.java @@ -28,11 +28,20 @@ public interface DatabaseAdminExecutorFactory extends TypedSPI { /** - * New instance of database admin executor. - * - * @param currentSchema current schema + * Create an instance of database admin executor, + * and this executor requires a connection containing a schema to be used. + * * @param sqlStatement SQL statement * @return instance of database admin executor */ - Optional newInstance(String currentSchema, SQLStatement sqlStatement); + Optional newInstance(SQLStatement sqlStatement); + + /** + * Create an executor of database admin executor. + * + * @param sqlStatement SQL statement + * @param sql SQL + * @return instance of database admin executor + */ + Optional newInstance(SQLStatement sqlStatement, String sql); } diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/MySQLAdminExecutorFactory.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/MySQLAdminExecutorFactory.java index 589dbe275086f..bab31995b07f3 100644 --- a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/MySQLAdminExecutorFactory.java +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/MySQLAdminExecutorFactory.java @@ -47,28 +47,32 @@ public final class MySQLAdminExecutorFactory implements DatabaseAdminExecutorFac private static final String PERFORMANCE_SCHEMA = "performance_schema"; @Override - public Optional newInstance(final String currentSchema, final SQLStatement sqlStatement) { + public Optional newInstance(final SQLStatement sqlStatement) { + if (sqlStatement instanceof MySQLShowTablesStatement) { + return Optional.of(new ShowTablesExecutor((MySQLShowTablesStatement) sqlStatement)); + } + return Optional.empty(); + } + + @Override + public Optional newInstance(final SQLStatement sqlStatement, final String sql) { if (sqlStatement instanceof UseStatement) { return Optional.of(new UseDatabaseExecutor((UseStatement) sqlStatement)); } if (sqlStatement instanceof MySQLShowDatabasesStatement) { return Optional.of(new ShowDatabasesExecutor()); } - if (sqlStatement instanceof MySQLShowTablesStatement) { - return Optional.of(new ShowTablesExecutor((MySQLShowTablesStatement) sqlStatement)); - } if (sqlStatement instanceof MySQLShowProcessListStatement) { return Optional.of(new ShowProcessListExecutor()); } if (sqlStatement instanceof SelectStatement) { if (isShowCurrentDatabaseStatement((SelectStatement) sqlStatement)) { return Optional.of(new ShowCurrentDatabaseExecutor()); - } - if (isQueryInformationSchema(currentSchema, (SelectStatement) sqlStatement)) { - // TODO - return Optional.empty(); } - if (isQueryPerformanceSchema(currentSchema, (SelectStatement) sqlStatement)) { + if (isQueryInformationSchema((SelectStatement) sqlStatement)) { + return Optional.of(MySQLInformationSchemaExecutorFactory.newInstance((SelectStatement) sqlStatement, sql)); + } + if (isQueryPerformanceSchema((SelectStatement) sqlStatement)) { // TODO return Optional.empty(); } @@ -81,22 +85,19 @@ private boolean isShowCurrentDatabaseStatement(final SelectStatement sqlStatemen return firstProjection instanceof ExpressionProjectionSegment && ShowCurrentDatabaseExecutor.FUNCTION_NAME.equalsIgnoreCase(((ExpressionProjectionSegment) firstProjection).getText()); } - private boolean isQueryInformationSchema(final String currentSchema, final SelectStatement sqlStatement) { - return isQuerySpecialSchema(currentSchema, sqlStatement, INFORMATION_SCHEMA); + private boolean isQueryInformationSchema(final SelectStatement sqlStatement) { + return isQuerySpecialSchema(sqlStatement, INFORMATION_SCHEMA); } - private boolean isQueryPerformanceSchema(final String currentSchema, final SelectStatement sqlStatement) { - return isQuerySpecialSchema(currentSchema, sqlStatement, PERFORMANCE_SCHEMA); + private boolean isQueryPerformanceSchema(final SelectStatement sqlStatement) { + return isQuerySpecialSchema(sqlStatement, PERFORMANCE_SCHEMA); } - private boolean isQuerySpecialSchema(final String currentSchema, final SelectStatement sqlStatement, final String specialSchemaName) { + private boolean isQuerySpecialSchema(final SelectStatement sqlStatement, final String specialSchemaName) { TableSegment tableSegment = sqlStatement.getFrom(); if (!(tableSegment instanceof SimpleTableSegment)) { return false; } - if (specialSchemaName.equalsIgnoreCase(currentSchema) && !((SimpleTableSegment) tableSegment).getOwner().isPresent()) { - return true; - } return ((SimpleTableSegment) tableSegment).getOwner().isPresent() && specialSchemaName.equalsIgnoreCase(((SimpleTableSegment) tableSegment).getOwner().get().getIdentifier().getValue()); } diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/MySQLInformationSchemaExecutorFactory.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/MySQLInformationSchemaExecutorFactory.java new file mode 100644 index 0000000000000..44c9b8fe710a6 --- /dev/null +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/MySQLInformationSchemaExecutorFactory.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shardingsphere.proxy.backend.text.admin.mysql; + +import org.apache.shardingsphere.proxy.backend.text.admin.executor.DatabaseAdminQueryExecutor; +import org.apache.shardingsphere.proxy.backend.text.admin.mysql.executor.information.SelectSchemataExecutor; +import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.SimpleTableSegment; +import org.apache.shardingsphere.sql.parser.sql.common.statement.dml.SelectStatement; + +/** + * Construct the information schema executor's factory. + */ +public final class MySQLInformationSchemaExecutorFactory { + + public static final String SCHEMATA = "schemata"; + + /** + * Create executor. + * + * @param sqlStatement SQL statement + * @param sql SQL being executed + * @return executor + */ + public static DatabaseAdminQueryExecutor newInstance(final SelectStatement sqlStatement, final String sql) { + String tableName = ((SimpleTableSegment) sqlStatement.getFrom()).getTableName().getIdentifier().getValue(); + if (SCHEMATA.equalsIgnoreCase(tableName)) { + return new SelectSchemataExecutor(sqlStatement, sql); + } + throw new UnsupportedOperationException(String.format("unsupported table : `%s`", tableName)); + } + +} diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/enums/InformationSchemataEnum.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/enums/InformationSchemataEnum.java new file mode 100644 index 0000000000000..b1e08c6ea6754 --- /dev/null +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/enums/InformationSchemataEnum.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shardingsphere.proxy.backend.text.admin.mysql.enums; + +/** + * Enumeration of the fields in the schemata table of the information schema. + */ +public enum InformationSchemataEnum { + + CATALOG_NAME, + SCHEMA_NAME, + DEFAULT_CHARACTER_SET_NAME, + DEFAULT_COLLATION_NAME, + SQL_PATH, + DEFAULT_ENCRYPTION; +} diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/executor/information/SelectSchemataExecutor.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/executor/information/SelectSchemataExecutor.java new file mode 100644 index 0000000000000..940039deba385 --- /dev/null +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/main/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/executor/information/SelectSchemataExecutor.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shardingsphere.proxy.backend.text.admin.mysql.executor.information; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.QueryResult; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.QueryResultMetaData; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.impl.raw.metadata.RawQueryResultColumnMetaData; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.impl.raw.metadata.RawQueryResultMetaData; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.impl.raw.type.RawMemoryQueryResult; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.type.memory.row.MemoryQueryResultDataRow; +import org.apache.shardingsphere.infra.merge.result.MergedResult; +import org.apache.shardingsphere.infra.merge.result.impl.transparent.TransparentMergedResult; +import org.apache.shardingsphere.infra.metadata.resource.ShardingSphereResource; +import org.apache.shardingsphere.proxy.backend.communication.jdbc.connection.BackendConnection; +import org.apache.shardingsphere.proxy.backend.context.ProxyContext; +import org.apache.shardingsphere.proxy.backend.text.admin.executor.DatabaseAdminQueryExecutor; +import org.apache.shardingsphere.proxy.backend.text.admin.mysql.enums.InformationSchemataEnum; +import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.ColumnProjectionSegment; +import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.ProjectionSegment; +import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.ShorthandProjectionSegment; +import org.apache.shardingsphere.sql.parser.sql.common.statement.dml.SelectStatement; +import org.apache.shardingsphere.sql.parser.sql.common.value.identifier.IdentifierValue; + +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Schemata query executor. + */ +@Slf4j +public final class SelectSchemataExecutor implements DatabaseAdminQueryExecutor { + + @Getter + private QueryResultMetaData queryResultMetaData; + + @Getter + private MergedResult mergedResult; + + private final Map initResultSetMap; + + private final Map> schemaMap = new HashMap<>(); + + private final String sql; + + public SelectSchemataExecutor(final SelectStatement sqlStatement, final String sql) { + this.sql = sql; + Collection projections = sqlStatement.getProjections().getProjections(); + checkSegment(projections); + initResultSetMap = isShorthandSegment(projections) ? initResultSetMap() : initResultSetMap(projections); + } + + private void checkSegment(final Collection projections) { + if (!isShorthandSegment(projections) && projections.stream().anyMatch(each -> !(each instanceof ColumnProjectionSegment))) { + throw new UnsupportedOperationException(String.format("unsupported SQL : %s ", sql)); + } + } + + private Boolean isShorthandSegment(final Collection projections) { + return projections.stream().anyMatch(each -> each instanceof ShorthandProjectionSegment); + } + + private Map initResultSetMap(final Collection projections) { + return projections.stream().map(each -> { + IdentifierValue identifier = ((ColumnProjectionSegment) each).getColumn().getIdentifier(); + return identifier.getValue(); + }).collect(Collectors.toMap(each -> each, each -> "")); + } + + private Map initResultSetMap() { + return Arrays.stream(InformationSchemataEnum.values()).map(Enum::name).collect(Collectors.toMap(each -> each, each -> "")); + } + + @Override + public void execute(final BackendConnection backendConnection) throws SQLException { + for (String each : ProxyContext.getInstance().getAllSchemaNames()) { + Map resultSetMap = new HashMap<>(initResultSetMap); + schemaMap.put(each, resultSetMap); + ShardingSphereResource resource = ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData(each).getResource(); + Optional> dataSourceEntry = resource.getDataSources().entrySet().stream().findFirst(); + if (!dataSourceEntry.isPresent()) { + continue; + } + String catalog = resource.getDataSourcesMetaData().getDataSourceMetaData(dataSourceEntry.get().getKey()).getCatalog(); + log.info("Actual SQL: {} ::: {}", dataSourceEntry.get().getKey(), sql); + // TODO Splicing where catalog? + ResultSet resultSet = dataSourceEntry.get().getValue().getConnection().prepareStatement(sql).executeQuery(); + while (resultSet.next()) { + String actualDatabaseName = resultSet.getString(InformationSchemataEnum.SCHEMA_NAME.name()); + if (!catalog.equals(actualDatabaseName)) { + continue; + } + putInResultSetMap(resultSetMap, resultSet); + break; + } + } + mergedResult = new TransparentMergedResult(getQueryResult()); + queryResultMetaData = createQueryResultMetaData(); + } + + private void putInResultSetMap(final Map resultSetMap, final ResultSet resultSet) throws SQLException { + for (String each : resultSetMap.keySet()) { + resultSetMap.put(each, resultSet.getString(each)); + } + } + + private RawQueryResultMetaData createQueryResultMetaData() { + List columns = initResultSetMap.keySet().stream() + .map(each -> new RawQueryResultColumnMetaData("", each, each, Types.VARCHAR, "VARCHAR", 20, 0)) + .collect(Collectors.toList()); + return new RawQueryResultMetaData(columns); + } + + private QueryResult getQueryResult() { + List rows = schemaMap.entrySet().stream() + .map(this::replaceQueryResults) + .map(each -> new MemoryQueryResultDataRow(new ArrayList<>(each.getValue().values()))) + .collect(Collectors.toList()); + return new RawMemoryQueryResult(queryResultMetaData, rows); + } + + private Entry> replaceQueryResults(final Entry> entry) { + entry.getValue().forEach((key, value) -> { + if (InformationSchemataEnum.SCHEMA_NAME.name().equalsIgnoreCase(key)) { + entry.getValue().put(InformationSchemataEnum.SCHEMA_NAME.name(), entry.getKey()); + } + }); + return entry; + } +} diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/test/java/org/apache/shardingsphere/proxy/backend/TextProtocolBackendHandlerFactoryTest.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/test/java/org/apache/shardingsphere/proxy/backend/TextProtocolBackendHandlerFactoryTest.java index 49d9d01d307c8..8a479d39783c9 100644 --- a/shardingsphere-proxy/shardingsphere-proxy-backend/src/test/java/org/apache/shardingsphere/proxy/backend/TextProtocolBackendHandlerFactoryTest.java +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/test/java/org/apache/shardingsphere/proxy/backend/TextProtocolBackendHandlerFactoryTest.java @@ -207,6 +207,9 @@ public void assertNewInstanceWithQuery() throws SQLException { String sql = "select * from t_order limit 1"; TextProtocolBackendHandler actual = TextProtocolBackendHandlerFactory.newInstance(databaseType, sql, backendConnection); assertThat(actual, instanceOf(SchemaAssignedDatabaseBackendHandler.class)); + sql = "select * from information_schema.schemata limit 1"; + actual = TextProtocolBackendHandlerFactory.newInstance(databaseType, sql, backendConnection); + assertThat(actual, instanceOf(DatabaseAdminQueryBackendHandler.class)); } @Test diff --git a/shardingsphere-proxy/shardingsphere-proxy-backend/src/test/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/executor/information/SelectSchemataExecutorTest.java b/shardingsphere-proxy/shardingsphere-proxy-backend/src/test/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/executor/information/SelectSchemataExecutorTest.java new file mode 100644 index 0000000000000..efcfe154e3654 --- /dev/null +++ b/shardingsphere-proxy/shardingsphere-proxy-backend/src/test/java/org/apache/shardingsphere/proxy/backend/text/admin/mysql/executor/information/SelectSchemataExecutorTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shardingsphere.proxy.backend.text.admin.mysql.executor.information; + +import com.zaxxer.hikari.pool.HikariProxyResultSet; +import org.apache.shardingsphere.infra.parser.ShardingSphereSQLParserEngine; +import org.apache.shardingsphere.mode.manager.ContextManager; +import org.apache.shardingsphere.proxy.backend.communication.jdbc.connection.BackendConnection; +import org.apache.shardingsphere.proxy.backend.context.ProxyContext; +import org.apache.shardingsphere.proxy.backend.text.admin.mysql.enums.InformationSchemataEnum; +import org.apache.shardingsphere.sql.parser.sql.common.statement.SQLStatement; +import org.apache.shardingsphere.sql.parser.sql.common.statement.dml.SelectStatement; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public final class SelectSchemataExecutorTest { + + private static final String SQL = "SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA"; + + private SelectSchemataExecutor selectSchemataExecutor; + + @Before + public void setUp() throws IllegalAccessException, NoSuchFieldException, SQLException { + SQLStatement sqlStatement = new ShardingSphereSQLParserEngine("MySQL").parse(SQL, false); + selectSchemataExecutor = new SelectSchemataExecutor((SelectStatement) sqlStatement, SQL); + ResultSet resultSet = mock(HikariProxyResultSet.class); + when(resultSet.getString(InformationSchemataEnum.SCHEMA_NAME.name())).thenReturn("demo_ds_0"); + when(resultSet.getString(InformationSchemataEnum.DEFAULT_CHARACTER_SET_NAME.name())).thenReturn("utf8mb4"); + when(resultSet.getString(InformationSchemataEnum.DEFAULT_COLLATION_NAME.name())).thenReturn("utf8mb4_0900_ai_ci"); + when(resultSet.next()).thenReturn(true); + ContextManager contextManager = mock(ContextManager.class, RETURNS_DEEP_STUBS); + when(contextManager.getMetaDataContexts().getAllSchemaNames()).thenReturn(Collections.singletonList("sharding_db")); + when(contextManager.getMetaDataContexts().getMetaData("sharding_db").getResource().getDataSourcesMetaData().getDataSourceMetaData("ds_0").getCatalog()).thenReturn("demo_ds_0"); + Map datasourceMap = mockDatasourceMap(resultSet); + when(contextManager.getMetaDataContexts().getMetaData("sharding_db").getResource().getDataSources()).thenReturn(datasourceMap); + ProxyContext.getInstance().init(contextManager); + } + + private Map mockDatasourceMap(final ResultSet resultSet) throws SQLException { + DataSource dataSource = mock(DataSource.class, RETURNS_DEEP_STUBS); + when(dataSource.getConnection().prepareStatement(SQL).executeQuery()).thenReturn(resultSet); + Map dataSourceMap = new HashMap<>(); + dataSourceMap.put("ds_0", dataSource); + return dataSourceMap; + } + + @Test + public void assertExecute() throws SQLException { + selectSchemataExecutor.execute(mockBackendConnection()); + assertThat(selectSchemataExecutor.getQueryResultMetaData().getColumnCount(), is(3)); + while (selectSchemataExecutor.getMergedResult().next()) { + assertThat(selectSchemataExecutor.getMergedResult().getValue(1, String.class), is("sharding_db")); + assertThat(selectSchemataExecutor.getMergedResult().getValue(2, String.class), is("utf8mb4_0900_ai_ci")); + assertThat(selectSchemataExecutor.getMergedResult().getValue(3, String.class), is("utf8mb4")); + } + } + + private BackendConnection mockBackendConnection() { + BackendConnection result = mock(BackendConnection.class); + return result; + } +}