Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.apache.shardingsphere.database.connector.core.metadata.database.system.SystemDatabase;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
import org.apache.shardingsphere.database.connector.core.metadata.manager.DialectSystemTableManager;
import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;

import javax.sql.DataSource;
import java.sql.Connection;
Expand Down Expand Up @@ -124,6 +126,7 @@ private Collection<String> loadTableNames(final Connection connection, final Str
}

private boolean isSystemTable(final String table) {
return table.contains("$") || table.contains("/") || table.contains("##");
return DatabaseTypedSPILoader.findService(DialectSystemTableManager.class, databaseType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can add a engine class to hide the details of SPI loading so that other scenarios can directly call the Engine. You can refer to the logic of SQLRouteEngine.

.map(optional -> optional.isSystemTable(table)).orElseGet(() -> table.contains("$") || table.contains("/") || table.contains("##"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.database.connector.core.metadata.manager;

import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPI;
import org.apache.shardingsphere.infra.spi.annotation.SingletonSPI;

/**
* Dialect system table manager.
*/
@SingletonSPI
public interface DialectSystemTableManager extends DatabaseTypedSPI {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think DialectSystemTableDetector is better? The meaning of Manager is broader, and we only want to add a class to detect whether a table is a system table.


/**
* Judge whether the table is system table or not.
*
* @param tableName table name
* @return whether the table is system table or not
*/
default boolean isSystemTable(final String tableName) {
return SystemTableManager.isSystemTable(getDatabaseType(), null, tableName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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.database.connector.core.metadata.manager;

import com.cedarsoftware.util.CaseInsensitiveMap;
import com.cedarsoftware.util.CaseInsensitiveSet;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.infra.util.directory.ClasspathResourceDirectoryReader;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* System table manager.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SystemTableManager {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SystemTableDetector maybe better.


private static final Map<String, Map<String, Collection<String>>> DATABASE_TYPE_SCHEMA_TABLE_MAP;

private static final String COMMON = "common";

static {
List<String> resourceNames;
try (Stream<String> resourceNameStream = ClasspathResourceDirectoryReader.read("schema")) {
resourceNames = resourceNameStream.filter(each -> each.endsWith(".yaml")).collect(Collectors.toList());
}
DATABASE_TYPE_SCHEMA_TABLE_MAP = resourceNames.stream().map(resourceName -> resourceName.split("/")).filter(each -> each.length == 4)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put 4 costant to the left hand of each.length == 4

.collect(Collectors.groupingBy(path -> path[1], CaseInsensitiveMap::new, Collectors.groupingBy(path -> path[2], CaseInsensitiveMap::new,
Collectors.mapping(path -> StringUtils.removeEnd(path[3], ".yaml"), Collectors.toCollection(CaseInsensitiveSet::new)))));
}

/**
* Judge whether current table is system table or not.
*
* @param schema schema
* @param tableName table name
* @return whether current table is system table or not
*/
// TODO check if this function is needed, since it's only used in SimpleTableSegmentBinder and could be replaced by SystemTableManager.isSystemTable(databaseType). (issues/36462)
public static boolean isSystemTable(final String schema, final String tableName) {
for (Map.Entry<String, Map<String, Collection<String>>> entry : DATABASE_TYPE_SCHEMA_TABLE_MAP.entrySet()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Entry to replace Map.Entry

if (Optional.ofNullable(entry.getValue().get(schema)).map(tables -> tables.contains(tableName)).orElse(false)) {
return true;
}
}
return false;
}

/**
* Judge whether current table is system table or not.
*
* @param databaseType database type
* @param schema schema
* @param tableName table name
* @return whether current table is system table or not
*/
public static boolean isSystemTable(final String databaseType, final String schema, final String tableName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These logics seem very common. As long as the system table is configured under resource, they can work normally?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the case, perhaps we can refactor it without SPI, as long as we ensure that the database without configuring the system table YAML file can go to the default logic.

Map<String, Collection<String>> schemaTableMap = DATABASE_TYPE_SCHEMA_TABLE_MAP.getOrDefault(databaseType, Collections.emptyMap());
Map<String, Collection<String>> commonTableMap = DATABASE_TYPE_SCHEMA_TABLE_MAP.getOrDefault(COMMON, Collections.emptyMap());
if (null == schema) {
return schemaTableMap.values().stream().anyMatch(each -> each.contains(tableName)) || commonTableMap.values().stream().anyMatch(each -> each.contains(tableName));
}
return schemaTableMap.getOrDefault(schema, Collections.emptyList()).contains(tableName) || commonTableMap.getOrDefault(schema, Collections.emptyList()).contains(tableName);
}

/**
* Judge whether current table is system table or not.
*
* @param databaseType database type
* @param schema schema
* @param tableNames table names
* @return whether current table is system table or not
*/
public static boolean isSystemTable(final String databaseType, final String schema, final Collection<String> tableNames) {
Collection<String> databaseTypeTables = Optional.ofNullable(DATABASE_TYPE_SCHEMA_TABLE_MAP.get(databaseType)).map(schemas -> schemas.get(schema)).orElse(Collections.emptyList());
Collection<String> commonTables = Optional.ofNullable(DATABASE_TYPE_SCHEMA_TABLE_MAP.get(COMMON)).map(schemas -> schemas.get(schema)).orElse(Collections.emptyList());
for (String each : tableNames) {
if (!databaseTypeTables.contains(each) && !commonTables.contains(each)) {
return false;
}
}
return true;
}

/**
* Get tables.
*
* @param databaseType database type
* @param schema schema
* @return optional tables
*/
public static Collection<String> getTables(final String databaseType, final String schema) {
Collection<String> result = new LinkedList<>();
Optional.ofNullable(DATABASE_TYPE_SCHEMA_TABLE_MAP.get(databaseType)).map(schemas -> schemas.get(schema)).ifPresent(result::addAll);
Optional.ofNullable(DATABASE_TYPE_SCHEMA_TABLE_MAP.get(COMMON)).map(schemas -> schemas.get(schema)).ifPresent(result::addAll);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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.database.connector.core.metadata.manager;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;

import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.Collection;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class SystemTableManagerTest {

private static ClassLoader originalClassLoader;

private static URLClassLoader schemaClassLoader;

@BeforeAll
static void setUp() throws Exception {
originalClassLoader = Thread.currentThread().getContextClassLoader();
URL[] urls = {
Paths.get("../dialect/mysql/src/main/resources").toUri().toURL(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write some FIXTUREs and test configurations specifically for testing to test the logic of SystemTableManager without actually loading the dialect module.

Paths.get("../dialect/postgresql/src/main/resources").toUri().toURL(),
Paths.get("../dialect/opengauss/src/main/resources").toUri().toURL(),
Paths.get("../dialect/firebird/src/main/resources").toUri().toURL()
};
schemaClassLoader = new URLClassLoader(urls, originalClassLoader);
Thread.currentThread().setContextClassLoader(schemaClassLoader);
Class.forName(SystemTableManager.class.getName(), true, schemaClassLoader);
}

@AfterAll
static void tearDown() throws Exception {
Thread.currentThread().setContextClassLoader(originalClassLoader);
schemaClassLoader.close();
}

@Test
void assertValueOfSchemaPathSuccess() {
Collection<String> actualInformationSchema = SystemTableManager.getTables("MySQL", "information_schema");
assertThat(actualInformationSchema.size(), is(95));
Collection<String> actualMySQLSchema = SystemTableManager.getTables("MySQL", "mysql");
assertThat(actualMySQLSchema.size(), is(40));
Collection<String> actualPerformanceSchema = SystemTableManager.getTables("MySQL", "performance_schema");
assertThat(actualPerformanceSchema.size(), is(114));
Collection<String> actualSysSchema = SystemTableManager.getTables("MySQL", "sys");
assertThat(actualSysSchema.size(), is(53));
Collection<String> actualShardingSphereSchema = SystemTableManager.getTables("MySQL", "shardingsphere");
assertThat(actualShardingSphereSchema.size(), is(1));
Collection<String> actualPgInformationSchema = SystemTableManager.getTables("PostgreSQL", "information_schema");
assertThat(actualPgInformationSchema.size(), is(69));
Collection<String> actualPgCatalog = SystemTableManager.getTables("PostgreSQL", "pg_catalog");
assertThat(actualPgCatalog.size(), is(134));
Collection<String> actualOgInformationSchema = SystemTableManager.getTables("openGauss", "information_schema");
assertThat(actualOgInformationSchema.size(), is(66));
Collection<String> actualOgPgCatalog = SystemTableManager.getTables("openGauss", "pg_catalog");
assertThat(actualOgPgCatalog.size(), is(240));
Collection<String> actualFbCatalog = SystemTableManager.getTables("Firebird", "system_tables");
assertThat(actualFbCatalog.size(), is(50));
}

@Test
void assertIsSystemTable() {
assertTrue(SystemTableManager.isSystemTable("information_schema", "columns"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_database"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_tables"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_aggregate"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_am"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_amop"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_amproc"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_attrdef"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_attribute"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_auth_members"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_authid"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_available_extension_versions"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_available_extensions"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_backend_memory_contexts"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_cast"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_range"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_replication_origin"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_rewrite"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_seclabel"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_sequence"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_roles"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_user_mapping"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_stat_database_conflicts"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_stat_gssapi"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_stat_progress_analyze"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_stat_progress_basebackup"));
assertTrue(SystemTableManager.isSystemTable("pg_catalog", "pg_stat_progress_cluster"));
assertFalse(SystemTableManager.isSystemTable("sharding_db", "t_order"));
assertTrue(SystemTableManager.isSystemTable("shardingsphere", "cluster_information"));
assertFalse(SystemTableManager.isSystemTable("shardingsphere", "nonexistent"));
assertTrue(SystemTableManager.isSystemTable("system_tables", "RDB$functions"));
assertTrue(SystemTableManager.isSystemTable("system_tables", "RDB$RELATIONS"));
assertFalse(SystemTableManager.isSystemTable("system_tables", "RDB$REL"));
}
}
Original file line number Diff line number Diff line change
@@ -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.database.connector.firebird.metadata.manager;

import org.apache.shardingsphere.database.connector.core.metadata.manager.DialectSystemTableManager;

/**
* Firebird system table manager.
*/
public final class FirebirdSystemTableManager implements DialectSystemTableManager {

@Override
public String getDatabaseType() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the implementation classes of these SPIs are empty. Based on this, I suggest removing the SPI interface and implementation class and encapsulating these logics through a SystemTableDetector.

return "Firebird";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# 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.
#

org.apache.shardingsphere.database.connector.firebird.metadata.manager.FirebirdSystemTableManager
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ void setUp() throws SQLException {

private ResultSet mockTableResultSet() throws SQLException {
ResultSet result = mock(ResultSet.class);
when(result.next()).thenReturn(true, true, true, true, true, false);
when(result.next()).thenReturn(true, true, true, true, true, true, true, true, false);
when(result.getString("TABLE_NAME")).thenReturn("tbl", "$tbl", "/tbl", "##tbl", "partitioned_tbl", "RDB$RELATIONS", "RDB$REL", "RDB_Relations", "rdb$functions");
return result;
}
Expand All @@ -80,7 +80,8 @@ private ResultSet mockSchemaResultSet() throws SQLException {

@Test
void assertLoadSchemaTableNames() throws SQLException {
Map<String, Collection<String>> schemaTableNames = Collections.singletonMap("foo_db", new CaseInsensitiveSet<>(Arrays.asList("tbl", "partitioned_tbl")));
Map<String, Collection<String>> schemaTableNames =
Collections.singletonMap("foo_db", new CaseInsensitiveSet<>(Arrays.asList("tbl", "$tbl", "/tbl", "##tbl", "partitioned_tbl", "RDB$REL", "RDB_Relations")));
assertThat(new SchemaMetaDataLoader(databaseType).loadSchemaTableNames("foo_db", dataSource, Collections.emptyList()), is(schemaTableNames));
}

Expand Down
Loading
Loading