Skip to content

Commit c65bd4e

Browse files
committed
Add GraalVM Reachability Metadata and corresponding nativeTest for Firebird
1 parent 3191042 commit c65bd4e

File tree

18 files changed

+616
-21
lines changed

18 files changed

+616
-21
lines changed

RELEASE-NOTES.md

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
1. Agent: Simplify the use of Agent's Docker Image - [#33356](https://github.com/apache/shardingsphere/pull/33356)
5656
1. Mode: Support modifying Hikari-CP configurations via props in standalone mode [#34185](https://github.com/apache/shardingsphere/pull/34185)
5757
1. Encrypt: Support insert statement rewrite use quote [#34259](https://github.com/apache/shardingsphere/pull/34259)
58+
1. Proxy Native: Add GraalVM Reachability Metadata and corresponding nativeTest for Firebird - [#34307](https://github.com/apache/shardingsphere/pull/34307)
5859

5960
### Bug Fixes
6061

infra/database/type/firebird/pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@
3333
<version>${project.version}</version>
3434
</dependency>
3535

36+
<dependency>
37+
<groupId>org.firebirdsql.jdbc</groupId>
38+
<artifactId>jaybird</artifactId>
39+
<scope>provided</scope>
40+
<optional>true</optional>
41+
</dependency>
42+
3643
<dependency>
3744
<groupId>org.apache.shardingsphere</groupId>
3845
<artifactId>shardingsphere-test-util</artifactId>

infra/database/type/firebird/src/main/java/org/apache/shardingsphere/infra/database/firebird/connector/FirebirdConnectionPropertiesParser.java

+15-6
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717

1818
package org.apache.shardingsphere.infra.database.firebird.connector;
1919

20+
import lombok.SneakyThrows;
2021
import org.apache.shardingsphere.infra.database.core.connector.ConnectionProperties;
2122
import org.apache.shardingsphere.infra.database.core.connector.ConnectionPropertiesParser;
2223
import org.apache.shardingsphere.infra.database.core.connector.StandardConnectionProperties;
23-
import org.apache.shardingsphere.infra.database.core.connector.url.JdbcUrl;
24-
import org.apache.shardingsphere.infra.database.core.connector.url.StandardJdbcUrlParser;
24+
import org.firebirdsql.gds.impl.DbAttachInfo;
25+
import org.firebirdsql.gds.impl.GDSFactory;
26+
import org.firebirdsql.gds.impl.GDSType;
27+
import org.firebirdsql.jdbc.FBDriver;
2528

2629
import java.util.Properties;
2730

@@ -30,12 +33,18 @@
3033
*/
3134
public final class FirebirdConnectionPropertiesParser implements ConnectionPropertiesParser {
3235

33-
private static final int DEFAULT_PORT = 3050;
34-
36+
@SneakyThrows(Exception.class)
3537
@Override
3638
public ConnectionProperties parse(final String url, final String username, final String catalog) {
37-
JdbcUrl jdbcUrl = new StandardJdbcUrlParser().parse(url);
38-
return new StandardConnectionProperties(jdbcUrl.getHostname(), jdbcUrl.getPort(DEFAULT_PORT), jdbcUrl.getDatabase(), null, jdbcUrl.getQueryProperties(), new Properties());
39+
GDSType type = GDSFactory.getTypeForProtocol(url);
40+
String databaseURL = GDSFactory.getDatabasePath(type, url);
41+
DbAttachInfo dbAttachInfo = DbAttachInfo.parseConnectString(databaseURL);
42+
String attachObjectName = dbAttachInfo.getAttachObjectName();
43+
String databaseName = attachObjectName.contains("?") ? attachObjectName.split("\\?")[0] : attachObjectName;
44+
Properties queryProperties = new Properties();
45+
queryProperties.putAll(FBDriver.normalizeProperties(url, new Properties()));
46+
return new StandardConnectionProperties(dbAttachInfo.getServerName(), dbAttachInfo.getPortNumber(),
47+
databaseName, null, queryProperties, new Properties());
3948
}
4049

4150
@Override

infra/database/type/firebird/src/test/java/org/apache/shardingsphere/infra/database/firebird/connector/FirebirdConnectionPropertiesParserTest.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,24 @@
1919

2020
import org.apache.shardingsphere.infra.database.core.connector.ConnectionProperties;
2121
import org.apache.shardingsphere.infra.database.core.connector.ConnectionPropertiesParser;
22-
import org.apache.shardingsphere.infra.database.core.exception.UnrecognizedDatabaseURLException;
2322
import org.apache.shardingsphere.infra.database.core.spi.DatabaseTypedSPILoader;
2423
import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
2524
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
25+
import org.apache.shardingsphere.test.util.PropertiesBuilder;
2626
import org.junit.jupiter.api.Test;
2727
import org.junit.jupiter.api.extension.ExtensionContext;
2828
import org.junit.jupiter.params.ParameterizedTest;
2929
import org.junit.jupiter.params.provider.Arguments;
3030
import org.junit.jupiter.params.provider.ArgumentsProvider;
3131
import org.junit.jupiter.params.provider.ArgumentsSource;
3232

33+
import java.sql.SQLNonTransientConnectionException;
3334
import java.util.Properties;
3435
import java.util.stream.Stream;
3536

3637
import static org.hamcrest.CoreMatchers.is;
3738
import static org.hamcrest.MatcherAssert.assertThat;
39+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
3840
import static org.junit.jupiter.api.Assertions.assertThrows;
3941

4042
class FirebirdConnectionPropertiesParserTest {
@@ -54,14 +56,29 @@ void assertNewConstructor(final String name, final String url, final String host
5456

5557
@Test
5658
void assertNewConstructorFailure() {
57-
assertThrows(UnrecognizedDatabaseURLException.class, () -> parser.parse("jdbc:firebirdsql:xxxxxxxx", null, null));
59+
assertDoesNotThrow(() -> parser.parse("jdbc:firebirdsql:xxxxxxxx", null, null));
60+
assertThrows(SQLNonTransientConnectionException.class, () -> parser.parse("jdbc:firebirdsql://localhost:c:/data/db/test.fdb", null, null));
5861
}
5962

6063
private static class NewConstructorTestCaseArgumentsProvider implements ArgumentsProvider {
6164

6265
@Override
6366
public Stream<? extends Arguments> provideArguments(final ExtensionContext extensionContext) {
64-
return Stream.of(Arguments.of("simple", "jdbc:firebirdsql://127.0.0.1/foo_ds", "127.0.0.1", 3050, "foo_ds", null, new Properties()));
67+
return Stream.of(
68+
Arguments.of("simple_first", "jdbc:firebirdsql://127.0.0.1/foo_ds", "127.0.0.1", 3050, "foo_ds", null, new Properties()),
69+
Arguments.of("simple_second", "jdbc:firebird://localhost:32783//var/lib/firebird/data/demo_ds_2.fdb",
70+
"localhost", 32783, "/var/lib/firebird/data/demo_ds_2.fdb", null, new Properties()),
71+
Arguments.of("simple_third", "jdbc:firebirdsql://localhost/database?socket_buffer_size=32767", "localhost", 3050, "database", null, PropertiesBuilder.build(
72+
new PropertiesBuilder.Property("socketBufferSize", "32767"))),
73+
Arguments.of("complex",
74+
"jdbc:firebirdsql://localhost/database?socket_buffer_size=32767"
75+
+ "&TRANSACTION_REPEATABLE_READ=concurrency,write,no_wait&columnLabelForName&soTimeout=1000&nonStandard2=value2",
76+
"localhost", 3050, "database", null, PropertiesBuilder.build(
77+
new PropertiesBuilder.Property("socketBufferSize", "32767"),
78+
new PropertiesBuilder.Property("TRANSACTION_REPEATABLE_READ", "concurrency,write,no_wait"),
79+
new PropertiesBuilder.Property("columnLabelForName", ""),
80+
new PropertiesBuilder.Property("soTimeout", "1000"),
81+
new PropertiesBuilder.Property("nonStandard2", "value2"))));
6582
}
6683
}
6784
}

infra/reachability-metadata/src/main/resources/META-INF/native-image/org.apache.shardingsphere/generated-reachability-metadata/reflect-config.json

+41-11
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"name":"[Lcom.github.dockerjava.api.model.VolumesFrom;"
2525
},
2626
{
27-
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.connection.DriverDatabaseConnectionManager$$Lambda/0x00007f8e2be1b4b0"},
27+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.connection.DriverDatabaseConnectionManager$$Lambda/0x00007fe0ffdf9828"},
2828
"name":"[Lcom.zaxxer.hikari.util.ConcurrentBag$IConcurrentBagEntry;"
2929
},
3030
{
@@ -745,11 +745,6 @@
745745
"queryAllPublicConstructors":true,
746746
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"add","parameterTypes":["long"] }, {"name":"sum","parameterTypes":[] }]
747747
},
748-
{
749-
"condition":{"typeReachable":"org.apache.shardingsphere.proxy.frontend.command.CommandExecutorTask"},
750-
"name":"java.util.concurrent.atomic.Striped64$Cell",
751-
"fields":[{"name":"value"}]
752-
},
753748
{
754749
"condition":{"typeReachable":"org.apache.shardingsphere.infra.expr.groovy.GroovyInlineExpressionParser"},
755750
"name":"java.util.function.DoubleFunction",
@@ -3256,11 +3251,31 @@
32563251
"condition":{"typeReachable":"org.apache.shardingsphere.sql.parser.core.database.cache.ParseTreeCacheBuilder"},
32573252
"name":"org.apache.shardingsphere.sql.parser.core.database.cache.ParseTreeCacheLoader"
32583253
},
3254+
{
3255+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSphereStatement"},
3256+
"name":"org.apache.shardingsphere.sql.parser.firebird.parser.FirebirdLexer",
3257+
"methods":[{"name":"<init>","parameterTypes":["org.antlr.v4.runtime.CharStream"] }]
3258+
},
3259+
{
3260+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSphereStatement"},
3261+
"name":"org.apache.shardingsphere.sql.parser.firebird.parser.FirebirdParser",
3262+
"methods":[{"name":"<init>","parameterTypes":["org.antlr.v4.runtime.TokenStream"] }]
3263+
},
32593264
{
32603265
"condition":{"typeReachable":"org.apache.shardingsphere.sql.parser.core.database.visitor.SQLStatementVisitorFactory"},
32613266
"name":"org.apache.shardingsphere.sql.parser.firebird.visitor.statement.FirebirdStatementVisitorFacade",
32623267
"methods":[{"name":"<init>","parameterTypes":[] }]
32633268
},
3269+
{
3270+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSphereStatement"},
3271+
"name":"org.apache.shardingsphere.sql.parser.firebird.visitor.statement.type.FirebirdDDLStatementVisitor",
3272+
"methods":[{"name":"<init>","parameterTypes":[] }]
3273+
},
3274+
{
3275+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSphereStatement"},
3276+
"name":"org.apache.shardingsphere.sql.parser.firebird.visitor.statement.type.FirebirdDMLStatementVisitor",
3277+
"methods":[{"name":"<init>","parameterTypes":[] }]
3278+
},
32643279
{
32653280
"condition":{"typeReachable":"org.apache.shardingsphere.sql.parser.core.database.visitor.SQLStatementVisitorFactory"},
32663281
"name":"org.apache.shardingsphere.sql.parser.hive.visitor.statement.HiveStatementVisitorFacade",
@@ -3421,6 +3436,26 @@
34213436
"name":"org.apache.shardingsphere.sql.parser.statement.clickhouse.dml.ClickHouseSelectStatement",
34223437
"methods":[{"name":"<init>","parameterTypes":[] }]
34233438
},
3439+
{
3440+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSphereStatement"},
3441+
"name":"org.apache.shardingsphere.sql.parser.statement.firebird.ddl.FirebirdCreateTableStatement",
3442+
"methods":[{"name":"<init>","parameterTypes":[] }]
3443+
},
3444+
{
3445+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement"},
3446+
"name":"org.apache.shardingsphere.sql.parser.statement.firebird.dml.FirebirdInsertStatement",
3447+
"methods":[{"name":"<init>","parameterTypes":[] }]
3448+
},
3449+
{
3450+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement"},
3451+
"name":"org.apache.shardingsphere.sql.parser.statement.firebird.dml.FirebirdSelectStatement",
3452+
"methods":[{"name":"<init>","parameterTypes":[] }]
3453+
},
3454+
{
3455+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSphereStatement"},
3456+
"name":"org.apache.shardingsphere.sql.parser.statement.firebird.dml.FirebirdSelectStatement",
3457+
"methods":[{"name":"<init>","parameterTypes":[] }]
3458+
},
34243459
{
34253460
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement"},
34263461
"name":"org.apache.shardingsphere.sql.parser.statement.hive.dml.HiveInsertStatement",
@@ -3747,10 +3782,5 @@
37473782
{
37483783
"condition":{"typeReachable":"org.apache.shardingsphere.infra.util.yaml.YamlEngine"},
37493784
"name":"org.apache.shardingsphere.transaction.yaml.config.YamlTransactionRuleConfigurationCustomizer"
3750-
},
3751-
{
3752-
"condition":{"typeReachable":"org.apache.shardingsphere.infra.executor.kernel.ExecutorEngine"},
3753-
"name":"sun.security.provider.SecureRandom",
3754-
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
37553785
}
37563786
]

infra/reachability-metadata/src/main/resources/META-INF/native-image/org.apache.shardingsphere/generated-reachability-metadata/resource-config.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.connection.DriverDatabaseConnectionManager"},
9292
"pattern":"\\QMETA-INF/services/com.clickhouse.client.ClickHouseClient\\E"
9393
}, {
94-
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.connection.DriverDatabaseConnectionManager$$Lambda/0x00007f8e2bcc2910"},
94+
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.connection.DriverDatabaseConnectionManager$$Lambda/0x00007fe0ffca4900"},
9595
"pattern":"\\QMETA-INF/services/com.clickhouse.client.ClickHouseClient\\E"
9696
}, {
9797
"condition":{"typeReachable":"org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource"},
@@ -2031,6 +2031,9 @@
20312031
}, {
20322032
"condition":{"typeReachable":"org.apache.shardingsphere.infra.url.classpath.ClassPathURLLoader"},
20332033
"pattern":"\\Qtest-native/yaml/jdbc/databases/clickhouse.yaml\\E"
2034+
}, {
2035+
"condition":{"typeReachable":"org.apache.shardingsphere.infra.url.classpath.ClassPathURLLoader"},
2036+
"pattern":"\\Qtest-native/yaml/jdbc/databases/firebird.yaml\\E"
20342037
}, {
20352038
"condition":{"typeReachable":"org.apache.shardingsphere.infra.url.classpath.ClassPathURLLoader"},
20362039
"pattern":"\\Qtest-native/yaml/jdbc/databases/hive/acid.yaml\\E"

infra/reachability-metadata/src/main/resources/META-INF/native-image/org.apache.shardingsphere/shardingsphere-infra-reachability-metadata/reflect-config.json

+10
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,16 @@
357357
"name":"org.apache.shardingsphere.sql.parser.statement.sqlserver.ddl.SQLServerDropTableStatement",
358358
"methods":[{"name":"<init>","parameterTypes":[] }]
359359
},
360+
{
361+
"condition":{"typeReachable":"org.apache.shardingsphere.sql.parser.statement.firebird.dml.FirebirdDeleteStatement"},
362+
"name":"org.apache.shardingsphere.sql.parser.statement.firebird.dml.FirebirdDeleteStatement",
363+
"methods":[{"name":"<init>","parameterTypes":[] }]
364+
},
365+
{
366+
"condition":{"typeReachable":"org.apache.shardingsphere.sql.parser.statement.firebird.ddl.FirebirdDropTableStatement"},
367+
"name":"org.apache.shardingsphere.sql.parser.statement.firebird.ddl.FirebirdDropTableStatement",
368+
"methods":[{"name":"<init>","parameterTypes":[] }]
369+
},
360370
{
361371
"condition":{"typeReachable":"javax.security.auth.login.Configuration"},
362372
"name":"sun.security.provider.ConfigFile",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
[
2+
{
3+
"condition":{"typeReachable":"org.firebirdsql.encodings.EncodingFactory"},
4+
"name":"org.firebirdsql.encodings.DefaultEncodingSet"
5+
},
6+
{
7+
"condition":{"typeReachable":"org.firebirdsql.gds.impl.GDSFactory"},
8+
"name":"org.firebirdsql.gds.impl.jni.EmbeddedGDSFactoryPlugin"
9+
},
10+
{
11+
"condition":{"typeReachable":"org.firebirdsql.gds.impl.GDSFactory"},
12+
"name":"org.firebirdsql.gds.impl.jni.NativeGDSFactoryPlugin"
13+
},
14+
{
15+
"condition":{"typeReachable":"org.firebirdsql.gds.impl.GDSFactory"},
16+
"name":"org.firebirdsql.gds.impl.oo.OOGDSFactoryPlugin"
17+
},
18+
{
19+
"condition":{"typeReachable":"org.firebirdsql.gds.impl.GDSFactory"},
20+
"name":"org.firebirdsql.gds.impl.wire.WireGDSFactoryPlugin"
21+
},
22+
{
23+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.auth.ClientAuthBlock"},
24+
"name":"org.firebirdsql.gds.ng.wire.auth.legacy.LegacyAuthenticationPluginSpi"
25+
},
26+
{
27+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.auth.ClientAuthBlock"},
28+
"name":"org.firebirdsql.gds.ng.wire.auth.srp.Srp224AuthenticationPluginSpi"
29+
},
30+
{
31+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.auth.ClientAuthBlock"},
32+
"name":"org.firebirdsql.gds.ng.wire.auth.srp.Srp256AuthenticationPluginSpi"
33+
},
34+
{
35+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.auth.ClientAuthBlock"},
36+
"name":"org.firebirdsql.gds.ng.wire.auth.srp.Srp384AuthenticationPluginSpi"
37+
},
38+
{
39+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.auth.ClientAuthBlock"},
40+
"name":"org.firebirdsql.gds.ng.wire.auth.srp.Srp512AuthenticationPluginSpi"
41+
},
42+
{
43+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.auth.ClientAuthBlock"},
44+
"name":"org.firebirdsql.gds.ng.wire.auth.srp.SrpAuthenticationPluginSpi"
45+
},
46+
{
47+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.version13.V13WireOperations"},
48+
"name":"org.firebirdsql.gds.ng.wire.crypt.arc4.Arc4EncryptionPluginSpi",
49+
"methods":[{"name":"<init>","parameterTypes":[] }]
50+
},
51+
{
52+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.version13.V13WireOperations"},
53+
"name":"org.firebirdsql.gds.ng.wire.crypt.chacha.ChaChaEncryptionPluginSpi"
54+
},
55+
{
56+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.ProtocolCollection"},
57+
"name":"org.firebirdsql.gds.ng.wire.version10.Version10Descriptor"
58+
},
59+
{
60+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.ProtocolCollection"},
61+
"name":"org.firebirdsql.gds.ng.wire.version11.Version11Descriptor"
62+
},
63+
{
64+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.ProtocolCollection"},
65+
"name":"org.firebirdsql.gds.ng.wire.version12.Version12Descriptor"
66+
},
67+
{
68+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.ProtocolCollection"},
69+
"name":"org.firebirdsql.gds.ng.wire.version13.Version13Descriptor"
70+
},
71+
{
72+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.ProtocolCollection"},
73+
"name":"org.firebirdsql.gds.ng.wire.version15.Version15Descriptor"
74+
},
75+
{
76+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.ProtocolCollection"},
77+
"name":"org.firebirdsql.gds.ng.wire.version16.Version16Descriptor"
78+
},
79+
{
80+
"condition":{"typeReachable":"org.firebirdsql.gds.ng.wire.ProtocolCollection"},
81+
"name":"org.firebirdsql.gds.ng.wire.version18.Version18Descriptor"
82+
},
83+
{
84+
"condition":{"typeReachable":"org.firebirdsql.jaybird.props.internal.UnregisteredDpbDefiner"},
85+
"name":"org.firebirdsql.jaybird.fb.constants.DpbItems",
86+
"allPublicFields":true
87+
},
88+
{
89+
"condition":{"typeReachable":"org.firebirdsql.jaybird.props.internal.UnregisteredDpbDefiner"},
90+
"name":"org.firebirdsql.jaybird.fb.constants.SpbItems",
91+
"allPublicFields":true
92+
},
93+
{
94+
"condition":{"typeReachable":"org.firebirdsql.jaybird.xca.FBManagedConnection"},
95+
"name":"org.firebirdsql.jaybird.xca.FBManagedConnection",
96+
"fields":[{"name":"connectionHandle"}, {"name":"unnotifiedWarnings"}]
97+
},
98+
{
99+
"condition":{"typeReachable":"org.firebirdsql.jaybird.xca.FBManagedConnectionFactory"},
100+
"name":"org.firebirdsql.jdbc.FBConnection",
101+
"methods":[{"name":"<init>","parameterTypes":["org.firebirdsql.jaybird.xca.FBManagedConnection"] }]
102+
},
103+
{
104+
"condition":{"typeReachable":"org.firebirdsql.jdbc.FBConnection"},
105+
"name":"org.firebirdsql.jdbc.FBConnection",
106+
"fields":[{"name":"savepointCounter"}]
107+
}
108+
]

0 commit comments

Comments
 (0)