diff --git a/distribution/bom/pom.xml b/distribution/bom/pom.xml index 878ce91132430..a5c29fe7723d9 100644 --- a/distribution/bom/pom.xml +++ b/distribution/bom/pom.xml @@ -195,6 +195,11 @@ shardingsphere-infra-binder-opengauss ${project.version} + + org.apache.shardingsphere + shardingsphere-infra-binder-firebird + ${project.version} + org.apache.shardingsphere shardingsphere-infra-checker diff --git a/infra/binder/dialect/firebird/pom.xml b/infra/binder/dialect/firebird/pom.xml new file mode 100644 index 0000000000000..81ed0a9dc1b43 --- /dev/null +++ b/infra/binder/dialect/firebird/pom.xml @@ -0,0 +1,36 @@ + + + + + 4.0.0 + + org.apache.shardingsphere + shardingsphere-infra-binder-dialect + 5.5.3-SNAPSHOT + + shardingsphere-infra-binder-firebird + ${project.artifactId} + + + + org.apache.shardingsphere + shardingsphere-infra-binder-core + ${project.version} + + + diff --git a/infra/binder/dialect/firebird/src/main/java/org/apache/shardingsphere/infra/binder/firebird/FirebirdProjectionIdentifierExtractor.java b/infra/binder/dialect/firebird/src/main/java/org/apache/shardingsphere/infra/binder/firebird/FirebirdProjectionIdentifierExtractor.java new file mode 100644 index 0000000000000..800c91e17f243 --- /dev/null +++ b/infra/binder/dialect/firebird/src/main/java/org/apache/shardingsphere/infra/binder/firebird/FirebirdProjectionIdentifierExtractor.java @@ -0,0 +1,54 @@ +/* + * 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.infra.binder.firebird; + +import org.apache.shardingsphere.infra.binder.context.segment.select.projection.extractor.DialectProjectionIdentifierExtractor; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.SubqueryProjectionSegment; +import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue; + +/** + * Projection identifier extractor for Firebird. + */ +public final class FirebirdProjectionIdentifierExtractor implements DialectProjectionIdentifierExtractor { + + @Override + public String getIdentifierValue(final IdentifierValue identifierValue) { + return identifierValue.getValue(); + } + + @Override + public String getColumnNameFromFunction(final String functionName, final String functionExpression) { + return functionName; + } + + @Override + public String getColumnNameFromExpression(final ExpressionSegment expressionSegment) { + return expressionSegment.getText(); + } + + @Override + public String getColumnNameFromSubquery(final SubqueryProjectionSegment subquerySegment) { + return subquerySegment.getText(); + } + + @Override + public String getDatabaseType() { + return "Firebird"; + } +} diff --git a/infra/binder/dialect/firebird/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.binder.context.segment.select.projection.extractor.DialectProjectionIdentifierExtractor b/infra/binder/dialect/firebird/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.binder.context.segment.select.projection.extractor.DialectProjectionIdentifierExtractor new file mode 100644 index 0000000000000..4dd472289ba42 --- /dev/null +++ b/infra/binder/dialect/firebird/src/main/resources/META-INF/services/org.apache.shardingsphere.infra.binder.context.segment.select.projection.extractor.DialectProjectionIdentifierExtractor @@ -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.infra.binder.firebird.FirebirdProjectionIdentifierExtractor diff --git a/infra/binder/dialect/firebird/src/test/java/org/apache/shardingsphere/infra/binder/firebird/FirebirdProjectionIdentifierExtractorTest.java b/infra/binder/dialect/firebird/src/test/java/org/apache/shardingsphere/infra/binder/firebird/FirebirdProjectionIdentifierExtractorTest.java new file mode 100644 index 0000000000000..a5e5782984237 --- /dev/null +++ b/infra/binder/dialect/firebird/src/test/java/org/apache/shardingsphere/infra/binder/firebird/FirebirdProjectionIdentifierExtractorTest.java @@ -0,0 +1,72 @@ +/* + * 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.infra.binder.firebird; + +import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader; +import org.apache.shardingsphere.database.connector.core.type.DatabaseType; +import org.apache.shardingsphere.infra.binder.context.segment.select.projection.extractor.DialectProjectionIdentifierExtractor; +import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.AggregationProjection; +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.sql.parser.statement.core.enums.AggregationType; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.AggregationProjectionSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.SubqueryProjectionSegment; +import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class FirebirdProjectionIdentifierExtractorTest { + + private final DatabaseType databaseType = TypedSPILoader.getService(DatabaseType.class, "Firebird"); + + private final DialectProjectionIdentifierExtractor extractor = DatabaseTypedSPILoader.getService(DialectProjectionIdentifierExtractor.class, databaseType); + + @Test + void assertGetIdentifierValue() { + assertThat(extractor.getIdentifierValue(new IdentifierValue("value")), is("value")); + } + + @Test + void assertGetColumnNameFromFunction() { + assertThat(extractor.getColumnNameFromFunction("COUNT", "COUNT(*)"), is("COUNT")); + } + + @Test + void assertGetColumnNameFromExpression() { + ExpressionSegment expressionSegment = mock(ExpressionSegment.class); + when(expressionSegment.getText()).thenReturn("expr"); + assertThat(extractor.getColumnNameFromExpression(expressionSegment), is("expr")); + } + + @Test + void assertGetColumnNameFromSubquery() { + SubqueryProjectionSegment subquerySegment = mock(SubqueryProjectionSegment.class); + when(subquerySegment.getText()).thenReturn("sub"); + assertThat(extractor.getColumnNameFromSubquery(subquerySegment), is("sub")); + } + + @Test + void assertAggregationProjectionColumnLabelWithoutAlias() { + AggregationProjection projection = new AggregationProjection(AggregationType.COUNT, new AggregationProjectionSegment(0, 0, AggregationType.COUNT, "COUNT(*)"), null, databaseType); + assertThat(projection.getColumnLabel(), is("COUNT")); + } +} diff --git a/infra/binder/dialect/pom.xml b/infra/binder/dialect/pom.xml index ac4fdba1ce5e1..e0c12d58475c9 100644 --- a/infra/binder/dialect/pom.xml +++ b/infra/binder/dialect/pom.xml @@ -33,5 +33,6 @@ oracle sqlserver opengauss + firebird diff --git a/jdbc-dialect/firebird/pom.xml b/jdbc-dialect/firebird/pom.xml index 0c577f9ec43c1..d6fe9cead16ee 100644 --- a/jdbc-dialect/firebird/pom.xml +++ b/jdbc-dialect/firebird/pom.xml @@ -33,5 +33,11 @@ ${project.version} runtime + + org.apache.shardingsphere + shardingsphere-infra-binder-firebird + ${project.version} + runtime + diff --git a/proxy/dialect/firebird/pom.xml b/proxy/dialect/firebird/pom.xml index 38bff61ce2ab7..6a22757f44d93 100644 --- a/proxy/dialect/firebird/pom.xml +++ b/proxy/dialect/firebird/pom.xml @@ -40,6 +40,12 @@ ${project.version} runtime + + org.apache.shardingsphere + shardingsphere-infra-binder-firebird + ${project.version} + runtime + org.apache.shardingsphere diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java index e80c774d1468e..7a425dd11d8b2 100644 --- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java +++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java @@ -89,6 +89,7 @@ import java.util.LinkedList; import java.util.Map; import java.util.OptionalInt; +import java.util.Locale; /** * Firebird prepare transaction command executor. @@ -413,13 +414,14 @@ private void processExpressionProjection(final ExpressionProjection expr, final private int getFunctionType(final String functionName) { // TODO add proper coalesce and other conditional functions return types - switch (functionName) { + switch (functionName.toLowerCase(Locale.ENGLISH)) { case "substring": case "current_role": case "current_user": case "coalesce": return Types.VARCHAR; case "gen_id": + case "count": return Types.BIGINT; case "current_timestamp": return Types.TIMESTAMP; diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutorTest.java index 77ca09d0714dc..aa517dbe1c19f 100644 --- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutorTest.java +++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutorTest.java @@ -18,11 +18,14 @@ package org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.prepare; import org.apache.shardingsphere.database.connector.core.type.DatabaseType; +import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType; import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.info.type.sql.FirebirdSQLInfoPacketType; import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.info.type.sql.FirebirdSQLInfoReturnValue; import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.statement.prepare.FirebirdPrepareStatementPacket; import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.statement.prepare.FirebirdPrepareStatementReturnPacket; +import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.statement.prepare.FirebirdReturnColumnPacket; import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket; +import org.apache.shardingsphere.database.protocol.firebird.payload.FirebirdPacketPayload; import org.apache.shardingsphere.database.protocol.packet.DatabasePacket; import org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectStatementContext; import org.apache.shardingsphere.infra.config.props.ConfigurationProperties; @@ -31,9 +34,13 @@ import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase; import org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData; import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData; +import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn; import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema; +import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable; import org.apache.shardingsphere.infra.metadata.statistics.ShardingSphereStatistics; +import org.apache.shardingsphere.infra.metadata.user.Grantee; import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.infra.session.connection.ConnectionContext; import org.apache.shardingsphere.mode.metadata.MetaDataContexts; import org.apache.shardingsphere.parser.config.SQLParserRuleConfiguration; import org.apache.shardingsphere.parser.rule.SQLParserRule; @@ -49,9 +56,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import java.sql.Types; import java.util.Collection; import java.util.Collections; import java.util.Properties; @@ -59,6 +68,8 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(AutoMockExtension.class) @@ -76,8 +87,10 @@ class FirebirdPrepareStatementCommandExecutorTest { @BeforeEach void setUp() { + ConnectionContext connectionContext = new ConnectionContext(Collections::emptySet, new Grantee("foo_user")); when(connectionSession.getServerPreparedStatementRegistry()).thenReturn(new ServerPreparedStatementRegistry()); when(connectionSession.getCurrentDatabaseName()).thenReturn("foo_db"); + when(connectionSession.getConnectionContext()).thenReturn(connectionContext); when(packet.getSQL()).thenReturn("SELECT 1"); when(packet.getHintValueContext()).thenReturn(new HintValueContext()); when(packet.isValidStatementHandle()).thenReturn(true); @@ -90,7 +103,9 @@ void setUp() { private MetaDataContexts createMetaDataContexts() { SQLParserRule parserRule = new SQLParserRule(new SQLParserRuleConfiguration(new CacheOption(128, 1024L), new CacheOption(128, 1024L))); RuleMetaData globalRuleMetaData = new RuleMetaData(Collections.singleton(parserRule)); - ShardingSphereSchema schema = new ShardingSphereSchema("foo_db", Collections.emptyList(), Collections.emptyList()); + ShardingSphereColumn column = new ShardingSphereColumn("id", Types.INTEGER, false, false, true, true, false, true); + ShardingSphereTable table = new ShardingSphereTable("foo_tbl", Collections.singleton(column), Collections.emptyList(), Collections.emptyList()); + ShardingSphereSchema schema = new ShardingSphereSchema("foo_db", Collections.singleton(table), Collections.emptyList()); ShardingSphereDatabase database = new ShardingSphereDatabase( "foo_db", databaseType, new ResourceMetaData(Collections.emptyMap()), new RuleMetaData(Collections.emptyList()), Collections.singleton(schema)); ShardingSphereMetaData metaData = new ShardingSphereMetaData( @@ -109,4 +124,26 @@ void assertExecute() { assertThat(preparedStatement.getSql(), is("SELECT 1")); assertThat(preparedStatement.getSqlStatementContext(), isA(SelectStatementContext.class)); } + + @Test + void assertDescribeCountReturnsBigintType() { + when(packet.getSQL()).thenReturn("SELECT COUNT(*) FROM foo_tbl"); + when(packet.nextItem()).thenReturn(true, true, true, true, true, false); + when(packet.getCurrentItem()).thenReturn( + FirebirdSQLInfoPacketType.STMT_TYPE, + FirebirdSQLInfoPacketType.SELECT, + FirebirdSQLInfoPacketType.TYPE, + FirebirdSQLInfoPacketType.TYPE, + FirebirdSQLInfoPacketType.DESCRIBE_END, + FirebirdSQLInfoPacketType.DESCRIBE_END); + FirebirdPrepareStatementCommandExecutor executor = new FirebirdPrepareStatementCommandExecutor(packet, connectionSession); + Collection actual = executor.execute(); + FirebirdGenericResponsePacket responsePacket = (FirebirdGenericResponsePacket) actual.iterator().next(); + FirebirdPrepareStatementReturnPacket returnPacket = (FirebirdPrepareStatementReturnPacket) responsePacket.getData(); + assertThat(returnPacket.getDescribeSelect().size(), is(1)); + FirebirdPacketPayload payload = mock(FirebirdPacketPayload.class, Mockito.RETURNS_DEEP_STUBS); + FirebirdReturnColumnPacket columnPacket = returnPacket.getDescribeSelect().get(0); + columnPacket.write(payload); + verify(payload).writeInt4LE(FirebirdBinaryColumnType.INT64.getValue() + 1); + } }