├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── codecov.yml ├── intellij-style.xml ├── licenseheader.txt ├── pom.xml └── src ├── benchmark ├── README.md ├── java │ └── org │ │ └── mariadb │ │ └── r2dbc │ │ ├── Common.java │ │ ├── Do_1.java │ │ ├── Do_1000_param.java │ │ ├── Insert_batch.java │ │ ├── Select_1.java │ │ ├── Select_1000_Rows.java │ │ └── Select_100_cols.java └── resources │ └── logback-test.xml ├── main ├── java │ └── org │ │ └── mariadb │ │ └── r2dbc │ │ ├── ExceptionFactory.java │ │ ├── HaMode.java │ │ ├── MariadbBatch.java │ │ ├── MariadbClientParameterizedQueryStatement.java │ │ ├── MariadbCommonStatement.java │ │ ├── MariadbConnection.java │ │ ├── MariadbConnectionConfiguration.java │ │ ├── MariadbConnectionFactory.java │ │ ├── MariadbConnectionFactoryMetadata.java │ │ ├── MariadbConnectionFactoryProvider.java │ │ ├── MariadbConnectionMetadata.java │ │ ├── MariadbServerParameterizedQueryStatement.java │ │ ├── MariadbTransactionDefinition.java │ │ ├── MariadbUpdateCount.java │ │ ├── SslMode.java │ │ ├── api │ │ ├── MariadbBatch.java │ │ ├── MariadbConnection.java │ │ ├── MariadbConnectionMetadata.java │ │ ├── MariadbOutParameters.java │ │ ├── MariadbOutSegment.java │ │ ├── MariadbResult.java │ │ ├── MariadbRow.java │ │ └── MariadbStatement.java │ │ ├── authentication │ │ ├── AuthenticationFlowPluginLoader.java │ │ ├── AuthenticationPlugin.java │ │ ├── addon │ │ │ └── ClearPasswordPluginFlow.java │ │ └── standard │ │ │ ├── CachingSha2PasswordFlow.java │ │ │ ├── Ed25519PasswordPluginFlow.java │ │ │ ├── NativePasswordPluginFlow.java │ │ │ ├── PamPluginFlow.java │ │ │ ├── ParsecPasswordPlugin.java │ │ │ ├── Sha256PasswordPluginFlow.java │ │ │ └── ed25519 │ │ │ ├── README │ │ │ ├── Utils.java │ │ │ ├── math │ │ │ ├── Constants.java │ │ │ ├── Curve.java │ │ │ ├── Encoding.java │ │ │ ├── Field.java │ │ │ ├── FieldElement.java │ │ │ ├── GroupElement.java │ │ │ └── ed25519 │ │ │ │ ├── Ed25519FieldElement.java │ │ │ │ ├── Ed25519LittleEndianEncoding.java │ │ │ │ └── ScalarOps.java │ │ │ └── spec │ │ │ ├── EdDSANamedCurveSpec.java │ │ │ ├── EdDSANamedCurveTable.java │ │ │ └── EdDSAParameterSpec.java │ │ ├── client │ │ ├── Client.java │ │ ├── DecoderState.java │ │ ├── DecoderStateInterface.java │ │ ├── Exchange.java │ │ ├── FailoverClient.java │ │ ├── MariadbFrameDecoder.java │ │ ├── MariadbOutParameters.java │ │ ├── MariadbOutParametersMetadata.java │ │ ├── MariadbPacketEncoder.java │ │ ├── MariadbResult.java │ │ ├── MariadbRow.java │ │ ├── MariadbRowBinary.java │ │ ├── MariadbRowMetadata.java │ │ ├── MariadbRowText.java │ │ ├── MariadbSegmentResult.java │ │ ├── RedoContext.java │ │ ├── ServerVersion.java │ │ ├── SimpleClient.java │ │ ├── SimpleContext.java │ │ └── TransactionSaver.java │ │ ├── codec │ │ ├── Codec.java │ │ ├── Codecs.java │ │ ├── DataType.java │ │ └── list │ │ │ ├── BigDecimalCodec.java │ │ │ ├── BigIntegerCodec.java │ │ │ ├── BitSetCodec.java │ │ │ ├── BlobCodec.java │ │ │ ├── BooleanCodec.java │ │ │ ├── ByteArrayCodec.java │ │ │ ├── ByteBufferCodec.java │ │ │ ├── ByteCodec.java │ │ │ ├── ClobCodec.java │ │ │ ├── DoubleCodec.java │ │ │ ├── DurationCodec.java │ │ │ ├── FloatCodec.java │ │ │ ├── IntCodec.java │ │ │ ├── LocalDateCodec.java │ │ │ ├── LocalDateTimeCodec.java │ │ │ ├── LocalTimeCodec.java │ │ │ ├── LongCodec.java │ │ │ ├── ShortCodec.java │ │ │ ├── StreamCodec.java │ │ │ ├── StringCodec.java │ │ │ └── UuidCodec.java │ │ ├── message │ │ ├── AuthMoreData.java │ │ ├── AuthSwitch.java │ │ ├── ClientMessage.java │ │ ├── Context.java │ │ ├── MessageSequence.java │ │ ├── Protocol.java │ │ ├── ServerMessage.java │ │ ├── client │ │ │ ├── AuthMoreRawPacket.java │ │ │ ├── ChangeSchemaPacket.java │ │ │ ├── ClearPasswordPacket.java │ │ │ ├── ClosePreparePacket.java │ │ │ ├── Ed25519PasswordPacket.java │ │ │ ├── ExecutePacket.java │ │ │ ├── HandshakeResponse.java │ │ │ ├── NativePasswordPacket.java │ │ │ ├── ParsecAuthPacket.java │ │ │ ├── PingPacket.java │ │ │ ├── PreparePacket.java │ │ │ ├── QueryPacket.java │ │ │ ├── QueryWithParametersPacket.java │ │ │ ├── QuitPacket.java │ │ │ ├── RequestExtSaltPacket.java │ │ │ ├── RsaPublicKeyRequestPacket.java │ │ │ ├── Sha256PasswordPacket.java │ │ │ ├── Sha2PublicKeyRequestPacket.java │ │ │ └── SslRequestPacket.java │ │ ├── flow │ │ │ └── AuthenticationFlow.java │ │ └── server │ │ │ ├── AuthMoreDataPacket.java │ │ │ ├── AuthSwitchPacket.java │ │ │ ├── ColumnCountPacket.java │ │ │ ├── ColumnDefinitionPacket.java │ │ │ ├── CompletePrepareResult.java │ │ │ ├── EofPacket.java │ │ │ ├── ErrorPacket.java │ │ │ ├── InitialHandshakePacket.java │ │ │ ├── OkPacket.java │ │ │ ├── PrepareResultPacket.java │ │ │ ├── RowPacket.java │ │ │ ├── Sequencer.java │ │ │ └── SkipPacket.java │ │ └── util │ │ ├── Assert.java │ │ ├── BindValue.java │ │ ├── Binding.java │ │ ├── BufferUtils.java │ │ ├── CharsetEncodingLength.java │ │ ├── ClientParser.java │ │ ├── HostAddress.java │ │ ├── LoggerHelper.java │ │ ├── MariadbType.java │ │ ├── PrepareCache.java │ │ ├── PrepareResult.java │ │ ├── Security.java │ │ ├── ServerNamedParamParser.java │ │ ├── ServerPrepareResult.java │ │ ├── SslConfig.java │ │ ├── VersionFactory.java │ │ └── constants │ │ ├── Capabilities.java │ │ ├── ColumnFlags.java │ │ ├── ServerStatus.java │ │ └── StateChange.java ├── java9 │ └── module-info.java └── resources │ ├── META-INF │ └── services │ │ ├── io.r2dbc.spi.ConnectionFactoryProvider │ │ └── org.mariadb.r2dbc.authentication.AuthenticationPlugin │ └── mariadb.properties └── test ├── java └── org │ └── mariadb │ └── r2dbc │ ├── BaseConnectionTest.java │ ├── TestConfiguration.java │ ├── integration │ ├── BatchTest.java │ ├── BigResultSetTest.java │ ├── ConfigurationTest.java │ ├── ConnectionMetadataTest.java │ ├── ConnectionTest.java │ ├── ErrorTest.java │ ├── FailoverConnectionTest.java │ ├── LoggingTest.java │ ├── MariadbBinaryTestKit.java │ ├── MariadbTextTestKit.java │ ├── MultiQueriesTest.java │ ├── NoPipelineTest.java │ ├── PrepareResultSetTest.java │ ├── ProcedureResultsetTest.java │ ├── RedirectionTest.java │ ├── ResultsetTest.java │ ├── RowMetadataTest.java │ ├── StatementBatchingTest.java │ ├── StatementTest.java │ ├── TlsTest.java │ ├── TransactionTest.java │ ├── authentication │ │ ├── Ed25519PluginTest.java │ │ ├── PamPluginTest.java │ │ ├── ParsecPluginTest.java │ │ └── Sha256PluginTest.java │ ├── codec │ │ ├── BigIntegerParseTest.java │ │ ├── BitParseTest.java │ │ ├── BlobParseTest.java │ │ ├── DateParseTest.java │ │ ├── DateTimeParseTest.java │ │ ├── DecimalParseTest.java │ │ ├── DoubleParseTest.java │ │ ├── FloatParseTest.java │ │ ├── IntParseTest.java │ │ ├── JsonParseTest.java │ │ ├── MediumIntParseTest.java │ │ ├── ShortParseTest.java │ │ ├── StringParseTest.java │ │ ├── TimeParseTest.java │ │ ├── TimestampParseTest.java │ │ ├── TinyIntParseTest.java │ │ ├── UuidParseTest.java │ │ └── YearParseTest.java │ └── parameter │ │ ├── BigIntegerParameterTest.java │ │ ├── BitParameterTest.java │ │ ├── BlobParameterTest.java │ │ ├── DateParameterTest.java │ │ ├── DateTimeParameterTest.java │ │ ├── DecimalParameterTest.java │ │ ├── FloatParameterTest.java │ │ ├── IntParameterTest.java │ │ ├── MediumIntParameterTest.java │ │ ├── ShortParameterTest.java │ │ ├── StringParameterTest.java │ │ ├── TimeParameterTest.java │ │ ├── TimeStampParameterTest.java │ │ └── TinyIntParameterTest.java │ ├── tools │ ├── TcpProxy.java │ └── TcpProxySocket.java │ └── unit │ ├── InitFinalClass.java │ ├── MariadbConnectionConfigurationTest.java │ ├── SslModeTest.java │ ├── client │ └── ServerVersionTest.java │ └── util │ ├── BufferUtilsTest.java │ ├── ClientPrepareResultTest.java │ ├── HostAddressTest.java │ ├── MariadbTypeTest.java │ └── ServerPrepareResultTest.java └── resources ├── conf.properties └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | 5 | # Keep empty directories: 6 | # Keep empty directories: >> .gitignore/.git* 7 | /target/ 8 | .idea 9 | /r2dbc-mariadb.iml 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | language: java 3 | jdk: openjdk17 4 | version: ~> 1.0 5 | 6 | before_install: 7 | - |- 8 | case $TRAVIS_OS_NAME in 9 | windows) 10 | choco install openjdk11 maven 11 | export PATH=$(cmd.exe //c "refreshenv > nul & C:\Progra~1\Git\bin\bash -c 'echo \$PATH' ") 12 | ;; 13 | esac 14 | 15 | env: 16 | global: packet=40 local=0 clear_text_plugin=0 RUN_LONG_TEST=true PROFILE=default DB=testr2 17 | 18 | import: mariadb-corporation/connector-test-machine:common-build.yml@master 19 | 20 | jobs: 21 | include: 22 | - stage: Language 23 | env: srv=mariadb v=10.6 PROFILE=java8 24 | jdk: openjdk8 25 | name: "CS 10.6 - openjdk 8" 26 | - stage: Language 27 | env: srv=mariadb v=10.6 NO_BACKSLASH_ESCAPES=1 28 | jdk: openjdk11 29 | name: "CS 10.6 - openjdk 11 - NO_BACKSLASH_ESCAPES" 30 | - stage: Language 31 | env: srv=mariadb v=10.6 packet=8 32 | jdk: openjdk17 33 | name: "CS 10.6 - openjdk 17 - packet 8M" 34 | 35 | script: 36 | - mvn clean -Dmaven.test.skip > /dev/null 37 | - if [ -n "$BENCH" ] ; then mvn package -P bench -Dmaven.test.skip; fi 38 | - if [ -n "$BENCH" ] ; then java -Duser.country=US -Duser.language=en -DTEST_PORT=$TEST_DB_PORT -DTEST_HOST=$TEST_DB_HOST -DTEST_USERNAME=$TEST_DB_USER -DTEST_PASSWORD=$TEST_DB_PASSWORD -jar target/benchmarks.jar; fi 39 | - if [ -z "$BENCH" ] ; then MAVEN_SKIP_RC=true MAVEN_OPTS="-Xmx2g" mvn test -P ${PROFILE} -DjobId=${TRAVIS_JOB_ID}; fi 40 | 41 | after_script: 42 | - bash <(curl -s https://codecov.io/bash) 43 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | codecov: 3 | token: 4e71e90c-99a8-40b8-b69c-181457ec4861 4 | ignore: 5 | - "src/main/java/org/mariadb/r2dbc/authentication/ed25519/**/*" 6 | -------------------------------------------------------------------------------- /licenseheader.txt: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | -------------------------------------------------------------------------------- /src/benchmark/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | # Benchmark 8 | 9 | How to run : 10 | 11 | ```script 12 | mvn clean package -P bench -Dmaven.test.skip 13 | 14 | # run all benchmarks 15 | java -Duser.country=US -Duser.language=en -jar target/benchmarks.jar 16 | 17 | # run a specific benchmark 18 | java -Duser.country=US -Duser.language=en -jar target/benchmarks.jar "Select_1_user" 19 | ``` 20 | 21 | Configuration by system properties : 22 | 23 | * TEST_HOST: localhost 24 | * TEST_PORT: 3306 25 | * TEST_USERNAME: root 26 | * TEST_PASSWORD: "" 27 | * TEST_DATABASE: "testr2" 28 | 29 | example: 30 | 31 | ```script 32 | mvn clean package -P bench -Dmaven.test.skip 33 | java -DTEST_PORT=3307 -Duser.country=US -Duser.language=en -jar target/benchmarks.jar "Select_1_user" 34 | ``` 35 | -------------------------------------------------------------------------------- /src/benchmark/java/org/mariadb/r2dbc/Do_1.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import org.openjdk.jmh.annotations.Benchmark; 7 | 8 | public class Do_1 extends Common { 9 | 10 | @Benchmark 11 | public Long testR2dbc(MyState state) throws Throwable { 12 | return consume(state.r2dbc); 13 | } 14 | 15 | @Benchmark 16 | public Long testR2dbcPrepare(MyState state) throws Throwable { 17 | return consume(state.r2dbcPrepare); 18 | } 19 | 20 | private Long consume(MariadbConnection connection) { 21 | return connection.createStatement("DO 1").execute() 22 | .flatMap(it -> it.getRowsUpdated()) 23 | .blockLast(); 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/benchmark/java/org/mariadb/r2dbc/Do_1000_param.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import org.mariadb.r2dbc.api.MariadbStatement; 7 | import org.openjdk.jmh.annotations.Benchmark; 8 | 9 | public class Do_1000_param extends Common { 10 | private static final String sql; 11 | 12 | static { 13 | StringBuilder sb = new StringBuilder("do ?"); 14 | for (int i = 1; i < 1000; i++) { 15 | sb.append(",?"); 16 | } 17 | sql = sb.toString(); 18 | } 19 | 20 | @Benchmark 21 | public Long testR2dbc(MyState state) throws Throwable { 22 | return consume(state.r2dbc); 23 | } 24 | 25 | @Benchmark 26 | public Long testR2dbcPrepare(MyState state) throws Throwable { 27 | return consume(state.r2dbcPrepare); 28 | } 29 | 30 | private Long consume(MariadbConnection connection) { 31 | MariadbStatement statement = connection.createStatement(sql); 32 | for (int i = 0; i < 1000; i++) 33 | statement.bind(i, i); 34 | return statement.execute() 35 | .flatMap(it -> it.getRowsUpdated()) 36 | .blockLast(); 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/benchmark/java/org/mariadb/r2dbc/Insert_batch.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import org.mariadb.r2dbc.api.MariadbStatement; 7 | import org.openjdk.jmh.annotations.Benchmark; 8 | import org.openjdk.jmh.infra.Blackhole; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | public class Insert_batch extends Common { 15 | 16 | static final List chars = new ArrayList<>(); 17 | 18 | static { 19 | chars.addAll(Arrays.asList("123456789abcdefghijklmnop\\Z".split(""))); 20 | chars.add("😎"); 21 | chars.add("🌶"); 22 | chars.add("🎤"); 23 | chars.add("🥂"); 24 | } 25 | 26 | static public String randomString(int length) { 27 | StringBuilder result = new StringBuilder(); 28 | for (int i = length; i > 0; --i) result.append(chars.get(Math.round((int) Math.random() * (chars.size() - 1)))); 29 | return result.toString(); 30 | } 31 | 32 | @Benchmark 33 | public Long testR2dbc(MyState state, Blackhole blackhole) throws Throwable { 34 | return consume(state.r2dbc, blackhole); 35 | } 36 | 37 | @Benchmark 38 | public Long testR2dbcPrepare(MyState state, Blackhole blackhole) throws Throwable { 39 | return consume(state.r2dbcPrepare, blackhole); 40 | } 41 | 42 | private Long consume(MariadbConnection connection, Blackhole blackhole) { 43 | String s = randomString(100); 44 | 45 | MariadbStatement statement = connection.createStatement("INSERT INTO perfTestTextBatch(t0) VALUES (?)"); 46 | for (int i = 0; i < 100; i++) { 47 | if (i != 0) statement.add(); 48 | statement.bind(0, s); 49 | } 50 | 51 | return 52 | statement.execute() 53 | .flatMap(it -> it.getRowsUpdated()) 54 | .blockLast(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/benchmark/java/org/mariadb/r2dbc/Select_1.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import org.mariadb.r2dbc.api.MariadbStatement; 7 | import org.openjdk.jmh.annotations.Benchmark; 8 | 9 | public class Select_1 extends Common { 10 | 11 | @Benchmark 12 | public Integer testR2dbc(MyState state) throws Throwable { 13 | return consume(state.r2dbc); 14 | } 15 | 16 | @Benchmark 17 | public Integer testR2dbcPrepare(MyState state) throws Throwable { 18 | return consume(state.r2dbcPrepare); 19 | } 20 | 21 | private Integer consume(MariadbConnection connection) { 22 | int rnd = (int) (Math.random() * 1000); 23 | MariadbStatement statement = connection.createStatement("select " + rnd); 24 | return 25 | statement.execute() 26 | .flatMap(it -> it.map((row, rowMetadata) -> row.get(0, Integer.class))) 27 | .blockLast(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/benchmark/java/org/mariadb/r2dbc/Select_1000_Rows.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import org.openjdk.jmh.annotations.Benchmark; 7 | import org.openjdk.jmh.infra.Blackhole; 8 | 9 | public class Select_1000_Rows extends Common { 10 | private static final String sql = 11 | "select seq, 'abcdefghijabcdefghijabcdefghijaa' from seq_1_to_1000"; 12 | 13 | @Benchmark 14 | public Integer testR2dbc(MyState state, Blackhole blackhole) throws Throwable { 15 | return consume(state.r2dbc, blackhole); 16 | } 17 | 18 | @Benchmark 19 | public Integer testR2dbcPrepare(MyState state, Blackhole blackhole) throws Throwable { 20 | return consumePrepare(state.r2dbcPrepare, blackhole); 21 | } 22 | 23 | private Integer consume(MariadbConnection connection, Blackhole blackhole) { 24 | return connection.createStatement(sql).execute() 25 | .flatMap(it -> it.map((row, rowMetadata) -> { 26 | Integer i = row.get(0, Integer.class); 27 | row.get(1, String.class); 28 | return i; 29 | })).blockLast(); 30 | } 31 | 32 | private Integer consumePrepare(MariadbConnection connection, Blackhole blackhole) { 33 | return connection.createStatement(sql + " WHERE 1 = ?").bind(0, 1).execute() 34 | .flatMap(it -> it.map((row, rowMetadata) -> { 35 | Integer i = row.get(0, Integer.class); 36 | row.get(1, String.class); 37 | return i; 38 | })).blockLast(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/benchmark/java/org/mariadb/r2dbc/Select_100_cols.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import org.mariadb.r2dbc.api.MariadbStatement; 7 | import org.openjdk.jmh.annotations.Benchmark; 8 | 9 | public class Select_100_cols extends Common { 10 | 11 | @Benchmark 12 | public int[] testR2dbc(MyState state) throws Throwable { 13 | return consume(state.r2dbc); 14 | } 15 | 16 | @Benchmark 17 | public int[] testR2dbcPrepare(MyState state) throws Throwable { 18 | return consumePrepare(state.r2dbcPrepare); 19 | } 20 | 21 | private int[] consume(MariadbConnection connection) { 22 | 23 | MariadbStatement statement = 24 | connection.createStatement("select * FROM test100"); 25 | return 26 | statement.execute() 27 | .flatMap( 28 | it -> 29 | it.map( 30 | (row, rowMetadata) -> { 31 | int[] objs = new int[100]; 32 | for (int i = 0; i < 100; i++) { 33 | objs[i] = row.get(i, Integer.class); 34 | } 35 | return objs; 36 | })) 37 | .blockLast(); 38 | } 39 | 40 | private int[] consumePrepare(MariadbConnection connection) { 41 | 42 | MariadbStatement statement = 43 | connection.createStatement("select * FROM test100 WHERE 1 = ?").bind(0, 1); 44 | return 45 | statement.execute() 46 | .flatMap( 47 | it -> 48 | it.map( 49 | (row, rowMetadata) -> { 50 | int[] objs = new int[100]; 51 | for (int i = 0; i < 100; i++) { 52 | objs[i] = row.get(i, Integer.class); 53 | } 54 | return objs; 55 | })) 56 | .blockLast(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/benchmark/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | %d{yyyy-MM-dd} | %d{HH:mm:ss.SSS} | %-20.20thread | %5p | %logger{25} | %m%n 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/ExceptionFactory.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import io.r2dbc.spi.*; 7 | import org.mariadb.r2dbc.message.ServerMessage; 8 | import org.mariadb.r2dbc.message.server.ErrorPacket; 9 | import reactor.core.publisher.SynchronousSink; 10 | 11 | public final class ExceptionFactory { 12 | 13 | public static final ExceptionFactory INSTANCE = new ExceptionFactory(""); 14 | private final String sql; 15 | 16 | private ExceptionFactory(String sql) { 17 | this.sql = sql; 18 | } 19 | 20 | public static ExceptionFactory withSql(String sql) { 21 | return new ExceptionFactory(sql); 22 | } 23 | 24 | public static R2dbcException createException(ErrorPacket error, String sql) { 25 | return createException(error.message(), error.sqlState(), error.errorCode(), sql); 26 | } 27 | 28 | public static R2dbcException createException( 29 | String message, String sqlState, int errorCode, String sql) { 30 | 31 | if ("70100".equals(sqlState) || errorCode == 3024) { // ER_QUERY_INTERRUPTED 32 | return new R2dbcTimeoutException(message, sqlState, errorCode, sql); 33 | } 34 | 35 | String sqlClass = sqlState.substring(0, 2); 36 | switch (sqlClass) { 37 | case "0A": 38 | case "22": 39 | case "26": 40 | case "2F": 41 | case "20": 42 | case "42": 43 | case "XA": 44 | return new R2dbcBadGrammarException(message, sqlState, errorCode, sql); 45 | case "25": 46 | case "28": 47 | return new R2dbcPermissionDeniedException(message, sqlState, errorCode, sql); 48 | case "21": 49 | case "23": 50 | return new R2dbcDataIntegrityViolationException(message, sqlState, errorCode, sql); 51 | case "H1": 52 | case "08": 53 | return new R2dbcNonTransientResourceException(message, sqlState, errorCode, sql); 54 | case "40": 55 | return new R2dbcRollbackException(message, sqlState, errorCode, sql); 56 | } 57 | 58 | return new R2dbcTransientResourceException(message, sqlState, errorCode, sql); 59 | } 60 | 61 | public R2dbcException createParsingException(String message) { 62 | return new R2dbcNonTransientResourceException(message, "H1000", 9000, this.sql); 63 | } 64 | 65 | public R2dbcException createParsingException(String message, Throwable cause) { 66 | return new R2dbcNonTransientResourceException(message, "H1000", 9000, this.sql, cause); 67 | } 68 | 69 | public R2dbcException createConnectionErrorException(String message) { 70 | return new R2dbcNonTransientResourceException(message, "08000", 9000, this.sql); 71 | } 72 | 73 | public R2dbcException createConnectionErrorException(String message, Throwable cause) { 74 | return new R2dbcNonTransientResourceException( 75 | message + " : " + cause.getMessage(), "08000", 9000, this.sql, cause); 76 | } 77 | 78 | public R2dbcException createException(String message, String sqlState, int errorCode) { 79 | return ExceptionFactory.createException(message, sqlState, errorCode, this.sql); 80 | } 81 | 82 | public R2dbcException from(ErrorPacket err) { 83 | return createException(err, this.sql); 84 | } 85 | 86 | public void handleErrorResponse(ServerMessage message, SynchronousSink sink) { 87 | if (message instanceof ErrorPacket) { 88 | sink.error(createException((ErrorPacket) message, this.sql)); 89 | } else { 90 | sink.next(message); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/MariadbConnectionFactoryMetadata.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import io.r2dbc.spi.ConnectionFactoryMetadata; 7 | 8 | final class MariadbConnectionFactoryMetadata implements ConnectionFactoryMetadata { 9 | 10 | public static final String NAME = "MariaDB"; 11 | 12 | static final MariadbConnectionFactoryMetadata INSTANCE = new MariadbConnectionFactoryMetadata(); 13 | 14 | private MariadbConnectionFactoryMetadata() {} 15 | 16 | @Override 17 | public String getName() { 18 | return NAME; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/MariadbConnectionMetadata.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import org.mariadb.r2dbc.client.ServerVersion; 7 | 8 | public final class MariadbConnectionMetadata 9 | implements org.mariadb.r2dbc.api.MariadbConnectionMetadata { 10 | 11 | private final ServerVersion version; 12 | 13 | MariadbConnectionMetadata(ServerVersion version) { 14 | this.version = version; 15 | } 16 | 17 | @Override 18 | public String getDatabaseProductName() { 19 | return this.version.isMariaDBServer() ? "MariaDB" : "MySQL"; 20 | } 21 | 22 | public boolean isMariaDBServer() { 23 | return this.version.isMariaDBServer(); 24 | } 25 | 26 | public boolean minVersion(int major, int minor, int patch) { 27 | return this.version.versionGreaterOrEqual(major, minor, patch); 28 | } 29 | 30 | public int getMajorVersion() { 31 | return version.getMajorVersion(); 32 | } 33 | 34 | public int getMinorVersion() { 35 | return version.getMinorVersion(); 36 | } 37 | 38 | public int getPatchVersion() { 39 | return version.getPatchVersion(); 40 | } 41 | 42 | @Override 43 | public String getDatabaseVersion() { 44 | return this.version.getServerVersion(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/MariadbTransactionDefinition.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import io.r2dbc.spi.IsolationLevel; 7 | import io.r2dbc.spi.Option; 8 | import io.r2dbc.spi.TransactionDefinition; 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import org.mariadb.r2dbc.util.Assert; 13 | 14 | public final class MariadbTransactionDefinition implements TransactionDefinition { 15 | 16 | public static final MariadbTransactionDefinition EMPTY = 17 | new MariadbTransactionDefinition(Collections.emptyMap()); 18 | 19 | public static final Option WITH_CONSISTENT_SNAPSHOT = 20 | Option.valueOf("WITH CONSISTENT SNAPSHOT"); 21 | public static MariadbTransactionDefinition WITH_CONSISTENT_SNAPSHOT_READ_WRITE = 22 | EMPTY.consistent().readWrite(); 23 | public static MariadbTransactionDefinition WITH_CONSISTENT_SNAPSHOT_READ_ONLY = 24 | EMPTY.consistent().readOnly(); 25 | public static MariadbTransactionDefinition READ_WRITE = EMPTY.readWrite(); 26 | public static MariadbTransactionDefinition READ_ONLY = EMPTY.readOnly(); 27 | private final Map, Object> options; 28 | 29 | private MariadbTransactionDefinition(Map, Object> options) { 30 | this.options = options; 31 | } 32 | 33 | static MariadbTransactionDefinition mutability(boolean readWrite) { 34 | return readWrite ? EMPTY.readWrite() : EMPTY.readOnly(); 35 | } 36 | 37 | static MariadbTransactionDefinition from(IsolationLevel isolationLevel) { 38 | return MariadbTransactionDefinition.EMPTY.isolationLevel(isolationLevel); 39 | } 40 | 41 | @Override 42 | @SuppressWarnings("unchecked") 43 | public T getAttribute(Option option) { 44 | return (T) this.options.get(option); 45 | } 46 | 47 | public MariadbTransactionDefinition with(Option option, Object value) { 48 | 49 | Map, Object> options = new HashMap<>(this.options); 50 | options.put( 51 | Assert.requireNonNull(option, "option must not be null"), 52 | Assert.requireNonNull(value, "value must not be null")); 53 | 54 | return new MariadbTransactionDefinition(options); 55 | } 56 | 57 | public MariadbTransactionDefinition isolationLevel(IsolationLevel isolationLevel) { 58 | return with(TransactionDefinition.ISOLATION_LEVEL, isolationLevel); 59 | } 60 | 61 | public MariadbTransactionDefinition readOnly() { 62 | return with(TransactionDefinition.READ_ONLY, true); 63 | } 64 | 65 | public MariadbTransactionDefinition readWrite() { 66 | return with(TransactionDefinition.READ_ONLY, false); 67 | } 68 | 69 | public MariadbTransactionDefinition consistent() { 70 | return with(MariadbTransactionDefinition.WITH_CONSISTENT_SNAPSHOT, true); 71 | } 72 | 73 | public MariadbTransactionDefinition notConsistent() { 74 | return with(MariadbTransactionDefinition.WITH_CONSISTENT_SNAPSHOT, false); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/MariadbUpdateCount.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | import io.r2dbc.spi.Result; 7 | 8 | public class MariadbUpdateCount implements Result.UpdateCount { 9 | public MariadbUpdateCount() {} 10 | 11 | @Override 12 | public long value() { 13 | return 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/SslMode.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc; 5 | 6 | public enum SslMode { 7 | 8 | // NO SSL 9 | DISABLE("disable", new String[] {"DISABLED", "0", "false"}), 10 | 11 | // Encryption only (no certificate and hostname validation) (DEVELOPMENT ONLY) 12 | TRUST("trust", new String[] {"REQUIRED", "ENABLE_TRUST"}), 13 | 14 | // Encryption, certificates validation, BUT no hostname verification 15 | VERIFY_CA("verify-ca", new String[] {"VERIFY_CA", "ENABLE_WITHOUT_HOSTNAME_VERIFICATION"}), 16 | 17 | // Standard SSL use: Encryption, certificate validation and hostname validation 18 | VERIFY_FULL("verify-full", new String[] {"VERIFY_IDENTITY", "1", "true", "enable"}), 19 | 20 | // Connect over a pre-created SSL tunnel 21 | TUNNEL("tunnel", new String[] {"TUNNEL"}); 22 | 23 | public final String value; 24 | private final String[] aliases; 25 | 26 | SslMode(String value, String[] aliases) { 27 | this.value = value; 28 | this.aliases = aliases; 29 | } 30 | 31 | public static SslMode from(String value) { 32 | for (SslMode sslMode : values()) { 33 | if (sslMode.value.equalsIgnoreCase(value) || sslMode.name().equalsIgnoreCase(value)) { 34 | return sslMode; 35 | } 36 | for (String alias : sslMode.aliases) { 37 | if (alias.equalsIgnoreCase(value)) { 38 | return sslMode; 39 | } 40 | } 41 | } 42 | throw new IllegalArgumentException( 43 | String.format("Wrong argument value '%s' for SslMode", value)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/api/MariadbBatch.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.api; 5 | 6 | import io.r2dbc.spi.Batch; 7 | import reactor.core.publisher.Flux; 8 | 9 | public interface MariadbBatch extends Batch { 10 | 11 | @Override 12 | MariadbBatch add(String sql); 13 | 14 | @Override 15 | Flux execute(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/api/MariadbConnection.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.api; 5 | 6 | import io.r2dbc.spi.Connection; 7 | import io.r2dbc.spi.IsolationLevel; 8 | import io.r2dbc.spi.TransactionDefinition; 9 | import io.r2dbc.spi.ValidationDepth; 10 | import java.time.Duration; 11 | import reactor.core.publisher.Mono; 12 | 13 | public interface MariadbConnection extends Connection { 14 | 15 | @Override 16 | Mono beginTransaction(); 17 | 18 | @Override 19 | Mono beginTransaction(TransactionDefinition definition); 20 | 21 | @Override 22 | Mono close(); 23 | 24 | @Override 25 | Mono commitTransaction(); 26 | 27 | @Override 28 | MariadbBatch createBatch(); 29 | 30 | @Override 31 | Mono createSavepoint(String name); 32 | 33 | @Override 34 | MariadbStatement createStatement(String sql); 35 | 36 | @Override 37 | MariadbConnectionMetadata getMetadata(); 38 | 39 | String getDatabase(); 40 | 41 | Mono setDatabase(String database); 42 | 43 | @Override 44 | IsolationLevel getTransactionIsolationLevel(); 45 | 46 | @Override 47 | boolean isAutoCommit(); 48 | 49 | boolean isInTransaction(); 50 | 51 | boolean isInReadOnlyTransaction(); 52 | 53 | @Override 54 | Mono releaseSavepoint(String name); 55 | 56 | @Override 57 | Mono rollbackTransaction(); 58 | 59 | @Override 60 | Mono rollbackTransactionToSavepoint(String name); 61 | 62 | @Override 63 | Mono setAutoCommit(boolean autoCommit); 64 | 65 | @Override 66 | Mono setTransactionIsolationLevel(IsolationLevel isolationLevel); 67 | 68 | @Override 69 | Mono validate(ValidationDepth depth); 70 | 71 | @Override 72 | Mono setLockWaitTimeout(Duration timeout); 73 | 74 | @Override 75 | Mono setStatementTimeout(Duration timeout); 76 | 77 | long getThreadId(); 78 | 79 | String getHost(); 80 | 81 | int getPort(); 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/api/MariadbConnectionMetadata.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.api; 5 | 6 | import io.r2dbc.spi.ConnectionMetadata; 7 | 8 | public interface MariadbConnectionMetadata extends ConnectionMetadata { 9 | 10 | @Override 11 | String getDatabaseProductName(); 12 | 13 | @Override 14 | String getDatabaseVersion(); 15 | 16 | /** 17 | * Short cut to indicate that database server is a MariaDB. i.e. equals to 18 | * "MariaDB".equals(getDatabaseProductName()) 19 | * 20 | * @return true if database server is a MariaDB server. 21 | */ 22 | boolean isMariaDBServer(); 23 | 24 | /** 25 | * Indicate if server does have required version. 26 | * 27 | * @param major major version 28 | * @param minor minor version 29 | * @param patch patch version 30 | * @return true is database version is equal or more than indicated version 31 | */ 32 | boolean minVersion(int major, int minor, int patch); 33 | 34 | /** 35 | * Indicate server major version. in 10.5.4, return 10 36 | * 37 | * @return server major version 38 | */ 39 | int getMajorVersion(); 40 | 41 | /** 42 | * Indicate server minor version. in 10.5.4, return 5 43 | * 44 | * @return server minor version 45 | */ 46 | int getMinorVersion(); 47 | 48 | /** 49 | * Indicate server patch version. in 10.5.4, return 4 50 | * 51 | * @return server patch version 52 | */ 53 | int getPatchVersion(); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/api/MariadbOutParameters.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.api; 5 | 6 | import io.r2dbc.spi.OutParameters; 7 | import io.r2dbc.spi.RowMetadata; 8 | import org.mariadb.r2dbc.client.MariadbOutParametersMetadata; 9 | 10 | /** A {@link io.r2dbc.spi.OutParameters} for a MariaDB/MySQL database. */ 11 | public interface MariadbOutParameters extends OutParameters { 12 | 13 | /** 14 | * Returns the {@link RowMetadata} for all columns in this row. 15 | * 16 | * @return the {@link RowMetadata} for all columns in this row 17 | * @since 0.9 18 | */ 19 | MariadbOutParametersMetadata getMetadata(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/api/MariadbOutSegment.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.api; 5 | 6 | import io.r2dbc.spi.Result; 7 | import io.r2dbc.spi.Row; 8 | 9 | public interface MariadbOutSegment extends Result.OutSegment { 10 | Row row(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/api/MariadbResult.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.api; 5 | 6 | import io.r2dbc.spi.Result; 7 | import io.r2dbc.spi.Row; 8 | import io.r2dbc.spi.RowMetadata; 9 | import java.util.function.BiFunction; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | public interface MariadbResult extends Result { 14 | 15 | @Override 16 | Mono getRowsUpdated(); 17 | 18 | @Override 19 | Flux map(BiFunction mappingFunction); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/api/MariadbRow.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.api; 5 | 6 | import io.r2dbc.spi.Row; 7 | import io.r2dbc.spi.RowMetadata; 8 | import org.mariadb.r2dbc.client.MariadbRowMetadata; 9 | 10 | /** A {@link Row} for a MariaDB/MySQL database. */ 11 | public interface MariadbRow extends Row { 12 | 13 | /** 14 | * Returns the {@link RowMetadata} for all columns in this row. 15 | * 16 | * @return the {@link RowMetadata} for all columns in this row 17 | * @since 0.9 18 | */ 19 | MariadbRowMetadata getMetadata(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/api/MariadbStatement.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.api; 5 | 6 | import io.r2dbc.spi.Statement; 7 | import reactor.core.publisher.Flux; 8 | 9 | public interface MariadbStatement extends Statement { 10 | 11 | @Override 12 | MariadbStatement add(); 13 | 14 | @Override 15 | MariadbStatement bind(String identifier, Object value); 16 | 17 | @Override 18 | MariadbStatement bind(int index, Object value); 19 | 20 | @Override 21 | MariadbStatement bindNull(String identifier, Class type); 22 | 23 | @Override 24 | MariadbStatement bindNull(int index, Class type); 25 | 26 | @Override 27 | Flux execute(); 28 | 29 | @Override 30 | default MariadbStatement fetchSize(int rows) { 31 | return this; 32 | } 33 | 34 | @Override 35 | MariadbStatement returnGeneratedValues(String... columns); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/AuthenticationFlowPluginLoader.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.authentication; 5 | 6 | import java.util.ServiceLoader; 7 | import org.mariadb.r2dbc.api.MariadbConnection; 8 | 9 | public class AuthenticationFlowPluginLoader { 10 | 11 | /** 12 | * Get authentication plugin from type String. Customs authentication plugin can be added 13 | * implementing AuthenticationPlugin and registering new type in resources services. 14 | * 15 | * @param type authentication plugin type 16 | * @return Authentication plugin corresponding to type 17 | */ 18 | public static AuthenticationPlugin get(String type) { 19 | ServiceLoader loader = 20 | ServiceLoader.load(AuthenticationPlugin.class, MariadbConnection.class.getClassLoader()); 21 | 22 | for (AuthenticationPlugin implClass : loader) { 23 | if (type.equals(implClass.type())) { 24 | return implClass.create(); 25 | } 26 | } 27 | 28 | throw new IllegalArgumentException( 29 | String.format( 30 | "Client does not support authentication protocol requested by server. " 31 | + "Plugin type was = '%s'", 32 | type)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/AuthenticationPlugin.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.authentication; 5 | 6 | import io.r2dbc.spi.R2dbcException; 7 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 8 | import org.mariadb.r2dbc.message.AuthMoreData; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.server.Sequencer; 11 | 12 | public interface AuthenticationPlugin { 13 | 14 | String type(); 15 | 16 | AuthenticationPlugin create(); 17 | 18 | ClientMessage next( 19 | MariadbConnectionConfiguration configuration, 20 | byte[] seed, 21 | Sequencer sequencer, 22 | AuthMoreData authMoreData) 23 | throws R2dbcException; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/addon/ClearPasswordPluginFlow.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.authentication.addon; 5 | 6 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 7 | import org.mariadb.r2dbc.authentication.AuthenticationPlugin; 8 | import org.mariadb.r2dbc.message.AuthMoreData; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.client.ClearPasswordPacket; 11 | import org.mariadb.r2dbc.message.server.Sequencer; 12 | 13 | public final class ClearPasswordPluginFlow implements AuthenticationPlugin { 14 | 15 | public static final String TYPE = "mysql_clear_password"; 16 | 17 | public ClearPasswordPluginFlow create() { 18 | return new ClearPasswordPluginFlow(); 19 | } 20 | 21 | public String type() { 22 | return TYPE; 23 | } 24 | 25 | public ClientMessage next( 26 | MariadbConnectionConfiguration configuration, 27 | byte[] seed, 28 | Sequencer sequencer, 29 | AuthMoreData authMoreData) { 30 | return new ClearPasswordPacket(sequencer, configuration.getPassword()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/Ed25519PasswordPluginFlow.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.authentication.standard; 5 | 6 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 7 | import org.mariadb.r2dbc.authentication.AuthenticationPlugin; 8 | import org.mariadb.r2dbc.message.AuthMoreData; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.client.Ed25519PasswordPacket; 11 | import org.mariadb.r2dbc.message.server.Sequencer; 12 | 13 | public final class Ed25519PasswordPluginFlow implements AuthenticationPlugin { 14 | 15 | public static final String TYPE = "client_ed25519"; 16 | 17 | public Ed25519PasswordPluginFlow create() { 18 | return new Ed25519PasswordPluginFlow(); 19 | } 20 | 21 | public String type() { 22 | return TYPE; 23 | } 24 | 25 | public ClientMessage next( 26 | MariadbConnectionConfiguration configuration, 27 | byte[] seed, 28 | Sequencer sequencer, 29 | AuthMoreData authMoreData) { 30 | 31 | return new Ed25519PasswordPacket(sequencer, configuration.getPassword(), seed); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/NativePasswordPluginFlow.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.authentication.standard; 5 | 6 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 7 | import org.mariadb.r2dbc.authentication.AuthenticationPlugin; 8 | import org.mariadb.r2dbc.message.AuthMoreData; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.client.NativePasswordPacket; 11 | import org.mariadb.r2dbc.message.server.Sequencer; 12 | 13 | public final class NativePasswordPluginFlow implements AuthenticationPlugin { 14 | 15 | public static final String TYPE = "mysql_native_password"; 16 | 17 | public NativePasswordPluginFlow create() { 18 | return new NativePasswordPluginFlow(); 19 | } 20 | 21 | public String type() { 22 | return TYPE; 23 | } 24 | 25 | public ClientMessage next( 26 | MariadbConnectionConfiguration configuration, 27 | byte[] seed, 28 | Sequencer sequencer, 29 | AuthMoreData authMoreData) { 30 | return new NativePasswordPacket(sequencer, configuration.getPassword(), seed); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/PamPluginFlow.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.authentication.standard; 5 | 6 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 7 | import org.mariadb.r2dbc.authentication.AuthenticationPlugin; 8 | import org.mariadb.r2dbc.message.AuthMoreData; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.client.ClearPasswordPacket; 11 | import org.mariadb.r2dbc.message.server.Sequencer; 12 | 13 | public final class PamPluginFlow implements AuthenticationPlugin { 14 | 15 | public static final String TYPE = "dialog"; 16 | private int counter = -1; 17 | 18 | public PamPluginFlow create() { 19 | return new PamPluginFlow(); 20 | } 21 | 22 | public String type() { 23 | return TYPE; 24 | } 25 | 26 | public ClientMessage next( 27 | MariadbConnectionConfiguration configuration, 28 | byte[] seed, 29 | Sequencer sequencer, 30 | AuthMoreData authMoreData) { 31 | while (true) { 32 | counter++; 33 | if (counter == 0) { 34 | return new ClearPasswordPacket(sequencer, configuration.getPassword()); 35 | } else { 36 | if (configuration.getPamOtherPwd() == null) { 37 | throw new IllegalArgumentException( 38 | "PAM authentication is set with multiple password, but pamOtherPwd option wasn't" 39 | + " set"); 40 | } 41 | if (configuration.getPamOtherPwd().length < counter) { 42 | throw new IllegalArgumentException( 43 | String.format( 44 | "PAM authentication required at least %s passwords, but pamOtherPwd has only %s", 45 | counter, configuration.getPamOtherPwd().length)); 46 | } 47 | return new ClearPasswordPacket(sequencer, configuration.getPamOtherPwd()[counter - 1]); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/README: -------------------------------------------------------------------------------- 1 | This plugin uses public domain ed25519 code by str4d (https://github.com/str4d/ed25519-java). 2 | It is "ref10" java implementation from the SUPERCOP ed25519: 3 | 4 | ============================== 5 | MariaDB changes: 6 | - doesn't register ed25519 to global java providers 7 | - remove unused classes 8 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/Utils.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519; 4 | 5 | /** 6 | * Basic utilities for EdDSA. Not for external use, not maintained as a public API. 7 | * 8 | * @author str4d 9 | */ 10 | public class Utils { 11 | 12 | /** 13 | * Constant-time byte comparison. 14 | * 15 | * @param b a byte 16 | * @param c a byte 17 | * @return 1 if b and c are equal, 0 otherwise. 18 | */ 19 | public static int equal(int b, int c) { 20 | int result = 0; 21 | int xor = b ^ c; 22 | for (int i = 0; i < 8; i++) { 23 | result |= xor >> i; 24 | } 25 | return (result ^ 0x01) & 0x01; 26 | } 27 | 28 | /** 29 | * Constant-time byte[] comparison. 30 | * 31 | * @param b a byte[] 32 | * @param c a byte[] 33 | * @return 1 if b and c are equal, 0 otherwise. 34 | */ 35 | public static int equal(byte[] b, byte[] c) { 36 | int result = 0; 37 | for (int i = 0; i < 32; i++) { 38 | result |= b[i] ^ c[i]; 39 | } 40 | 41 | return equal(result, 0); 42 | } 43 | 44 | /** 45 | * Constant-time determine if byte is negative. 46 | * 47 | * @param b the byte to check. 48 | * @return 1 if the byte is negative, 0 otherwise. 49 | */ 50 | public static int negative(int b) { 51 | return (b >> 8) & 1; 52 | } 53 | 54 | /** 55 | * Get the i'th bit of a byte array. 56 | * 57 | * @param h the byte array. 58 | * @param i the bit index. 59 | * @return 0 or 1, the value of the i'th bit in h 60 | */ 61 | public static int bit(byte[] h, int i) { 62 | return (h[i >> 3] >> (i & 7)) & 1; 63 | } 64 | 65 | /** 66 | * Converts a hex string to bytes. 67 | * 68 | * @param s the hex string to be converted. 69 | * @return the byte[] 70 | */ 71 | public static byte[] hexToBytes(String s) { 72 | int len = s.length(); 73 | byte[] data = new byte[len / 2]; 74 | for (int i = 0; i < len; i += 2) { 75 | data[i / 2] = 76 | (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); 77 | } 78 | return data; 79 | } 80 | 81 | /** 82 | * Converts bytes to a hex string. 83 | * 84 | * @param raw the byte[] to be converted. 85 | * @return the hex representation as a string. 86 | */ 87 | public static String bytesToHex(byte[] raw) { 88 | if (raw == null) { 89 | return null; 90 | } 91 | final StringBuilder hex = new StringBuilder(2 * raw.length); 92 | for (final byte b : raw) { 93 | hex.append(Character.forDigit((b & 0xF0) >> 4, 16)) 94 | .append(Character.forDigit((b & 0x0F), 16)); 95 | } 96 | return hex.toString(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/math/Constants.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519.math; 4 | 5 | import org.mariadb.r2dbc.authentication.standard.ed25519.Utils; 6 | 7 | final class Constants { 8 | 9 | public static final byte[] ZERO = 10 | Utils.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"); 11 | public static final byte[] ONE = 12 | Utils.hexToBytes("0100000000000000000000000000000000000000000000000000000000000000"); 13 | public static final byte[] TWO = 14 | Utils.hexToBytes("0200000000000000000000000000000000000000000000000000000000000000"); 15 | public static final byte[] FOUR = 16 | Utils.hexToBytes("0400000000000000000000000000000000000000000000000000000000000000"); 17 | public static final byte[] FIVE = 18 | Utils.hexToBytes("0500000000000000000000000000000000000000000000000000000000000000"); 19 | public static final byte[] EIGHT = 20 | Utils.hexToBytes("0800000000000000000000000000000000000000000000000000000000000000"); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/math/Curve.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519.math; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * A twisted Edwards curve. Points on the curve satisfy $-x^2 + y^2 = 1 + d x^2y^2$ 9 | * 10 | * @author str4d 11 | */ 12 | public class Curve implements Serializable { 13 | 14 | private static final long serialVersionUID = 4578920872509827L; 15 | private final Field f; 16 | private final FieldElement d; 17 | private final FieldElement d2; 18 | private final FieldElement I; 19 | 20 | private final GroupElement zeroP2; 21 | private final GroupElement zeroP3; 22 | private final GroupElement zeroPrecomp; 23 | 24 | public Curve(Field f, byte[] d, FieldElement I) { 25 | this.f = f; 26 | this.d = f.fromByteArray(d); 27 | this.d2 = this.d.add(this.d); 28 | this.I = I; 29 | 30 | FieldElement zero = f.ZERO; 31 | FieldElement one = f.ONE; 32 | zeroP2 = GroupElement.p2(this, zero, one, one); 33 | zeroP3 = GroupElement.p3(this, zero, one, one, zero); 34 | zeroPrecomp = GroupElement.precomp(this, one, one, zero); 35 | } 36 | 37 | public Field getField() { 38 | return f; 39 | } 40 | 41 | public FieldElement getD() { 42 | return d; 43 | } 44 | 45 | public FieldElement get2D() { 46 | return d2; 47 | } 48 | 49 | public FieldElement getI() { 50 | return I; 51 | } 52 | 53 | public GroupElement getZero(GroupElement.Representation repr) { 54 | switch (repr) { 55 | case P2: 56 | return zeroP2; 57 | case P3: 58 | return zeroP3; 59 | case PRECOMP: 60 | return zeroPrecomp; 61 | default: 62 | return null; 63 | } 64 | } 65 | 66 | public GroupElement createPoint(byte[] P, boolean precompute) { 67 | GroupElement ge = new GroupElement(this, P); 68 | if (precompute) { 69 | ge.precompute(true); 70 | } 71 | return ge; 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return f.hashCode() ^ d.hashCode() ^ I.hashCode(); 77 | } 78 | 79 | @Override 80 | public boolean equals(Object o) { 81 | if (o == this) { 82 | return true; 83 | } 84 | if (!(o instanceof Curve)) { 85 | return false; 86 | } 87 | Curve c = (Curve) o; 88 | return f.equals(c.getField()) && d.equals(c.getD()) && I.equals(c.getI()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/math/Encoding.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519.math; 4 | 5 | /** 6 | * Common interface for all $(b-1)$-bit encodings of elements of EdDSA finite fields. 7 | * 8 | * @author str4d 9 | */ 10 | public abstract class Encoding { 11 | 12 | protected Field f; 13 | 14 | public synchronized void setField(Field f) { 15 | if (this.f != null) { 16 | throw new IllegalStateException("already set"); 17 | } 18 | this.f = f; 19 | } 20 | 21 | /** 22 | * Encode a FieldElement in its $(b-1)$-bit encoding. 23 | * 24 | * @param x the FieldElement to encode 25 | * @return the $(b-1)$-bit encoding of this FieldElement. 26 | */ 27 | public abstract byte[] encode(FieldElement x); 28 | 29 | /** 30 | * Decode a FieldElement from its $(b-1)$-bit encoding. The highest bit is masked out. 31 | * 32 | * @param in the $(b-1)$-bit encoding of a FieldElement. 33 | * @return the FieldElement represented by 'val'. 34 | */ 35 | public abstract FieldElement decode(byte[] in); 36 | 37 | /** 38 | * From the Ed25519 paper:
39 | * $x$ is negative if the $(b-1)$-bit encoding of $x$ is lexicographically larger than the 40 | * $(b-1)$-bit encoding of -x. If $q$ is an odd prime and the encoding is the little-endian 41 | * representation of $\{0, 1,\dots, q-1\}$ then the negative elements of $F_q$ are $\{1, 3, 42 | * 5,\dots, q-2\}$. 43 | * 44 | * @param x the FieldElement to check 45 | * @return true if negative 46 | */ 47 | public abstract boolean isNegative(FieldElement x); 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/math/Field.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519.math; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * An EdDSA finite field. Includes several pre-computed values. 9 | * 10 | * @author str4d 11 | */ 12 | public class Field implements Serializable { 13 | 14 | private static final long serialVersionUID = 8746587465875676L; 15 | 16 | public final FieldElement ZERO; 17 | public final FieldElement ONE; 18 | public final FieldElement TWO; 19 | public final FieldElement FOUR; 20 | public final FieldElement FIVE; 21 | public final FieldElement EIGHT; 22 | 23 | private final int b; 24 | private final FieldElement q; 25 | /** 26 | * q-2 27 | */ 28 | private final FieldElement qm2; 29 | /** 30 | * (q-5) / 8 31 | */ 32 | private final FieldElement qm5d8; 33 | 34 | private final Encoding enc; 35 | 36 | public Field(int b, byte[] q, Encoding enc) { 37 | this.b = b; 38 | this.enc = enc; 39 | this.enc.setField(this); 40 | 41 | this.q = fromByteArray(q); 42 | 43 | // Set up constants 44 | ZERO = fromByteArray(Constants.ZERO); 45 | ONE = fromByteArray(Constants.ONE); 46 | TWO = fromByteArray(Constants.TWO); 47 | FOUR = fromByteArray(Constants.FOUR); 48 | FIVE = fromByteArray(Constants.FIVE); 49 | EIGHT = fromByteArray(Constants.EIGHT); 50 | 51 | // Precompute values 52 | qm2 = this.q.subtract(TWO); 53 | qm5d8 = this.q.subtract(FIVE).divide(EIGHT); 54 | } 55 | 56 | public FieldElement fromByteArray(byte[] x) { 57 | return enc.decode(x); 58 | } 59 | 60 | public int getb() { 61 | return b; 62 | } 63 | 64 | public FieldElement getQ() { 65 | return q; 66 | } 67 | 68 | public FieldElement getQm2() { 69 | return qm2; 70 | } 71 | 72 | public FieldElement getQm5d8() { 73 | return qm5d8; 74 | } 75 | 76 | public Encoding getEncoding() { 77 | return enc; 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | return q.hashCode(); 83 | } 84 | 85 | @Override 86 | public boolean equals(Object obj) { 87 | if (!(obj instanceof Field)) { 88 | return false; 89 | } 90 | Field f = (Field) obj; 91 | return b == f.b && q.equals(f.q); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/math/FieldElement.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519.math; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * Note: concrete subclasses must implement hashCode() and equals() 9 | */ 10 | public abstract class FieldElement implements Serializable { 11 | 12 | private static final long serialVersionUID = 1239527465875676L; 13 | 14 | protected final Field f; 15 | 16 | public FieldElement(Field f) { 17 | if (null == f) { 18 | throw new IllegalArgumentException("field cannot be null"); 19 | } 20 | this.f = f; 21 | } 22 | 23 | /** 24 | * Encode a FieldElement in its $(b-1)$-bit encoding. 25 | * 26 | * @return the $(b-1)$-bit encoding of this FieldElement. 27 | */ 28 | public byte[] toByteArray() { 29 | return f.getEncoding().encode(this); 30 | } 31 | 32 | public abstract boolean isNonZero(); 33 | 34 | public boolean isNegative() { 35 | return f.getEncoding().isNegative(this); 36 | } 37 | 38 | public abstract FieldElement add(FieldElement val); 39 | 40 | public FieldElement addOne() { 41 | return add(f.ONE); 42 | } 43 | 44 | public abstract FieldElement subtract(FieldElement val); 45 | 46 | public FieldElement subtractOne() { 47 | return subtract(f.ONE); 48 | } 49 | 50 | public abstract FieldElement negate(); 51 | 52 | public FieldElement divide(FieldElement val) { 53 | return multiply(val.invert()); 54 | } 55 | 56 | public abstract FieldElement multiply(FieldElement val); 57 | 58 | public abstract FieldElement square(); 59 | 60 | public abstract FieldElement squareAndDouble(); 61 | 62 | public abstract FieldElement invert(); 63 | 64 | public abstract FieldElement pow22523(); 65 | 66 | public abstract FieldElement cmov(FieldElement val, final int b); 67 | 68 | // Note: concrete subclasses must implement hashCode() and equals() 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/spec/EdDSANamedCurveSpec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519.spec; 4 | 5 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.Curve; 6 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.GroupElement; 7 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.ed25519.ScalarOps; 8 | 9 | /** 10 | * EdDSA Curve specification that can also be referred to by name. 11 | * 12 | * @author str4d 13 | */ 14 | public class EdDSANamedCurveSpec extends EdDSAParameterSpec { 15 | 16 | private static final long serialVersionUID = -4771155800270949200L; 17 | private final String name; 18 | 19 | public EdDSANamedCurveSpec( 20 | String name, Curve curve, String hashAlgo, ScalarOps sc, GroupElement B) { 21 | super(curve, hashAlgo, sc, B); 22 | this.name = name; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/spec/EdDSANamedCurveTable.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519.spec; 4 | 5 | import java.util.Hashtable; 6 | import java.util.Locale; 7 | import org.mariadb.r2dbc.authentication.standard.ed25519.Utils; 8 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.Curve; 9 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.Field; 10 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.ed25519.Ed25519LittleEndianEncoding; 11 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.ed25519.ScalarOps; 12 | 13 | /** 14 | * The named EdDSA curves. 15 | * 16 | * @author str4d 17 | */ 18 | public class EdDSANamedCurveTable { 19 | 20 | public static final String ED_25519 = "Ed25519"; 21 | 22 | private static final Field ed25519field = 23 | new Field( 24 | 256, // b 25 | Utils.hexToBytes("edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"), // q 26 | new Ed25519LittleEndianEncoding()); 27 | 28 | private static final Curve ed25519curve = 29 | new Curve( 30 | ed25519field, 31 | Utils.hexToBytes("a3785913ca4deb75abd841414d0a700098e879777940c78c73fe6f2bee6c0352"), // d 32 | ed25519field.fromByteArray( 33 | Utils.hexToBytes( 34 | "b0a00e4a271beec478e42fad0618432fa7d7fb3d99004d2b0bdfc14f8024832b"))); // I 35 | 36 | private static final EdDSANamedCurveSpec ed25519 = 37 | new EdDSANamedCurveSpec( 38 | ED_25519, 39 | ed25519curve, 40 | "SHA-512", // H 41 | new ScalarOps(), // l 42 | ed25519curve.createPoint( // B 43 | Utils.hexToBytes("5866666666666666666666666666666666666666666666666666666666666666"), 44 | true)); // Precompute tables for B 45 | 46 | private static final Hashtable curves = 47 | new Hashtable(); 48 | 49 | static { 50 | // RFC 8032 51 | defineCurve(ed25519); 52 | } 53 | 54 | public static void defineCurve(EdDSANamedCurveSpec curve) { 55 | curves.put(curve.getName().toLowerCase(Locale.ENGLISH), curve); 56 | } 57 | 58 | static void defineCurveAlias(String name, String alias) { 59 | EdDSANamedCurveSpec curve = curves.get(name.toLowerCase(Locale.ENGLISH)); 60 | if (curve == null) { 61 | throw new IllegalStateException(); 62 | } 63 | curves.put(alias.toLowerCase(Locale.ENGLISH), curve); 64 | } 65 | 66 | public static EdDSANamedCurveSpec getByName(String name) { 67 | return curves.get(name.toLowerCase(Locale.ENGLISH)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/authentication/standard/ed25519/spec/EdDSAParameterSpec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | package org.mariadb.r2dbc.authentication.standard.ed25519.spec; 4 | 5 | import java.io.Serializable; 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.spec.AlgorithmParameterSpec; 9 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.Curve; 10 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.GroupElement; 11 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.ed25519.ScalarOps; 12 | 13 | /** 14 | * Parameter specification for an EdDSA algorithm. 15 | * 16 | * @author str4d 17 | */ 18 | public class EdDSAParameterSpec implements AlgorithmParameterSpec, Serializable { 19 | 20 | private static final long serialVersionUID = 8274987108472012L; 21 | private final Curve curve; 22 | private final String hashAlgo; 23 | private final ScalarOps sc; 24 | private final GroupElement B; 25 | 26 | /** 27 | * @param curve the curve 28 | * @param hashAlgo the JCA string for the hash algorithm 29 | * @param sc the parameter L represented as ScalarOps 30 | * @param B the parameter B 31 | * @throws IllegalArgumentException if hash algorithm is unsupported or length is wrong 32 | */ 33 | public EdDSAParameterSpec(Curve curve, String hashAlgo, ScalarOps sc, GroupElement B) { 34 | try { 35 | MessageDigest hash = MessageDigest.getInstance(hashAlgo); 36 | // EdDSA hash function must produce 2b-bit output 37 | if (curve.getField().getb() / 4 != hash.getDigestLength()) { 38 | throw new IllegalArgumentException("Hash output is not 2b-bit"); 39 | } 40 | } catch (NoSuchAlgorithmException e) { 41 | throw new IllegalArgumentException("Unsupported hash algorithm"); 42 | } 43 | 44 | this.curve = curve; 45 | this.hashAlgo = hashAlgo; 46 | this.sc = sc; 47 | this.B = B; 48 | } 49 | 50 | public Curve getCurve() { 51 | return curve; 52 | } 53 | 54 | public String getHashAlgorithm() { 55 | return hashAlgo; 56 | } 57 | 58 | public ScalarOps getScalarOps() { 59 | return sc; 60 | } 61 | 62 | /** @return the base (generator) */ 63 | public GroupElement getB() { 64 | return B; 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return hashAlgo.hashCode() ^ curve.hashCode() ^ B.hashCode(); 70 | } 71 | 72 | @Override 73 | public boolean equals(Object o) { 74 | if (o == this) { 75 | return true; 76 | } 77 | if (!(o instanceof EdDSAParameterSpec)) { 78 | return false; 79 | } 80 | EdDSAParameterSpec s = (EdDSAParameterSpec) o; 81 | return hashAlgo.equals(s.getHashAlgorithm()) 82 | && curve.equals(s.getCurve()) 83 | && B.equals(s.getB()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/Client.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.r2dbc.spi.TransactionDefinition; 7 | import org.mariadb.r2dbc.ExceptionFactory; 8 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.Context; 11 | import org.mariadb.r2dbc.message.ServerMessage; 12 | import org.mariadb.r2dbc.message.client.ExecutePacket; 13 | import org.mariadb.r2dbc.message.client.PreparePacket; 14 | import org.mariadb.r2dbc.message.client.SslRequestPacket; 15 | import org.mariadb.r2dbc.message.server.InitialHandshakePacket; 16 | import org.mariadb.r2dbc.util.HostAddress; 17 | import org.mariadb.r2dbc.util.PrepareCache; 18 | import org.mariadb.r2dbc.util.ServerPrepareResult; 19 | import reactor.core.publisher.Flux; 20 | import reactor.core.publisher.Mono; 21 | 22 | public interface Client { 23 | 24 | Mono close(); 25 | 26 | boolean closeChannelIfNeeded(); 27 | 28 | void handleConnectionError(Throwable throwable); 29 | 30 | void sendCommandWithoutResult(ClientMessage requests); 31 | 32 | Flux sendCommand(ClientMessage requests, boolean canSafelyBeReExecuted); 33 | 34 | Flux sendCommand( 35 | ClientMessage requests, DecoderState initialState, boolean canSafelyBeReExecuted); 36 | 37 | Flux sendCommand( 38 | ClientMessage requests, DecoderState initialState, String sql, boolean canSafelyBeReExecuted); 39 | 40 | Flux sendCommand( 41 | PreparePacket preparePacket, ExecutePacket executePacket, boolean canSafelyBeReExecuted); 42 | 43 | Mono sendPrepare( 44 | ClientMessage requests, ExceptionFactory factory, String sql); 45 | 46 | Mono sendSslRequest( 47 | SslRequestPacket sslRequest, MariadbConnectionConfiguration configuration); 48 | 49 | boolean isAutoCommit(); 50 | 51 | boolean isInTransaction(); 52 | 53 | boolean noBackslashEscapes(); 54 | 55 | ServerVersion getVersion(); 56 | 57 | boolean isConnected(); 58 | 59 | boolean isCloseRequested(); 60 | 61 | void setContext(InitialHandshakePacket packet, long clientCapabilities); 62 | 63 | Context getContext(); 64 | 65 | PrepareCache getPrepareCache(); 66 | 67 | Mono beginTransaction(); 68 | 69 | Mono beginTransaction(TransactionDefinition definition); 70 | 71 | Mono commitTransaction(); 72 | 73 | Mono rollbackTransaction(); 74 | 75 | Mono setAutoCommit(boolean autoCommit); 76 | 77 | Mono rollbackTransactionToSavepoint(String name); 78 | 79 | long getThreadId(); 80 | 81 | HostAddress getHostAddress(); 82 | 83 | Mono redirect(); 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/DecoderStateInterface.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import org.mariadb.r2dbc.message.ServerMessage; 8 | import org.mariadb.r2dbc.message.server.Sequencer; 9 | 10 | public interface DecoderStateInterface { 11 | 12 | default DecoderState decoder(short val, int len) { 13 | return (DecoderState) this; 14 | } 15 | 16 | default ServerMessage decode(ByteBuf body, Sequencer sequencer, MariadbFrameDecoder decoder) { 17 | throw new IllegalArgumentException("unexpected state"); 18 | } 19 | 20 | default DecoderState next(MariadbFrameDecoder decoder) { 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/Exchange.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import java.util.concurrent.atomic.AtomicLongFieldUpdater; 7 | import org.mariadb.r2dbc.message.ServerMessage; 8 | import reactor.core.publisher.FluxSink; 9 | import reactor.core.publisher.Operators; 10 | 11 | public class Exchange { 12 | private static final AtomicLongFieldUpdater DEMAND_UPDATER = 13 | AtomicLongFieldUpdater.newUpdater(Exchange.class, "demand"); 14 | private final FluxSink sink; 15 | private final DecoderState initialState; 16 | private final String sql; 17 | private volatile long demand = 0L; 18 | 19 | public Exchange(FluxSink sink, DecoderState initialState) { 20 | this.sink = sink; 21 | this.initialState = initialState; 22 | this.sql = null; 23 | } 24 | 25 | public Exchange(FluxSink sink, DecoderState initialState, String sql) { 26 | this.sink = sink; 27 | this.initialState = initialState; 28 | this.sql = sql; 29 | } 30 | 31 | public DecoderState getInitialState() { 32 | return initialState; 33 | } 34 | 35 | public String getSql() { 36 | return sql; 37 | } 38 | 39 | public boolean hasDemand() { 40 | return demand > 0; 41 | } 42 | 43 | public boolean isCancelled() { 44 | return sink.isCancelled(); 45 | } 46 | 47 | public void onError(Throwable throwable) { 48 | if (!this.sink.isCancelled()) { 49 | this.sink.error(throwable); 50 | } 51 | } 52 | 53 | /** 54 | * Emit server message. 55 | * 56 | * @param srvMsg message to emit 57 | * @return true if ending message 58 | */ 59 | public boolean emit(ServerMessage srvMsg) { 60 | if (this.sink.isCancelled()) { 61 | srvMsg.release(); 62 | return srvMsg.ending(); 63 | } 64 | 65 | Operators.addCap(DEMAND_UPDATER, this, -1); 66 | this.sink.next(srvMsg); 67 | if (srvMsg.ending()) { 68 | if (!this.sink.isCancelled()) { 69 | this.sink.complete(); 70 | } 71 | return true; 72 | } 73 | return false; 74 | } 75 | 76 | public void incrementDemand(long n) { 77 | Operators.addCap(DEMAND_UPDATER, this, n); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/MariadbOutParametersMetadata.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.r2dbc.spi.OutParameterMetadata; 7 | import io.r2dbc.spi.OutParametersMetadata; 8 | import java.util.*; 9 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 10 | import org.mariadb.r2dbc.util.Assert; 11 | 12 | public final class MariadbOutParametersMetadata implements OutParametersMetadata { 13 | 14 | private final List metadataList; 15 | private volatile Collection columnNames; 16 | private Map mapper = null; 17 | 18 | MariadbOutParametersMetadata(List metadataList) { 19 | this.metadataList = metadataList; 20 | } 21 | 22 | @Override 23 | public ColumnDefinitionPacket getParameterMetadata(int index) { 24 | if (index < 0 || index >= this.metadataList.size()) { 25 | throw new IllegalArgumentException( 26 | String.format( 27 | "Column index %d is not in permit range[0,%s]", index, this.metadataList.size() - 1)); 28 | } 29 | return this.metadataList.get(index); 30 | } 31 | 32 | @Override 33 | public ColumnDefinitionPacket getParameterMetadata(String name) { 34 | return metadataList.get(getIndex(name)); 35 | } 36 | 37 | @Override 38 | public List getParameterMetadatas() { 39 | return Collections.unmodifiableList(this.metadataList); 40 | } 41 | 42 | int getIndex(String name) throws NoSuchElementException { 43 | Assert.requireNonNull(name, "name must not be null"); 44 | 45 | if (mapper == null) { 46 | Map tmpmapper = new HashMap<>(); 47 | for (int i = 0; i < metadataList.size(); i++) { 48 | ColumnDefinitionPacket ci = metadataList.get(i); 49 | String columnAlias = ci.getName(); 50 | if (columnAlias == null || columnAlias.isEmpty()) { 51 | String columnName = ci.getColumn(); 52 | if (columnName != null && !columnName.isEmpty()) { 53 | columnName = columnName.toLowerCase(Locale.ROOT); 54 | tmpmapper.putIfAbsent(columnName, i); 55 | } 56 | } else { 57 | tmpmapper.putIfAbsent(columnAlias.toLowerCase(Locale.ROOT), i); 58 | } 59 | } 60 | mapper = tmpmapper; 61 | } 62 | 63 | Integer ind = mapper.get(name.toLowerCase(Locale.ROOT)); 64 | if (ind == null) { 65 | throw new NoSuchElementException( 66 | String.format( 67 | "Column name '%s' does not exist in column names %s", 68 | name, Collections.unmodifiableCollection(mapper.keySet()))); 69 | } 70 | return ind; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/MariadbPacketEncoder.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import io.netty.buffer.CompositeByteBuf; 9 | import io.netty.buffer.Unpooled; 10 | import org.mariadb.r2dbc.message.ClientMessage; 11 | import org.mariadb.r2dbc.message.Context; 12 | import reactor.core.publisher.Mono; 13 | 14 | public class MariadbPacketEncoder { 15 | private Context context = null; 16 | 17 | public Mono encodeFlux(ClientMessage msg) { 18 | ByteBufAllocator allocator = context.getByteBufAllocator(); 19 | 20 | return msg.encode(context, allocator) 21 | .map( 22 | buf -> { 23 | CompositeByteBuf out = allocator.compositeBuffer(); 24 | 25 | int initialReaderIndex = buf.readerIndex(); 26 | int packetLength; 27 | do { 28 | packetLength = Math.min(0xffffff, buf.readableBytes()); 29 | 30 | ByteBuf header = Unpooled.buffer(4, 4); 31 | header.writeMediumLE(packetLength); 32 | header.writeByte(msg.getSequencer().next()); 33 | 34 | out.addComponent(true, header); 35 | out.addComponent(true, buf.readRetainedSlice(packetLength)); 36 | 37 | } while (buf.readableBytes() > 0); 38 | 39 | if (packetLength == 0xffffff) { 40 | // in case last packet is full, sending an empty packet to indicate that command is 41 | // complete 42 | ByteBuf header = Unpooled.buffer(4, 4); 43 | header.writeMediumLE(0); 44 | header.writeByte(msg.getSequencer().next()); 45 | out.addComponent(true, header); 46 | } 47 | 48 | context.saveRedo(msg, buf, initialReaderIndex); 49 | buf.release(); 50 | return out; 51 | }); 52 | } 53 | 54 | public void setContext(Context context) { 55 | this.context = context; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/MariadbRow.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.r2dbc.spi.R2dbcTransientResourceException; 8 | import java.util.EnumSet; 9 | import org.mariadb.r2dbc.ExceptionFactory; 10 | import org.mariadb.r2dbc.codec.DataType; 11 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 12 | import org.mariadb.r2dbc.util.Assert; 13 | import reactor.util.annotation.Nullable; 14 | 15 | public abstract class MariadbRow { 16 | protected static final int NULL_LENGTH = -1; 17 | protected final MariadbRowMetadata meta; 18 | protected final ByteBuf buf; 19 | protected final ExceptionFactory factory; 20 | protected int length; 21 | protected int index = -1; 22 | 23 | MariadbRow(ByteBuf buf, MariadbRowMetadata meta, ExceptionFactory factory) { 24 | this.buf = buf; 25 | this.meta = meta; 26 | this.factory = factory; 27 | } 28 | 29 | protected static R2dbcTransientResourceException noDecoderException( 30 | ColumnDefinitionPacket column, Class type) { 31 | 32 | if (type.isArray()) { 33 | if (EnumSet.of( 34 | DataType.TINYINT, 35 | DataType.SMALLINT, 36 | DataType.MEDIUMINT, 37 | DataType.INTEGER, 38 | DataType.BIGINT) 39 | .contains(column.getDataType())) { 40 | throw new R2dbcTransientResourceException( 41 | String.format( 42 | "No decoder for type %s[] and column type %s(%s)", 43 | type.getComponentType().getName(), 44 | column.getDataType().toString(), 45 | column.isSigned() ? "signed" : "unsigned")); 46 | } 47 | throw new R2dbcTransientResourceException( 48 | String.format( 49 | "No decoder for type %s[] and column type %s", 50 | type.getComponentType().getName(), column.getDataType().toString())); 51 | } 52 | if (EnumSet.of( 53 | DataType.TINYINT, 54 | DataType.SMALLINT, 55 | DataType.MEDIUMINT, 56 | DataType.INTEGER, 57 | DataType.BIGINT) 58 | .contains(column.getDataType())) { 59 | throw new R2dbcTransientResourceException( 60 | String.format( 61 | "No decoder for type %s and column type %s(%s)", 62 | type.getName(), 63 | column.getDataType().toString(), 64 | column.isSigned() ? "signed" : "unsigned")); 65 | } 66 | throw new R2dbcTransientResourceException( 67 | String.format( 68 | "No decoder for type %s and column type %s", 69 | type.getName(), column.getDataType().toString())); 70 | } 71 | 72 | public abstract T get(int index, Class type); 73 | 74 | @Nullable 75 | public T get(String name, Class type) { 76 | Assert.requireNonNull(name, "name must not be null"); 77 | return get(this.meta.getIndex(name), type); 78 | } 79 | 80 | @FunctionalInterface 81 | public interface MariadbRowConstructor { 82 | 83 | org.mariadb.r2dbc.api.MariadbRow create( 84 | ByteBuf buf, MariadbRowMetadata meta, ExceptionFactory factory); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/MariadbRowMetadata.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.r2dbc.spi.RowMetadata; 7 | import java.util.*; 8 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 9 | import org.mariadb.r2dbc.util.Assert; 10 | 11 | public final class MariadbRowMetadata implements RowMetadata { 12 | 13 | private final ColumnDefinitionPacket[] metadataList; 14 | private volatile Collection columnNames; 15 | private Map mapper = null; 16 | 17 | public MariadbRowMetadata(ColumnDefinitionPacket[] metadataList) { 18 | this.metadataList = metadataList; 19 | } 20 | 21 | @Override 22 | public ColumnDefinitionPacket getColumnMetadata(int index) { 23 | if (index < 0 || index >= this.metadataList.length) { 24 | throw new IndexOutOfBoundsException( 25 | String.format( 26 | "Column index %d is not in permit range[0,%s]", index, this.metadataList.length - 1)); 27 | } 28 | return this.metadataList[index]; 29 | } 30 | 31 | @Override 32 | public ColumnDefinitionPacket getColumnMetadata(String name) { 33 | return metadataList[getIndex(name)]; 34 | } 35 | 36 | @Override 37 | public List getColumnMetadatas() { 38 | return Arrays.asList(this.metadataList); 39 | } 40 | 41 | private Collection getColumnNames(ColumnDefinitionPacket[] columnMetadatas) { 42 | List columnNames = new ArrayList<>(); 43 | for (ColumnDefinitionPacket columnMetadata : columnMetadatas) { 44 | columnNames.add(columnMetadata.getName()); 45 | } 46 | return Collections.unmodifiableCollection(columnNames); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | if (this.columnNames == null) { 52 | this.columnNames = getColumnNames(this.metadataList); 53 | } 54 | return "MariadbRowMetadata{columnNames=" + columnNames + "}"; 55 | } 56 | 57 | @Override 58 | public boolean contains(String columnName) { 59 | if (this.columnNames == null) { 60 | this.columnNames = getColumnNames(this.metadataList); 61 | } 62 | return this.columnNames.stream().anyMatch(columnName::equalsIgnoreCase); 63 | } 64 | 65 | public int size() { 66 | return this.metadataList.length; 67 | } 68 | 69 | ColumnDefinitionPacket get(int index) { 70 | return this.metadataList[index]; 71 | } 72 | 73 | int getIndex(String name) throws NoSuchElementException { 74 | Assert.requireNonNull(name, "name must not be null"); 75 | 76 | if (mapper == null) { 77 | Map tmpmapper = new HashMap<>(); 78 | for (int i = 0; i < metadataList.length; i++) { 79 | ColumnDefinitionPacket ci = metadataList[i]; 80 | String columnAlias = ci.getName(); 81 | if (columnAlias == null || columnAlias.isEmpty()) { 82 | String columnName = ci.getColumn(); 83 | if (columnName != null && !columnName.isEmpty()) { 84 | columnName = columnName.toLowerCase(Locale.ROOT); 85 | tmpmapper.putIfAbsent(columnName, i); 86 | } 87 | } else { 88 | tmpmapper.putIfAbsent(columnAlias.toLowerCase(Locale.ROOT), i); 89 | } 90 | } 91 | mapper = tmpmapper; 92 | } 93 | 94 | Integer ind = mapper.get(name.toLowerCase(Locale.ROOT)); 95 | if (ind == null) { 96 | throw new NoSuchElementException( 97 | String.format( 98 | "Column name '%s' does not exist in column names %s", 99 | name, Collections.unmodifiableCollection(mapper.keySet()))); 100 | } 101 | return ind; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/MariadbRowText.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.r2dbc.spi.R2dbcTransientResourceException; 8 | import org.mariadb.r2dbc.ExceptionFactory; 9 | import org.mariadb.r2dbc.codec.Codec; 10 | import org.mariadb.r2dbc.codec.Codecs; 11 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 12 | import org.mariadb.r2dbc.util.Assert; 13 | import reactor.util.annotation.Nullable; 14 | 15 | public class MariadbRowText extends MariadbRow implements org.mariadb.r2dbc.api.MariadbRow { 16 | 17 | public MariadbRowText(ByteBuf buf, MariadbRowMetadata meta, ExceptionFactory factory) { 18 | super(buf, meta, factory); 19 | this.buf.markReaderIndex(); 20 | } 21 | 22 | public MariadbRowMetadata getMetadata() { 23 | return meta; 24 | } 25 | 26 | @Nullable 27 | @Override 28 | @SuppressWarnings("unchecked") 29 | public T get(int index, Class type) { 30 | ColumnDefinitionPacket column = meta.getColumnMetadata(index); 31 | this.setPosition(index); 32 | 33 | if (length == NULL_LENGTH) { 34 | if (type.isPrimitive()) { 35 | throw new R2dbcTransientResourceException( 36 | String.format("Cannot return null for primitive %s", type.getName())); 37 | } 38 | return null; 39 | } 40 | 41 | Codec defaultCodec; 42 | // type generic, return "natural" java type 43 | if (Object.class == type || type == null) { 44 | defaultCodec = ((Codec) column.getType().getDefaultCodec()); 45 | return defaultCodec.decodeText(buf, length, column, type, factory); 46 | } 47 | 48 | // fast path checking default codec 49 | if ((defaultCodec = (Codec) Codecs.typeMapper.get(type)) != null) { 50 | if (!defaultCodec.canDecode(column, type)) { 51 | buf.skipBytes(length); 52 | throw MariadbRow.noDecoderException(column, type); 53 | } 54 | return defaultCodec.decodeText(buf, length, column, type, factory); 55 | } 56 | 57 | for (Codec codec : Codecs.LIST) { 58 | if (codec.canDecode(column, type)) { 59 | return ((Codec) codec).decodeText(buf, length, column, type, factory); 60 | } 61 | } 62 | 63 | buf.skipBytes(length); 64 | throw MariadbRow.noDecoderException(column, type); 65 | } 66 | 67 | @Nullable 68 | @Override 69 | public T get(String name, Class type) { 70 | Assert.requireNonNull(name, "name must not be null"); 71 | return get(this.meta.getIndex(name), type); 72 | } 73 | 74 | /** 75 | * Set length and pos indicator to asked index. 76 | * 77 | * @param newIndex index (0 is first). 78 | */ 79 | public void setPosition(int newIndex) { 80 | if (index >= newIndex) { 81 | index = -1; 82 | buf.resetReaderIndex(); 83 | } 84 | 85 | index++; 86 | 87 | for (; index < newIndex; index++) { 88 | int type = this.buf.readUnsignedByte(); 89 | switch (type) { 90 | case 252: 91 | buf.skipBytes(buf.readUnsignedShortLE()); 92 | break; 93 | case 253: 94 | buf.skipBytes(buf.readUnsignedMediumLE()); 95 | break; 96 | case 254: 97 | buf.skipBytes((int) (buf.readLongLE())); 98 | break; 99 | case 251: 100 | break; 101 | default: 102 | buf.skipBytes(type); 103 | break; 104 | } 105 | } 106 | short type = this.buf.readUnsignedByte(); 107 | switch (type) { 108 | case 251: 109 | length = NULL_LENGTH; 110 | break; 111 | case 252: 112 | length = buf.readUnsignedShortLE(); 113 | break; 114 | case 253: 115 | length = buf.readUnsignedMediumLE(); 116 | break; 117 | case 254: 118 | length = (int) buf.readLongLE(); 119 | break; 120 | default: 121 | length = type; 122 | break; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/RedoContext.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import io.r2dbc.spi.IsolationLevel; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.util.constants.ServerStatus; 11 | 12 | public class RedoContext extends SimpleContext { 13 | 14 | private final TransactionSaver transactionSaver; 15 | 16 | public RedoContext( 17 | String serverVersion, 18 | long threadId, 19 | long capabilities, 20 | short serverStatus, 21 | boolean mariaDBServer, 22 | long clientCapabilities, 23 | String database, 24 | ByteBufAllocator byteBufAllocator, 25 | IsolationLevel isolationLevel) { 26 | super( 27 | serverVersion, 28 | threadId, 29 | capabilities, 30 | serverStatus, 31 | mariaDBServer, 32 | clientCapabilities, 33 | database, 34 | byteBufAllocator, 35 | isolationLevel); 36 | transactionSaver = new TransactionSaver(); 37 | } 38 | 39 | /** 40 | * Set server status 41 | * 42 | * @param serverStatus server status 43 | */ 44 | public void setServerStatus(short serverStatus) { 45 | super.setServerStatus(serverStatus); 46 | if ((serverStatus & ServerStatus.IN_TRANSACTION) == 0) { 47 | transactionSaver.clear(); 48 | } 49 | } 50 | 51 | /** 52 | * Save client message 53 | * 54 | * @param msg client message 55 | */ 56 | public void saveRedo(ClientMessage msg, ByteBuf buf, int initialReaderIndex) { 57 | msg.save(buf, initialReaderIndex); 58 | transactionSaver.add(msg); 59 | } 60 | 61 | /** 62 | * Get transaction saver cache 63 | * 64 | * @return transaction saver cache 65 | */ 66 | public TransactionSaver getTransactionSaver() { 67 | return transactionSaver; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/SimpleContext.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import io.netty.buffer.ByteBufAllocator; 7 | import io.r2dbc.spi.IsolationLevel; 8 | import org.mariadb.r2dbc.message.Context; 9 | 10 | public class SimpleContext implements Context { 11 | 12 | private final long threadId; 13 | private final long serverCapabilities; 14 | private final long clientCapabilities; 15 | private final ServerVersion version; 16 | private final ByteBufAllocator byteBufAllocator; 17 | private String redirectValue; 18 | private short serverStatus; 19 | private IsolationLevel isolationLevel; 20 | private String database; 21 | 22 | public SimpleContext( 23 | String serverVersion, 24 | long threadId, 25 | long capabilities, 26 | short serverStatus, 27 | boolean mariaDBServer, 28 | long clientCapabilities, 29 | String database, 30 | ByteBufAllocator byteBufAllocator, 31 | IsolationLevel isolationLevel) { 32 | 33 | this.threadId = threadId; 34 | this.serverCapabilities = capabilities; 35 | this.clientCapabilities = clientCapabilities; 36 | this.serverStatus = serverStatus; 37 | this.version = new ServerVersion(serverVersion, mariaDBServer); 38 | this.isolationLevel = isolationLevel; 39 | this.database = database; 40 | this.byteBufAllocator = byteBufAllocator; 41 | this.redirectValue = null; 42 | } 43 | 44 | public long getThreadId() { 45 | return threadId; 46 | } 47 | 48 | public long getServerCapabilities() { 49 | return serverCapabilities; 50 | } 51 | 52 | public long getClientCapabilities() { 53 | return clientCapabilities; 54 | } 55 | 56 | public short getServerStatus() { 57 | return serverStatus; 58 | } 59 | 60 | public void setServerStatus(short serverStatus) { 61 | this.serverStatus = serverStatus; 62 | } 63 | 64 | public String getDatabase() { 65 | return database; 66 | } 67 | 68 | public void setDatabase(String database) { 69 | this.database = database; 70 | } 71 | 72 | public IsolationLevel getIsolationLevel() { 73 | return isolationLevel; 74 | } 75 | 76 | public void setIsolationLevel(IsolationLevel isolationLevel) { 77 | this.isolationLevel = isolationLevel; 78 | } 79 | 80 | public ServerVersion getVersion() { 81 | return version; 82 | } 83 | 84 | public ByteBufAllocator getByteBufAllocator() { 85 | return byteBufAllocator; 86 | } 87 | 88 | @Override 89 | public void setRedirect(String redirectValue) { 90 | this.redirectValue = redirectValue; 91 | } 92 | 93 | public String getRedirectValue() { 94 | return redirectValue; 95 | } 96 | 97 | @Override 98 | public String toString() { 99 | return "ConnectionContext{" + "threadId=" + threadId + ", version=" + version + '}'; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/client/TransactionSaver.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.client; 5 | 6 | import java.util.Queue; 7 | import org.mariadb.r2dbc.message.ClientMessage; 8 | import reactor.util.concurrent.Queues; 9 | 10 | /** 11 | * Transaction cache Huge command are not cached, cache is limited to configuration 12 | * transactionReplaySize commands 13 | */ 14 | public class TransactionSaver { 15 | private final Queue messages = 16 | Queues.get(Queues.SMALL_BUFFER_SIZE).get(); 17 | private boolean dirty = false; 18 | 19 | /** 20 | * Add a command to cache. 21 | * 22 | * @param clientMessage client message 23 | */ 24 | public void add(ClientMessage clientMessage) { 25 | if (!messages.offer(clientMessage)) { 26 | dirty = true; 27 | } 28 | } 29 | 30 | /** Transaction finished, clearing cache */ 31 | public void clear() { 32 | ClientMessage clientMessage; 33 | while ((clientMessage = messages.poll()) != null) clientMessage.releaseSave(); 34 | messages.clear(); 35 | dirty = false; 36 | } 37 | 38 | /** 39 | * Is cache not valid (some commands have not been cached) 40 | * 41 | * @return is dirty 42 | */ 43 | public boolean isDirty() { 44 | return dirty; 45 | } 46 | 47 | public void forceDirty() { 48 | dirty = true; 49 | } 50 | 51 | /** 52 | * cache buffer 53 | * 54 | * @return cached messages 55 | */ 56 | public Queue getMessages() { 57 | return messages; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/codec/Codec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.codec; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.ExceptionFactory; 9 | import org.mariadb.r2dbc.message.Context; 10 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 11 | import reactor.core.publisher.Mono; 12 | 13 | public interface Codec { 14 | 15 | boolean canDecode(ColumnDefinitionPacket column, Class type); 16 | 17 | boolean canEncode(Class value); 18 | 19 | T decodeText( 20 | ByteBuf buffer, 21 | int length, 22 | ColumnDefinitionPacket column, 23 | Class type, 24 | ExceptionFactory factory); 25 | 26 | T decodeBinary( 27 | ByteBuf buffer, 28 | int length, 29 | ColumnDefinitionPacket column, 30 | Class type, 31 | ExceptionFactory factory); 32 | 33 | default Mono encodeText(ByteBufAllocator allocator, Object value, Context context) { 34 | throw new IllegalStateException("Not expected to be use"); 35 | } 36 | 37 | default Mono encodeBinary(ByteBufAllocator allocator, Object value) { 38 | throw new IllegalStateException("Not expected to be use"); 39 | } 40 | 41 | default void encodeDirectText(ByteBuf out, Object value, Context context) { 42 | throw new IllegalStateException("Not expected to be use"); 43 | } 44 | 45 | default void encodeDirectBinary( 46 | ByteBufAllocator allocator, ByteBuf out, Object value, Context context) { 47 | throw new IllegalStateException("Not expected to be use"); 48 | } 49 | 50 | DataType getBinaryEncodeType(); 51 | 52 | default boolean isDirect() { 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/codec/DataType.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.codec; 5 | 6 | public enum DataType { 7 | OLDDECIMAL(0), 8 | TINYINT(1), 9 | SMALLINT(2), 10 | INTEGER(3), 11 | FLOAT(4), 12 | DOUBLE(5), 13 | NULL(6), 14 | TIMESTAMP(7), 15 | BIGINT(8), 16 | MEDIUMINT(9), 17 | DATE(10), 18 | TIME(11), 19 | DATETIME(12), 20 | YEAR(13), 21 | NEWDATE(14), 22 | TEXT(15), 23 | BIT(16), 24 | JSON(245), 25 | DECIMAL(246), 26 | ENUM(247), 27 | SET(248), 28 | TINYBLOB(249), 29 | MEDIUMBLOB(250), 30 | LONGBLOB(251), 31 | BLOB(252), 32 | VARSTRING(253), 33 | STRING(254), 34 | GEOMETRY(255); 35 | 36 | static final DataType[] typeMap; 37 | 38 | static { 39 | typeMap = new DataType[256]; 40 | for (DataType v : values()) { 41 | typeMap[v.mariadbType] = v; 42 | } 43 | } 44 | 45 | private final short mariadbType; 46 | 47 | DataType(int mariadbType) { 48 | this.mariadbType = (short) mariadbType; 49 | } 50 | 51 | /** 52 | * Convert server Type to server type. 53 | * 54 | * @param typeValue type value 55 | * @param charsetNumber charset 56 | * @return MariaDb type 57 | */ 58 | public static DataType fromServer(int typeValue, int charsetNumber) { 59 | 60 | DataType dataType = typeMap[typeValue]; 61 | 62 | if (charsetNumber != 63 && typeValue >= 249 && typeValue <= 252) { 63 | // MariaDB Text dataType 64 | return DataType.TEXT; 65 | } 66 | 67 | return dataType; 68 | } 69 | 70 | public short get() { 71 | return mariadbType; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/codec/list/BitSetCodec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.codec.list; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.BitSet; 10 | import org.mariadb.r2dbc.ExceptionFactory; 11 | import org.mariadb.r2dbc.codec.Codec; 12 | import org.mariadb.r2dbc.codec.DataType; 13 | import org.mariadb.r2dbc.message.Context; 14 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 15 | import org.mariadb.r2dbc.util.BufferUtils; 16 | 17 | public class BitSetCodec implements Codec { 18 | 19 | public static final BitSetCodec INSTANCE = new BitSetCodec(); 20 | 21 | public static BitSet parseBit(ByteBuf buf, int length) { 22 | byte[] arr = new byte[length]; 23 | buf.readBytes(arr); 24 | revertOrder(arr); 25 | return BitSet.valueOf(arr); 26 | } 27 | 28 | public static void revertOrder(byte[] array) { 29 | int i = 0; 30 | int j = array.length - 1; 31 | byte tmp; 32 | while (j > i) { 33 | tmp = array[j]; 34 | array[j] = array[i]; 35 | array[i] = tmp; 36 | j--; 37 | i++; 38 | } 39 | } 40 | 41 | public boolean canDecode(ColumnDefinitionPacket column, Class type) { 42 | return column.getDataType() == DataType.BIT && type.isAssignableFrom(BitSet.class); 43 | } 44 | 45 | @Override 46 | public BitSet decodeText( 47 | ByteBuf buf, 48 | int length, 49 | ColumnDefinitionPacket column, 50 | Class type, 51 | ExceptionFactory factory) { 52 | return parseBit(buf, length); 53 | } 54 | 55 | @Override 56 | public BitSet decodeBinary( 57 | ByteBuf buf, 58 | int length, 59 | ColumnDefinitionPacket column, 60 | Class type, 61 | ExceptionFactory factory) { 62 | return parseBit(buf, length); 63 | } 64 | 65 | public boolean canEncode(Class value) { 66 | return BitSet.class.isAssignableFrom(value); 67 | } 68 | 69 | @Override 70 | public void encodeDirectText(ByteBuf out, Object value, Context context) { 71 | byte[] bytes = ((BitSet) value).toByteArray(); 72 | revertOrder(bytes); 73 | 74 | StringBuilder sb = new StringBuilder(bytes.length * Byte.SIZE + 3); 75 | sb.append("b'"); 76 | for (int i = 0; i < Byte.SIZE * bytes.length; i++) 77 | sb.append((bytes[i / Byte.SIZE] << i % Byte.SIZE & 0x80) == 0 ? '0' : '1'); 78 | sb.append("'"); 79 | out.writeCharSequence(sb.toString(), StandardCharsets.US_ASCII); 80 | } 81 | 82 | @Override 83 | public void encodeDirectBinary( 84 | ByteBufAllocator allocator, ByteBuf out, Object value, Context context) { 85 | byte[] bytes = ((BitSet) value).toByteArray(); 86 | revertOrder(bytes); 87 | out.writeBytes(BufferUtils.encodeLength(bytes.length)); 88 | out.writeBytes(bytes); 89 | } 90 | 91 | public DataType getBinaryEncodeType() { 92 | return DataType.BLOB; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/codec/list/ByteArrayCodec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.codec.list; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.util.EnumSet; 9 | import org.mariadb.r2dbc.ExceptionFactory; 10 | import org.mariadb.r2dbc.codec.Codec; 11 | import org.mariadb.r2dbc.codec.DataType; 12 | import org.mariadb.r2dbc.message.Context; 13 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 14 | import org.mariadb.r2dbc.util.BufferUtils; 15 | 16 | public class ByteArrayCodec implements Codec { 17 | 18 | public static final ByteArrayCodec INSTANCE = new ByteArrayCodec(); 19 | 20 | private static final EnumSet COMPATIBLE_TYPES = 21 | EnumSet.of( 22 | DataType.BLOB, 23 | DataType.TINYBLOB, 24 | DataType.MEDIUMBLOB, 25 | DataType.LONGBLOB, 26 | DataType.GEOMETRY, 27 | DataType.VARSTRING, 28 | DataType.TEXT, 29 | DataType.STRING); 30 | 31 | public boolean canDecode(ColumnDefinitionPacket column, Class type) { 32 | return COMPATIBLE_TYPES.contains(column.getDataType()) 33 | && ((type.isPrimitive() && type == Byte.TYPE && type.isArray()) 34 | || type.isAssignableFrom(byte[].class)); 35 | } 36 | 37 | @Override 38 | public byte[] decodeText( 39 | ByteBuf buf, 40 | int length, 41 | ColumnDefinitionPacket column, 42 | Class type, 43 | ExceptionFactory factory) { 44 | 45 | byte[] arr = new byte[length]; 46 | buf.readBytes(arr); 47 | return arr; 48 | } 49 | 50 | @Override 51 | public byte[] decodeBinary( 52 | ByteBuf buf, 53 | int length, 54 | ColumnDefinitionPacket column, 55 | Class type, 56 | ExceptionFactory factory) { 57 | byte[] arr = new byte[length]; 58 | buf.readBytes(arr); 59 | return arr; 60 | } 61 | 62 | public boolean canEncode(Class value) { 63 | return byte[].class.isAssignableFrom(value); 64 | } 65 | 66 | @Override 67 | public void encodeDirectText(ByteBuf out, Object value, Context context) { 68 | byte[] b = (byte[]) value; 69 | out.writeBytes(BufferUtils.BINARY_PREFIX); 70 | BufferUtils.escapedBytes(out, b, b.length, context); 71 | out.writeByte('\''); 72 | } 73 | 74 | @Override 75 | public void encodeDirectBinary( 76 | ByteBufAllocator allocator, ByteBuf out, Object value, Context context) { 77 | byte[] b = (byte[]) value; 78 | out.writeBytes(BufferUtils.encodeLength(b.length)); 79 | out.writeBytes(b); 80 | } 81 | 82 | public DataType getBinaryEncodeType() { 83 | return DataType.BLOB; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/codec/list/ByteBufferCodec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.codec.list; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.nio.ByteBuffer; 9 | import java.util.EnumSet; 10 | import org.mariadb.r2dbc.ExceptionFactory; 11 | import org.mariadb.r2dbc.codec.Codec; 12 | import org.mariadb.r2dbc.codec.DataType; 13 | import org.mariadb.r2dbc.message.Context; 14 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 15 | import org.mariadb.r2dbc.util.BufferUtils; 16 | 17 | public class ByteBufferCodec implements Codec { 18 | 19 | public static final ByteBufferCodec INSTANCE = new ByteBufferCodec(); 20 | 21 | private static final EnumSet COMPATIBLE_TYPES = 22 | EnumSet.of( 23 | DataType.BIT, 24 | DataType.BLOB, 25 | DataType.TINYBLOB, 26 | DataType.MEDIUMBLOB, 27 | DataType.LONGBLOB, 28 | DataType.STRING, 29 | DataType.VARSTRING, 30 | DataType.TEXT); 31 | 32 | private static ByteBuffer decode( 33 | ByteBuf buf, int length, ColumnDefinitionPacket column, ExceptionFactory factory) { 34 | switch (column.getDataType()) { 35 | case STRING: 36 | case TEXT: 37 | case VARSTRING: 38 | if (!column.isBinary()) { 39 | buf.skipBytes(length); 40 | throw factory.createParsingException( 41 | String.format( 42 | "Data type %s (not binary) cannot be decoded as Blob", column.getDataType())); 43 | } 44 | ByteBuffer value = ByteBuffer.allocate(length); 45 | buf.readBytes(value); 46 | return value; 47 | 48 | default: 49 | // BIT, TINYBLOB, MEDIUMBLOB, LONGBLOB, BLOB, GEOMETRY 50 | byte[] val = new byte[length]; 51 | buf.readBytes(val); 52 | return ByteBuffer.wrap(val); 53 | } 54 | } 55 | 56 | public boolean canDecode(ColumnDefinitionPacket column, Class type) { 57 | return COMPATIBLE_TYPES.contains(column.getDataType()) 58 | && type.isAssignableFrom(ByteBuffer.class); 59 | } 60 | 61 | @Override 62 | public ByteBuffer decodeText( 63 | ByteBuf buf, 64 | int length, 65 | ColumnDefinitionPacket column, 66 | Class type, 67 | ExceptionFactory factory) { 68 | return decode(buf, length, column, factory); 69 | } 70 | 71 | @Override 72 | public ByteBuffer decodeBinary( 73 | ByteBuf buf, 74 | int length, 75 | ColumnDefinitionPacket column, 76 | Class type, 77 | ExceptionFactory factory) { 78 | return decode(buf, length, column, factory); 79 | } 80 | 81 | public boolean canEncode(Class value) { 82 | return ByteBuffer.class.isAssignableFrom(value); 83 | } 84 | 85 | @Override 86 | public void encodeDirectText(ByteBuf out, Object value, Context context) { 87 | out.writeBytes(BufferUtils.BINARY_PREFIX); 88 | ByteBuffer val = (ByteBuffer) value; 89 | if (val.hasArray()) { 90 | BufferUtils.escapedBytes(out, val.array(), val.remaining(), context); 91 | } else { 92 | byte[] arr = new byte[val.remaining()]; 93 | val.get(arr); 94 | BufferUtils.escapedBytes(out, arr, arr.length, context); 95 | } 96 | out.writeByte('\''); 97 | } 98 | 99 | @Override 100 | public void encodeDirectBinary( 101 | ByteBufAllocator allocator, ByteBuf out, Object value, Context context) { 102 | ByteBuffer val = (ByteBuffer) value; 103 | out.writeBytes(BufferUtils.encodeLength(val.remaining())); 104 | out.writeBytes(val); 105 | } 106 | 107 | public DataType getBinaryEncodeType() { 108 | return DataType.BLOB; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/codec/list/ClobCodec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.codec.list; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import io.r2dbc.spi.Clob; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.EnumSet; 11 | import org.mariadb.r2dbc.ExceptionFactory; 12 | import org.mariadb.r2dbc.codec.Codec; 13 | import org.mariadb.r2dbc.codec.DataType; 14 | import org.mariadb.r2dbc.message.Context; 15 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 16 | import org.mariadb.r2dbc.util.BufferUtils; 17 | import reactor.core.publisher.Flux; 18 | import reactor.core.publisher.Mono; 19 | 20 | public class ClobCodec implements Codec { 21 | 22 | public static final ClobCodec INSTANCE = new ClobCodec(); 23 | 24 | private static final EnumSet COMPATIBLE_TYPES = 25 | EnumSet.of(DataType.TEXT, DataType.VARSTRING, DataType.STRING); 26 | 27 | public boolean canDecode(ColumnDefinitionPacket column, Class type) { 28 | return COMPATIBLE_TYPES.contains(column.getDataType()) && (type.isAssignableFrom(Clob.class)); 29 | } 30 | 31 | public boolean canEncode(Class value) { 32 | return Clob.class.isAssignableFrom(value); 33 | } 34 | 35 | @Override 36 | public Clob decodeText( 37 | ByteBuf buf, 38 | int length, 39 | ColumnDefinitionPacket column, 40 | Class type, 41 | ExceptionFactory factory) { 42 | String rawValue = buf.readCharSequence(length, StandardCharsets.UTF_8).toString(); 43 | return Clob.from(Mono.just(rawValue)); 44 | } 45 | 46 | @Override 47 | public Clob decodeBinary( 48 | ByteBuf buf, 49 | int length, 50 | ColumnDefinitionPacket column, 51 | Class type, 52 | ExceptionFactory factory) { 53 | String rawValue = buf.readCharSequence(length, StandardCharsets.UTF_8).toString(); 54 | return Clob.from(Mono.just(rawValue)); 55 | } 56 | 57 | public boolean isDirect() { 58 | return false; 59 | } 60 | 61 | @Override 62 | public Mono encodeText(ByteBufAllocator allocator, Object value, Context context) { 63 | return Flux.from(((Clob) value).stream()) 64 | .reduce(new StringBuilder(), (a, b) -> a.append(b)) 65 | .map( 66 | b -> 67 | BufferUtils.encodeEscapedBytes( 68 | allocator, 69 | BufferUtils.STRING_PREFIX, 70 | b.toString().getBytes(StandardCharsets.UTF_8), 71 | context)) 72 | .doOnSubscribe(e -> ((Clob) value).discard()); 73 | } 74 | 75 | @Override 76 | public Mono encodeBinary(ByteBufAllocator allocator, Object value) { 77 | return Flux.from(((Clob) value).stream()) 78 | .reduce(new StringBuilder(), (a, b) -> a.append(b)) 79 | .map(b -> BufferUtils.encodeLengthUtf8(allocator, b.toString())) 80 | .doOnSubscribe(e -> ((Clob) value).discard()); 81 | } 82 | 83 | public DataType getBinaryEncodeType() { 84 | return DataType.VARSTRING; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/codec/list/StreamCodec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.codec.list; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import io.netty.buffer.ByteBufInputStream; 9 | import io.r2dbc.spi.R2dbcNonTransientResourceException; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.EnumSet; 14 | import org.mariadb.r2dbc.ExceptionFactory; 15 | import org.mariadb.r2dbc.codec.Codec; 16 | import org.mariadb.r2dbc.codec.DataType; 17 | import org.mariadb.r2dbc.message.Context; 18 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 19 | import org.mariadb.r2dbc.util.BufferUtils; 20 | 21 | public class StreamCodec implements Codec { 22 | 23 | public static final StreamCodec INSTANCE = new StreamCodec(); 24 | 25 | private static final EnumSet COMPATIBLE_TYPES = 26 | EnumSet.of( 27 | DataType.BLOB, 28 | DataType.TINYBLOB, 29 | DataType.MEDIUMBLOB, 30 | DataType.LONGBLOB, 31 | DataType.TEXT, 32 | DataType.VARSTRING, 33 | DataType.STRING); 34 | 35 | public boolean canDecode(ColumnDefinitionPacket column, Class type) { 36 | return COMPATIBLE_TYPES.contains(column.getDataType()) 37 | && type.isAssignableFrom(InputStream.class); 38 | } 39 | 40 | @Override 41 | public InputStream decodeText( 42 | ByteBuf buf, 43 | int length, 44 | ColumnDefinitionPacket column, 45 | Class type, 46 | ExceptionFactory factory) { 47 | // STRING, VARCHAR, VARSTRING, BLOB, TINYBLOB, MEDIUMBLOB, LONGBLOB: 48 | return new ByteBufInputStream(buf.readRetainedSlice(length), true); 49 | } 50 | 51 | @Override 52 | public InputStream decodeBinary( 53 | ByteBuf buf, 54 | int length, 55 | ColumnDefinitionPacket column, 56 | Class type, 57 | ExceptionFactory factory) { 58 | return new ByteBufInputStream(buf.readRetainedSlice(length), true); 59 | } 60 | 61 | public boolean canEncode(Class value) { 62 | return InputStream.class.isAssignableFrom(value); 63 | } 64 | 65 | @Override 66 | public void encodeDirectText(ByteBuf out, Object value, Context context) { 67 | 68 | try { 69 | out.writeBytes("_binary '".getBytes(StandardCharsets.US_ASCII)); 70 | byte[] array = new byte[4096]; 71 | int len; 72 | while ((len = ((InputStream) value).read(array)) > 0) { 73 | BufferUtils.escapedBytes(out, array, len, context); 74 | } 75 | out.writeByte('\''); 76 | } catch (IOException ioe) { 77 | throw new R2dbcNonTransientResourceException( 78 | "Failed to read InputStream", "H1000", 9000, ioe); 79 | } 80 | } 81 | 82 | @Override 83 | public void encodeDirectBinary( 84 | ByteBufAllocator allocator, ByteBuf out, Object value, Context context) { 85 | ByteBuf val = allocator.buffer(); 86 | try { 87 | byte[] array = new byte[4096]; 88 | int len; 89 | while ((len = ((InputStream) value).read(array)) > 0) { 90 | val.writeBytes(array, 0, len); 91 | } 92 | } catch (IOException ioe) { 93 | throw new R2dbcNonTransientResourceException( 94 | "Failed to read InputStream", "H1000", 9000, ioe); 95 | } 96 | out.writeBytes(BufferUtils.encodeLength(val.readableBytes())); 97 | out.writeBytes(val); 98 | val.release(); 99 | } 100 | 101 | public DataType getBinaryEncodeType() { 102 | return DataType.BLOB; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/codec/list/UuidCodec.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.codec.list; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.EnumSet; 10 | import java.util.UUID; 11 | import org.mariadb.r2dbc.ExceptionFactory; 12 | import org.mariadb.r2dbc.codec.Codec; 13 | import org.mariadb.r2dbc.codec.DataType; 14 | import org.mariadb.r2dbc.message.Context; 15 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 16 | import org.mariadb.r2dbc.util.BufferUtils; 17 | 18 | public class UuidCodec implements Codec { 19 | 20 | public static final UuidCodec INSTANCE = new UuidCodec(); 21 | 22 | private static final EnumSet COMPATIBLE_TYPES = 23 | EnumSet.of(DataType.TEXT, DataType.VARSTRING, DataType.STRING); 24 | 25 | public boolean canDecode(ColumnDefinitionPacket column, Class type) { 26 | return COMPATIBLE_TYPES.contains(column.getDataType()) && type.isAssignableFrom(UUID.class); 27 | } 28 | 29 | public boolean canEncode(Class value) { 30 | return UUID.class.isAssignableFrom(value); 31 | } 32 | 33 | @Override 34 | public UUID decodeText( 35 | ByteBuf buf, 36 | int length, 37 | ColumnDefinitionPacket column, 38 | Class type, 39 | ExceptionFactory factory) { 40 | return UUID.fromString(buf.readCharSequence(length, StandardCharsets.UTF_8).toString()); 41 | } 42 | 43 | @Override 44 | public UUID decodeBinary( 45 | ByteBuf buf, 46 | int length, 47 | ColumnDefinitionPacket column, 48 | Class type, 49 | ExceptionFactory factory) { 50 | return UUID.fromString(buf.readCharSequence(length, StandardCharsets.UTF_8).toString()); 51 | } 52 | 53 | @Override 54 | public void encodeDirectText(ByteBuf out, Object value, Context context) { 55 | out.writeBytes(BufferUtils.STRING_PREFIX); 56 | byte[] b = value.toString().getBytes(StandardCharsets.UTF_8); 57 | BufferUtils.escapedBytes(out, b, b.length, context); 58 | out.writeByte('\''); 59 | } 60 | 61 | @Override 62 | public void encodeDirectBinary( 63 | ByteBufAllocator allocator, ByteBuf out, Object value, Context context) { 64 | byte[] b = value.toString().getBytes(StandardCharsets.UTF_8); 65 | out.writeBytes(BufferUtils.encodeLength(b.length)); 66 | out.writeBytes(b); 67 | } 68 | 69 | public DataType getBinaryEncodeType() { 70 | return DataType.TEXT; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/AuthMoreData.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | 8 | public interface AuthMoreData { 9 | 10 | MessageSequence getSequencer(); 11 | 12 | ByteBuf getBuf(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/AuthSwitch.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message; 5 | 6 | public interface AuthSwitch { 7 | 8 | String getPlugin(); 9 | 10 | byte[] getSeed(); 11 | 12 | MessageSequence getSequencer(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/ClientMessage.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.server.Sequencer; 9 | import reactor.core.publisher.Mono; 10 | 11 | public interface ClientMessage { 12 | default MessageSequence getSequencer() { 13 | return new Sequencer((byte) 0xff); 14 | } 15 | 16 | Mono encode(Context context, ByteBufAllocator byteBufAllocator); 17 | 18 | default void save(ByteBuf buf, int initialReaderIndex) {} 19 | 20 | default void releaseSave() {} 21 | 22 | default void resetSequencer() {} 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/Context.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import io.r2dbc.spi.IsolationLevel; 9 | import org.mariadb.r2dbc.client.ServerVersion; 10 | 11 | public interface Context { 12 | 13 | long getThreadId(); 14 | 15 | long getServerCapabilities(); 16 | 17 | long getClientCapabilities(); 18 | 19 | short getServerStatus(); 20 | 21 | void setServerStatus(short serverStatus); 22 | 23 | IsolationLevel getIsolationLevel(); 24 | 25 | void setIsolationLevel(IsolationLevel isolationLevel); 26 | 27 | void setRedirect(String redirectValue); 28 | 29 | String getRedirectValue(); 30 | 31 | String getDatabase(); 32 | 33 | void setDatabase(String database); 34 | 35 | ServerVersion getVersion(); 36 | 37 | ByteBufAllocator getByteBufAllocator(); 38 | 39 | default void saveRedo(ClientMessage msg, ByteBuf buf, int initialReaderIndex) {} 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/MessageSequence.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message; 5 | 6 | public interface MessageSequence { 7 | byte next(); 8 | 9 | void reset(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/Protocol.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message; 5 | 6 | public enum Protocol { 7 | BINARY, 8 | TEXT 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/ServerMessage.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message; 5 | 6 | public interface ServerMessage { 7 | 8 | default boolean ending() { 9 | return false; 10 | } 11 | 12 | default boolean resultSetEnd() { 13 | return false; 14 | } 15 | 16 | default int refCnt() { 17 | return -1000; 18 | } 19 | 20 | default boolean release() { 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/AuthMoreRawPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import org.mariadb.r2dbc.message.MessageSequence; 11 | import reactor.core.publisher.Mono; 12 | 13 | public final class AuthMoreRawPacket implements ClientMessage { 14 | 15 | private final byte[] raw; 16 | private final MessageSequence sequencer; 17 | 18 | public AuthMoreRawPacket(MessageSequence sequencer, byte[] raw) { 19 | this.sequencer = sequencer; 20 | this.raw = raw; 21 | } 22 | 23 | @Override 24 | public Mono encode(Context context, ByteBufAllocator allocator) { 25 | ByteBuf buf = allocator.ioBuffer(raw.length); 26 | buf.writeBytes(raw); 27 | return Mono.just(buf); 28 | } 29 | 30 | @Override 31 | public MessageSequence getSequencer() { 32 | return sequencer; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/ChangeSchemaPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.nio.charset.StandardCharsets; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.Context; 11 | import reactor.core.publisher.Mono; 12 | 13 | /** 14 | * see COM_INIT_DB https://mariadb.com/kb/en/com_init_db/ COM_INIT_DB is used to specify the default 15 | * schema for the connection. 16 | */ 17 | public final class ChangeSchemaPacket implements ClientMessage { 18 | private final String schema; 19 | 20 | /** 21 | * Constructor 22 | * 23 | * @param schema new default schema 24 | */ 25 | public ChangeSchemaPacket(String schema) { 26 | this.schema = schema; 27 | } 28 | 29 | @Override 30 | public Mono encode(Context context, ByteBufAllocator allocator) { 31 | ByteBuf buf = allocator.ioBuffer(); 32 | buf.writeByte(0x02); 33 | buf.writeCharSequence(this.schema, StandardCharsets.UTF_8); 34 | return Mono.just(buf); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/ClearPasswordPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.nio.charset.StandardCharsets; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.Context; 11 | import org.mariadb.r2dbc.message.MessageSequence; 12 | import reactor.core.publisher.Mono; 13 | 14 | public final class ClearPasswordPacket implements ClientMessage { 15 | 16 | private final CharSequence password; 17 | private final MessageSequence sequencer; 18 | 19 | public ClearPasswordPacket(MessageSequence sequencer, CharSequence password) { 20 | this.sequencer = sequencer; 21 | this.password = password; 22 | } 23 | 24 | @Override 25 | public Mono encode(Context context, ByteBufAllocator allocator) { 26 | if (password == null) return Mono.just(allocator.ioBuffer(0)); 27 | ByteBuf buf = allocator.ioBuffer(password.length() * 4); 28 | buf.writeCharSequence(password, StandardCharsets.UTF_8); 29 | buf.writeByte(0); 30 | return Mono.just(buf); 31 | } 32 | 33 | @Override 34 | public MessageSequence getSequencer() { 35 | return sequencer; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/ClosePreparePacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import reactor.core.publisher.Mono; 11 | 12 | /** 13 | * COM_STMT_CLOSE packet. See 14 | * https://mariadb.com/kb/en/3-binary-protocol-prepared-statements-com_stmt_close/ 15 | */ 16 | public final class ClosePreparePacket implements ClientMessage { 17 | 18 | private final int statementId; 19 | 20 | public ClosePreparePacket(int statementId) { 21 | this.statementId = statementId; 22 | } 23 | 24 | @Override 25 | public Mono encode(Context context, ByteBufAllocator allocator) { 26 | ByteBuf buf = allocator.ioBuffer(); 27 | buf.writeByte(0x19); 28 | buf.writeIntLE(statementId); 29 | return Mono.just(buf); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/Ed25519PasswordPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import io.r2dbc.spi.R2dbcNonTransientResourceException; 9 | import java.nio.charset.StandardCharsets; 10 | import java.security.MessageDigest; 11 | import java.security.NoSuchAlgorithmException; 12 | import java.util.Arrays; 13 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.GroupElement; 14 | import org.mariadb.r2dbc.authentication.standard.ed25519.math.ed25519.ScalarOps; 15 | import org.mariadb.r2dbc.authentication.standard.ed25519.spec.EdDSANamedCurveTable; 16 | import org.mariadb.r2dbc.authentication.standard.ed25519.spec.EdDSAParameterSpec; 17 | import org.mariadb.r2dbc.message.ClientMessage; 18 | import org.mariadb.r2dbc.message.Context; 19 | import org.mariadb.r2dbc.message.MessageSequence; 20 | import reactor.core.publisher.Mono; 21 | 22 | public final class Ed25519PasswordPacket implements ClientMessage { 23 | 24 | private final MessageSequence sequencer; 25 | private final CharSequence password; 26 | private final byte[] seed; 27 | 28 | public Ed25519PasswordPacket(MessageSequence sequencer, CharSequence password, byte[] seed) { 29 | this.sequencer = sequencer; 30 | this.password = password; 31 | this.seed = seed; 32 | } 33 | 34 | private static byte[] ed25519SignWithPassword(final CharSequence password, final byte[] seed) 35 | throws R2dbcNonTransientResourceException { 36 | 37 | try { 38 | byte[] bytePwd = password.toString().getBytes(StandardCharsets.UTF_8); 39 | 40 | MessageDigest hash = MessageDigest.getInstance("SHA-512"); 41 | 42 | int mlen = seed.length; 43 | final byte[] sm = new byte[64 + mlen]; 44 | 45 | byte[] az = hash.digest(bytePwd); 46 | az[0] &= 248; 47 | az[31] &= 63; 48 | az[31] |= 64; 49 | 50 | System.arraycopy(seed, 0, sm, 64, mlen); 51 | System.arraycopy(az, 32, sm, 32, 32); 52 | 53 | byte[] buff = Arrays.copyOfRange(sm, 32, 96); 54 | hash.reset(); 55 | byte[] nonce = hash.digest(buff); 56 | 57 | ScalarOps scalar = new ScalarOps(); 58 | 59 | EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519"); 60 | GroupElement elementAvalue = spec.getB().scalarMultiply(az); 61 | byte[] elementAarray = elementAvalue.toByteArray(); 62 | System.arraycopy(elementAarray, 0, sm, 32, elementAarray.length); 63 | 64 | nonce = scalar.reduce(nonce); 65 | GroupElement elementRvalue = spec.getB().scalarMultiply(nonce); 66 | byte[] elementRarray = elementRvalue.toByteArray(); 67 | System.arraycopy(elementRarray, 0, sm, 0, elementRarray.length); 68 | 69 | hash.reset(); 70 | byte[] hram = hash.digest(sm); 71 | hram = scalar.reduce(hram); 72 | byte[] tt = scalar.multiplyAndAdd(hram, az, nonce); 73 | System.arraycopy(tt, 0, sm, 32, tt.length); 74 | 75 | return Arrays.copyOfRange(sm, 0, 64); 76 | 77 | } catch (NoSuchAlgorithmException e) { 78 | throw new RuntimeException("Could not use SHA-512, failing", e); 79 | } 80 | } 81 | 82 | @Override 83 | public Mono encode(Context context, ByteBufAllocator allocator) { 84 | if (password == null) return Mono.just(allocator.ioBuffer(0)); 85 | ByteBuf buf = allocator.ioBuffer(64); 86 | buf.writeBytes(ed25519SignWithPassword(password, seed)); 87 | return Mono.just(buf); 88 | } 89 | 90 | @Override 91 | public MessageSequence getSequencer() { 92 | return sequencer; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/NativePasswordPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.nio.charset.StandardCharsets; 9 | import java.security.MessageDigest; 10 | import java.security.NoSuchAlgorithmException; 11 | import org.mariadb.r2dbc.message.ClientMessage; 12 | import org.mariadb.r2dbc.message.Context; 13 | import org.mariadb.r2dbc.message.MessageSequence; 14 | import reactor.core.publisher.Mono; 15 | 16 | public final class NativePasswordPacket implements ClientMessage { 17 | 18 | private final MessageSequence sequencer; 19 | private final CharSequence password; 20 | private final byte[] seed; 21 | 22 | public NativePasswordPacket(MessageSequence sequencer, CharSequence password, byte[] seed) { 23 | this.sequencer = sequencer; 24 | this.password = password; 25 | byte[] truncatedSeed = new byte[seed.length - 1]; 26 | System.arraycopy(seed, 0, truncatedSeed, 0, seed.length - 1); 27 | 28 | this.seed = truncatedSeed; 29 | } 30 | 31 | public static byte[] encrypt(CharSequence authenticationData, byte[] seed) { 32 | if (authenticationData == null) { 33 | return new byte[0]; 34 | } 35 | 36 | try { 37 | final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); 38 | byte[] bytePwd = authenticationData.toString().getBytes(StandardCharsets.UTF_8); 39 | 40 | final byte[] stage1 = messageDigest.digest(bytePwd); 41 | messageDigest.reset(); 42 | final byte[] stage2 = messageDigest.digest(stage1); 43 | messageDigest.reset(); 44 | messageDigest.update(seed); 45 | messageDigest.update(stage2); 46 | 47 | final byte[] digest = messageDigest.digest(); 48 | final byte[] returnBytes = new byte[digest.length]; 49 | for (int i = 0; i < digest.length; i++) { 50 | returnBytes[i] = (byte) (stage1[i] ^ digest[i]); 51 | } 52 | return returnBytes; 53 | } catch (NoSuchAlgorithmException e) { 54 | throw new RuntimeException("Could not use SHA-1, failing", e); 55 | } 56 | } 57 | 58 | @Override 59 | public Mono encode(Context context, ByteBufAllocator allocator) { 60 | if (password == null) return Mono.just(allocator.ioBuffer(0)); 61 | ByteBuf buf = allocator.ioBuffer(32); 62 | buf.writeBytes(encrypt(password, seed)); 63 | return Mono.just(buf); 64 | } 65 | 66 | @Override 67 | public MessageSequence getSequencer() { 68 | return sequencer; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/ParsecAuthPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import org.mariadb.r2dbc.message.MessageSequence; 11 | import reactor.core.publisher.Mono; 12 | 13 | public final class ParsecAuthPacket implements ClientMessage { 14 | 15 | private final MessageSequence sequencer; 16 | private final byte[] clientScramble; 17 | private final byte[] signature; 18 | 19 | public ParsecAuthPacket(MessageSequence sequencer, byte[] clientScramble, byte[] signature) { 20 | this.sequencer = sequencer; 21 | this.clientScramble = clientScramble; 22 | this.signature = signature; 23 | } 24 | 25 | @Override 26 | public Mono encode(Context context, ByteBufAllocator allocator) { 27 | ByteBuf buf = allocator.ioBuffer(256); 28 | buf.writeBytes(clientScramble); 29 | buf.writeBytes(signature); 30 | return Mono.just(buf); 31 | } 32 | 33 | @Override 34 | public MessageSequence getSequencer() { 35 | return sequencer; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/PingPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import reactor.core.publisher.Mono; 11 | 12 | public final class PingPacket implements ClientMessage { 13 | 14 | @Override 15 | public Mono encode(Context context, ByteBufAllocator allocator) { 16 | ByteBuf buf = allocator.ioBuffer(); 17 | buf.writeByte(0x0e); 18 | return Mono.just(buf); 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "PingPacket{}"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/PreparePacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.nio.charset.StandardCharsets; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.Context; 11 | import org.mariadb.r2dbc.message.MessageSequence; 12 | import org.mariadb.r2dbc.message.server.Sequencer; 13 | import org.mariadb.r2dbc.util.Assert; 14 | import reactor.core.publisher.Mono; 15 | 16 | public final class PreparePacket implements ClientMessage { 17 | private final String sql; 18 | private final MessageSequence sequencer = new Sequencer((byte) 0xff); 19 | 20 | public PreparePacket(String sql) { 21 | this.sql = Assert.requireNonNull(sql, "query must not be null"); 22 | } 23 | 24 | public String getSql() { 25 | return sql; 26 | } 27 | 28 | public MessageSequence getSequencer() { 29 | return sequencer; 30 | } 31 | 32 | public void resetSequencer() { 33 | sequencer.reset(); 34 | } 35 | 36 | @Override 37 | public Mono encode(Context context, ByteBufAllocator allocator) { 38 | ByteBuf buf = allocator.ioBuffer(this.sql.length() + 1); 39 | buf.writeByte(0x16); 40 | buf.writeCharSequence(this.sql, StandardCharsets.UTF_8); 41 | return Mono.just(buf); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/QueryPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.nio.charset.StandardCharsets; 9 | import org.mariadb.r2dbc.message.ClientMessage; 10 | import org.mariadb.r2dbc.message.Context; 11 | import org.mariadb.r2dbc.message.MessageSequence; 12 | import org.mariadb.r2dbc.message.server.Sequencer; 13 | import org.mariadb.r2dbc.util.Assert; 14 | import reactor.core.publisher.Mono; 15 | 16 | public final class QueryPacket implements ClientMessage { 17 | 18 | private final String sql; 19 | private final MessageSequence sequencer = new Sequencer((byte) 0xff); 20 | 21 | public QueryPacket(String sql) { 22 | this.sql = Assert.requireNonNull(sql, "query must not be null"); 23 | } 24 | 25 | @Override 26 | public Mono encode(Context context, ByteBufAllocator byteBufAllocator) { 27 | ByteBuf out = byteBufAllocator.ioBuffer(this.sql.length() + 1); 28 | out.writeByte(0x03); 29 | out.writeCharSequence(this.sql, StandardCharsets.UTF_8); 30 | return Mono.just(out); 31 | } 32 | 33 | public MessageSequence getSequencer() { 34 | return sequencer; 35 | } 36 | 37 | public void resetSequencer() { 38 | sequencer.reset(); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "QueryPacket{" + "sql='" + sql + '\'' + '}'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/QuitPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import reactor.core.publisher.Mono; 11 | 12 | public final class QuitPacket implements ClientMessage { 13 | public static final QuitPacket INSTANCE = new QuitPacket(); 14 | 15 | @Override 16 | public String toString() { 17 | return "QuitPacket{}"; 18 | } 19 | 20 | @Override 21 | public Mono encode(Context context, ByteBufAllocator allocator) { 22 | ByteBuf buf = allocator.ioBuffer(1); 23 | buf.writeByte(0x01); 24 | return Mono.just(buf); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/RequestExtSaltPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import org.mariadb.r2dbc.message.MessageSequence; 11 | import reactor.core.publisher.Mono; 12 | 13 | public final class RequestExtSaltPacket implements ClientMessage { 14 | 15 | private final MessageSequence sequencer; 16 | 17 | public RequestExtSaltPacket(MessageSequence sequencer) { 18 | this.sequencer = sequencer; 19 | } 20 | 21 | @Override 22 | public Mono encode(Context context, ByteBufAllocator allocator) { 23 | return Mono.just(allocator.ioBuffer(0)); 24 | } 25 | 26 | @Override 27 | public MessageSequence getSequencer() { 28 | return sequencer; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/RsaPublicKeyRequestPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import org.mariadb.r2dbc.message.MessageSequence; 11 | import reactor.core.publisher.Mono; 12 | 13 | public final class RsaPublicKeyRequestPacket implements ClientMessage { 14 | 15 | private final MessageSequence sequencer; 16 | 17 | public RsaPublicKeyRequestPacket(MessageSequence sequencer) { 18 | this.sequencer = sequencer; 19 | } 20 | 21 | @Override 22 | public Mono encode(Context context, ByteBufAllocator allocator) { 23 | ByteBuf buf = allocator.ioBuffer(1); 24 | buf.writeByte(0x01); 25 | return Mono.just(buf); 26 | } 27 | 28 | @Override 29 | public MessageSequence getSequencer() { 30 | return sequencer; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/Sha256PasswordPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import io.r2dbc.spi.R2dbcException; 9 | import io.r2dbc.spi.R2dbcPermissionDeniedException; 10 | import java.nio.charset.StandardCharsets; 11 | import java.security.PublicKey; 12 | import java.util.Arrays; 13 | import javax.crypto.Cipher; 14 | import org.mariadb.r2dbc.message.ClientMessage; 15 | import org.mariadb.r2dbc.message.Context; 16 | import org.mariadb.r2dbc.message.MessageSequence; 17 | import reactor.core.publisher.Mono; 18 | 19 | public final class Sha256PasswordPacket implements ClientMessage { 20 | 21 | private final MessageSequence sequencer; 22 | private final CharSequence password; 23 | private final byte[] seed; 24 | private final PublicKey publicKey; 25 | 26 | public Sha256PasswordPacket( 27 | MessageSequence sequencer, CharSequence password, byte[] seed, PublicKey publicKey) { 28 | this.sequencer = sequencer; 29 | this.password = password; 30 | byte[] truncatedSeed = new byte[seed.length - 1]; 31 | System.arraycopy(seed, 0, truncatedSeed, 0, seed.length - 1); 32 | this.seed = truncatedSeed; 33 | this.publicKey = publicKey; 34 | } 35 | 36 | /** 37 | * Encode password with seed and public key. 38 | * 39 | * @param publicKey public key 40 | * @param password password 41 | * @param seed seed 42 | * @return encoded password 43 | * @throws R2dbcException if cannot encode password 44 | */ 45 | public static byte[] encrypt(PublicKey publicKey, CharSequence password, byte[] seed) 46 | throws R2dbcException { 47 | 48 | byte[] bytePwd = password.toString().getBytes(StandardCharsets.UTF_8); 49 | 50 | byte[] nullFinishedPwd = Arrays.copyOf(bytePwd, bytePwd.length + 1); 51 | byte[] xorBytes = new byte[nullFinishedPwd.length]; 52 | int seedLength = seed.length; 53 | 54 | for (int i = 0; i < xorBytes.length; i++) { 55 | xorBytes[i] = (byte) (nullFinishedPwd[i] ^ seed[i % seedLength]); 56 | } 57 | 58 | try { 59 | Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); 60 | cipher.init(Cipher.ENCRYPT_MODE, publicKey); 61 | return cipher.doFinal(xorBytes); 62 | } catch (Exception ex) { 63 | throw new R2dbcPermissionDeniedException( 64 | "Could not connect using SHA256 plugin : " + ex.getMessage(), "S1009", ex); 65 | } 66 | } 67 | 68 | @Override 69 | public Mono encode(Context context, ByteBufAllocator allocator) { 70 | if (password == null) return Mono.just(allocator.ioBuffer(0)); 71 | ByteBuf buf = allocator.ioBuffer(256); 72 | buf.writeBytes(encrypt(publicKey, password, seed)); 73 | return Mono.just(buf); 74 | } 75 | 76 | @Override 77 | public MessageSequence getSequencer() { 78 | return sequencer; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/Sha2PublicKeyRequestPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import org.mariadb.r2dbc.message.MessageSequence; 11 | import reactor.core.publisher.Mono; 12 | 13 | public final class Sha2PublicKeyRequestPacket implements ClientMessage { 14 | 15 | private final MessageSequence sequencer; 16 | 17 | public Sha2PublicKeyRequestPacket(MessageSequence sequencer) { 18 | this.sequencer = sequencer; 19 | } 20 | 21 | @Override 22 | public Mono encode(Context context, ByteBufAllocator allocator) { 23 | ByteBuf buf = allocator.ioBuffer(1); 24 | buf.writeByte(0x02); 25 | return Mono.just(buf); 26 | } 27 | 28 | @Override 29 | public MessageSequence getSequencer() { 30 | return sequencer; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/client/SslRequestPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.client; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import org.mariadb.r2dbc.message.ClientMessage; 9 | import org.mariadb.r2dbc.message.Context; 10 | import org.mariadb.r2dbc.message.MessageSequence; 11 | import org.mariadb.r2dbc.message.server.InitialHandshakePacket; 12 | import reactor.core.publisher.Mono; 13 | 14 | public final class SslRequestPacket implements ClientMessage { 15 | 16 | private final InitialHandshakePacket initialHandshakePacket; 17 | private final long clientCapabilities; 18 | 19 | public SslRequestPacket(InitialHandshakePacket initialHandshakePacket, long clientCapabilities) { 20 | this.initialHandshakePacket = initialHandshakePacket; 21 | this.clientCapabilities = clientCapabilities; 22 | } 23 | 24 | @Override 25 | public Mono encode(Context context, ByteBufAllocator allocator) { 26 | 27 | byte exchangeCharset = 28 | HandshakeResponse.decideLanguage( 29 | initialHandshakePacket.getDefaultCollation(), 30 | initialHandshakePacket.getMajorServerVersion(), 31 | initialHandshakePacket.getMinorServerVersion()); 32 | 33 | ByteBuf buf = allocator.buffer(32, 32); 34 | 35 | buf.writeIntLE((int) clientCapabilities); 36 | buf.writeIntLE(1024 * 1024 * 1024); 37 | buf.writeByte(exchangeCharset); // 1 byte 38 | 39 | buf.writeZero(19); // 19 bytes 40 | buf.writeIntLE((int) (clientCapabilities >> 32)); // Maria extended flag 41 | return Mono.just(buf); 42 | } 43 | 44 | @Override 45 | public MessageSequence getSequencer() { 46 | return initialHandshakePacket.getSequencer(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/AuthMoreDataPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.util.ReferenceCounted; 8 | import org.mariadb.r2dbc.message.AuthMoreData; 9 | import org.mariadb.r2dbc.message.MessageSequence; 10 | import org.mariadb.r2dbc.message.ServerMessage; 11 | 12 | public class AuthMoreDataPacket implements AuthMoreData, ServerMessage, ReferenceCounted { 13 | 14 | private final MessageSequence sequencer; 15 | private final ByteBuf buf; 16 | 17 | private AuthMoreDataPacket(MessageSequence sequencer, ByteBuf buf) { 18 | this.sequencer = sequencer; 19 | this.buf = buf; 20 | } 21 | 22 | public static AuthMoreDataPacket decode(MessageSequence sequencer, ByteBuf buf) { 23 | ByteBuf data = buf.readRetainedSlice(buf.readableBytes()); 24 | return new AuthMoreDataPacket(sequencer, data); 25 | } 26 | 27 | @Override 28 | public int refCnt() { 29 | return buf.refCnt(); 30 | } 31 | 32 | @Override 33 | public ReferenceCounted retain() { 34 | buf.retain(); 35 | return this; 36 | } 37 | 38 | @Override 39 | public ReferenceCounted retain(int increment) { 40 | buf.retain(increment); 41 | return this; 42 | } 43 | 44 | @Override 45 | public ReferenceCounted touch() { 46 | buf.touch(); 47 | return this; 48 | } 49 | 50 | @Override 51 | public ReferenceCounted touch(Object hint) { 52 | buf.touch(hint); 53 | return this; 54 | } 55 | 56 | public boolean release() { 57 | return buf.release(); 58 | } 59 | 60 | @Override 61 | public boolean release(int decrement) { 62 | return buf.release(); 63 | } 64 | 65 | public MessageSequence getSequencer() { 66 | return sequencer; 67 | } 68 | 69 | public ByteBuf getBuf() { 70 | return buf; 71 | } 72 | 73 | @Override 74 | public boolean ending() { 75 | return true; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/AuthSwitchPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import java.nio.charset.StandardCharsets; 8 | import org.mariadb.r2dbc.message.AuthSwitch; 9 | import org.mariadb.r2dbc.message.ServerMessage; 10 | 11 | public class AuthSwitchPacket implements AuthSwitch, ServerMessage { 12 | 13 | private final Sequencer sequencer; 14 | private final String plugin; 15 | private final byte[] seed; 16 | 17 | public AuthSwitchPacket(Sequencer sequencer, String plugin, byte[] seed) { 18 | this.sequencer = sequencer; 19 | this.plugin = plugin; 20 | this.seed = seed; 21 | } 22 | 23 | public static AuthSwitchPacket decode(Sequencer sequencer, ByteBuf buf) { 24 | buf.skipBytes(1); 25 | int nullLength = buf.bytesBefore((byte) 0x00); 26 | String plugin = buf.toString(buf.readerIndex(), nullLength, StandardCharsets.US_ASCII); 27 | buf.skipBytes(nullLength + 1); 28 | 29 | byte[] seed = new byte[buf.readableBytes()]; 30 | buf.getBytes(buf.readerIndex(), seed); 31 | return new AuthSwitchPacket(sequencer, plugin, seed); 32 | } 33 | 34 | public String getPlugin() { 35 | return plugin; 36 | } 37 | 38 | public byte[] getSeed() { 39 | return seed; 40 | } 41 | 42 | public Sequencer getSequencer() { 43 | return sequencer; 44 | } 45 | 46 | @Override 47 | public boolean ending() { 48 | return true; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/ColumnCountPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import org.mariadb.r2dbc.message.Context; 8 | import org.mariadb.r2dbc.message.ServerMessage; 9 | import org.mariadb.r2dbc.util.BufferUtils; 10 | import org.mariadb.r2dbc.util.constants.Capabilities; 11 | 12 | public class ColumnCountPacket implements ServerMessage { 13 | 14 | private final int columnCount; 15 | private final boolean metaFollows; 16 | 17 | public ColumnCountPacket(int columnCount, boolean metaFollows) { 18 | this.columnCount = columnCount; 19 | this.metaFollows = metaFollows; 20 | } 21 | 22 | public static ColumnCountPacket decode(ByteBuf buf, Context context) { 23 | long columnCount = BufferUtils.readLengthEncodedInt(buf); 24 | boolean metaFollow = true; 25 | if ((context.getServerCapabilities() & Capabilities.MARIADB_CLIENT_CACHE_METADATA) > 0) { 26 | metaFollow = buf.readByte() == 1; 27 | } 28 | return new ColumnCountPacket((int) columnCount, metaFollow); 29 | } 30 | 31 | public int getColumnCount() { 32 | return columnCount; 33 | } 34 | 35 | public boolean isMetaFollows() { 36 | return metaFollows; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/CompletePrepareResult.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import org.mariadb.r2dbc.message.ServerMessage; 7 | import org.mariadb.r2dbc.util.ServerPrepareResult; 8 | 9 | public final class CompletePrepareResult implements ServerMessage { 10 | 11 | private final ServerPrepareResult prepare; 12 | private final boolean continueOnEnd; 13 | 14 | public CompletePrepareResult(final ServerPrepareResult prepare, boolean continueOnEnd) { 15 | this.prepare = prepare; 16 | this.continueOnEnd = continueOnEnd; 17 | } 18 | 19 | @Override 20 | public boolean ending() { 21 | return !continueOnEnd; 22 | } 23 | 24 | public ServerPrepareResult getPrepare() { 25 | return prepare; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/EofPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import org.mariadb.r2dbc.message.Context; 8 | import org.mariadb.r2dbc.message.ServerMessage; 9 | import org.mariadb.r2dbc.util.constants.ServerStatus; 10 | 11 | public class EofPacket implements ServerMessage { 12 | 13 | private final short serverStatus; 14 | private final short warningCount; 15 | private final boolean ending; 16 | private final boolean resultSetEnd; 17 | 18 | public EofPacket( 19 | final short serverStatus, 20 | final short warningCount, 21 | final boolean resultSetEnd, 22 | final boolean ending) { 23 | this.serverStatus = serverStatus; 24 | this.warningCount = warningCount; 25 | this.resultSetEnd = resultSetEnd; 26 | this.ending = ending; 27 | } 28 | 29 | public static EofPacket decode(ByteBuf buf, Context context, boolean resultSetEnd) { 30 | buf.skipBytes(1); 31 | short warningCount = buf.readShortLE(); 32 | short serverStatus = buf.readShortLE(); 33 | context.setServerStatus(serverStatus); 34 | return new EofPacket( 35 | serverStatus, 36 | warningCount, 37 | resultSetEnd, 38 | resultSetEnd && (serverStatus & ServerStatus.MORE_RESULTS_EXISTS) == 0); 39 | } 40 | 41 | /** 42 | * This is for mysql that doesn't send MORE_RESULTS_EXISTS flag, but sending an OK_Packet after, 43 | * breaking protocol. 44 | * 45 | * @param buf current EOF buf 46 | * @param context current context 47 | * @return Eof packet 48 | */ 49 | public static EofPacket decodeOutputParam(ByteBuf buf, Context context) { 50 | buf.skipBytes(1); 51 | short warningCount = buf.readShortLE(); 52 | short serverStatus = 53 | (short) 54 | (buf.readShortLE() | ServerStatus.PS_OUT_PARAMETERS | ServerStatus.MORE_RESULTS_EXISTS); 55 | context.setServerStatus(serverStatus); 56 | return new EofPacket(serverStatus, warningCount, false, false); 57 | } 58 | 59 | public short getServerStatus() { 60 | return serverStatus; 61 | } 62 | 63 | public short getWarningCount() { 64 | return warningCount; 65 | } 66 | 67 | @Override 68 | public boolean ending() { 69 | return this.ending; 70 | } 71 | 72 | @Override 73 | public boolean resultSetEnd() { 74 | return resultSetEnd; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "EofPacket{" 80 | + "serverStatus=" 81 | + serverStatus 82 | + ", warningCount=" 83 | + warningCount 84 | + ", ending=" 85 | + ending 86 | + ", resultSetEnd=" 87 | + resultSetEnd 88 | + '}'; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/ErrorPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.r2dbc.spi.R2dbcException; 8 | import io.r2dbc.spi.Result; 9 | import java.nio.charset.StandardCharsets; 10 | import org.mariadb.r2dbc.ExceptionFactory; 11 | import org.mariadb.r2dbc.message.ServerMessage; 12 | import org.mariadb.r2dbc.util.Assert; 13 | import reactor.util.Logger; 14 | import reactor.util.Loggers; 15 | 16 | public final class ErrorPacket implements ServerMessage, Result.Message { 17 | private static final Logger logger = Loggers.getLogger(ErrorPacket.class); 18 | private final short errorCode; 19 | private final String message; 20 | private final String sqlState; 21 | private final boolean ending; 22 | 23 | private ErrorPacket(short errorCode, String sqlState, String message, boolean ending) { 24 | this.errorCode = errorCode; 25 | this.message = message; 26 | this.sqlState = sqlState; 27 | this.ending = ending; 28 | } 29 | 30 | public static ErrorPacket decode(Sequencer sequencer, ByteBuf buf, boolean ending) { 31 | Assert.requireNonNull(buf, "buffer must not be null"); 32 | buf.skipBytes(1); 33 | short errorCode = buf.readShortLE(); 34 | byte next = buf.getByte(buf.readerIndex()); 35 | String sqlState; 36 | String msg; 37 | if (next == (byte) '#') { 38 | buf.skipBytes(1); // skip '#' 39 | sqlState = buf.readCharSequence(5, StandardCharsets.UTF_8).toString(); 40 | msg = buf.readCharSequence(buf.readableBytes(), StandardCharsets.UTF_8).toString(); 41 | } else { 42 | // Pre-4.1 message, still can be output in newer versions (e.g with 'Too many connections') 43 | msg = buf.readCharSequence(buf.readableBytes(), StandardCharsets.UTF_8).toString(); 44 | sqlState = "HY000"; 45 | } 46 | ErrorPacket err = new ErrorPacket(errorCode, sqlState, msg, ending); 47 | logger.warn("Error: '{}' sqlState='{}' code={} ", msg, sqlState, errorCode); 48 | return err; 49 | } 50 | 51 | public String getMessage() { 52 | return message; 53 | } 54 | 55 | @Override 56 | public R2dbcException exception() { 57 | return ExceptionFactory.createException(this, null); 58 | } 59 | 60 | @Override 61 | public int errorCode() { 62 | return errorCode; 63 | } 64 | 65 | @Override 66 | public String sqlState() { 67 | return sqlState; 68 | } 69 | 70 | @Override 71 | public String message() { 72 | return message; 73 | } 74 | 75 | @Override 76 | public boolean ending() { 77 | return ending; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/PrepareResultPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import org.mariadb.r2dbc.message.Context; 8 | import org.mariadb.r2dbc.message.ServerMessage; 9 | 10 | public final class PrepareResultPacket implements ServerMessage { 11 | 12 | private final int statementId; 13 | private final int numColumns; 14 | private final int numParams; 15 | private final Sequencer sequencer; 16 | private final boolean continueOnEnd; 17 | 18 | private PrepareResultPacket( 19 | final Sequencer sequencer, 20 | final int statementId, 21 | final int numColumns, 22 | final int numParams, 23 | boolean continueOnEnd) { 24 | this.sequencer = sequencer; 25 | this.statementId = statementId; 26 | this.numColumns = numColumns; 27 | this.numParams = numParams; 28 | this.continueOnEnd = continueOnEnd; 29 | } 30 | 31 | public static PrepareResultPacket decode( 32 | Sequencer sequencer, ByteBuf buffer, Context context, boolean continueOnEnd) { 33 | /* Prepared Statement OK */ 34 | buffer.readByte(); /* skip field count */ 35 | final int statementId = buffer.readIntLE(); 36 | final int numColumns = buffer.readUnsignedShortLE(); 37 | final int numParams = buffer.readUnsignedShortLE(); 38 | return new PrepareResultPacket(sequencer, statementId, numColumns, numParams, continueOnEnd); 39 | } 40 | 41 | @Override 42 | public boolean ending() { 43 | return false; 44 | } 45 | 46 | public boolean isContinueOnEnd() { 47 | return continueOnEnd; 48 | } 49 | 50 | public int getStatementId() { 51 | return statementId; 52 | } 53 | 54 | public int getNumColumns() { 55 | return numColumns; 56 | } 57 | 58 | public int getNumParams() { 59 | return numParams; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/RowPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.util.ReferenceCounted; 8 | import org.mariadb.r2dbc.message.ServerMessage; 9 | 10 | public final class RowPacket implements ServerMessage, ReferenceCounted { 11 | 12 | private final ByteBuf raw; 13 | 14 | public RowPacket(ByteBuf raw) { 15 | this.raw = raw.retain(); 16 | } 17 | 18 | public ByteBuf getRaw() { 19 | return raw; 20 | } 21 | 22 | @Override 23 | public int refCnt() { 24 | return raw.refCnt(); 25 | } 26 | 27 | @Override 28 | public ReferenceCounted retain() { 29 | raw.retain(); 30 | return this; 31 | } 32 | 33 | @Override 34 | public ReferenceCounted retain(int increment) { 35 | raw.retain(increment); 36 | return this; 37 | } 38 | 39 | @Override 40 | public ReferenceCounted touch() { 41 | raw.touch(); 42 | return this; 43 | } 44 | 45 | @Override 46 | public ReferenceCounted touch(Object hint) { 47 | raw.touch(hint); 48 | return this; 49 | } 50 | 51 | public boolean release() { 52 | return raw.release(); 53 | } 54 | 55 | @Override 56 | public boolean release(int decrement) { 57 | return raw.release(decrement); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/Sequencer.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import org.mariadb.r2dbc.message.MessageSequence; 7 | 8 | public class Sequencer implements MessageSequence { 9 | private byte sequenceId; 10 | 11 | public Sequencer(byte sequenceId) { 12 | this.sequenceId = sequenceId; 13 | } 14 | 15 | public void reset() { 16 | sequenceId = (byte) 0xff; 17 | } 18 | 19 | public byte next() { 20 | return ++sequenceId; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/message/server/SkipPacket.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.message.server; 5 | 6 | import org.mariadb.r2dbc.message.ServerMessage; 7 | 8 | public class SkipPacket implements ServerMessage { 9 | 10 | private final boolean ending; 11 | 12 | public SkipPacket(boolean ending) { 13 | this.ending = ending; 14 | } 15 | 16 | public static SkipPacket decode(boolean ending) { 17 | return new SkipPacket(ending); 18 | } 19 | 20 | @Override 21 | public boolean ending() { 22 | return this.ending; 23 | } 24 | 25 | public boolean resultSetEnd() { 26 | return this.ending; 27 | } 28 | 29 | public Sequencer getSequencer() { 30 | return null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/Assert.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import reactor.util.annotation.Nullable; 7 | 8 | public final class Assert { 9 | 10 | private Assert() {} 11 | 12 | public static T requireNonNull(@Nullable T t, String message) { 13 | if (t == null) { 14 | throw new IllegalArgumentException(message); 15 | } 16 | return t; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/BindValue.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.buffer.ByteBufAllocator; 8 | import java.util.Objects; 9 | import org.mariadb.r2dbc.codec.Codec; 10 | import org.mariadb.r2dbc.message.Context; 11 | import reactor.core.publisher.Mono; 12 | 13 | public class BindValue { 14 | 15 | private final Codec codec; 16 | private final Object value; 17 | 18 | public BindValue(Codec codec, Object value) { 19 | this.codec = codec; 20 | this.value = value; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) { 26 | return true; 27 | } 28 | if (o == null || getClass() != o.getClass()) { 29 | return false; 30 | } 31 | BindValue that = (BindValue) o; 32 | return Objects.equals(this.codec, that.codec) && Objects.equals(this.value, that.value); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(this.codec, this.value); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "BindValue{codec=" + this.codec.getClass().getSimpleName() + '}'; 43 | } 44 | 45 | public Codec getCodec() { 46 | return this.codec; 47 | } 48 | 49 | public boolean isNull() { 50 | return this.value == null; 51 | } 52 | 53 | public void encodeDirectText(ByteBuf out, Context context) { 54 | this.codec.encodeDirectText(out, this.value, context); 55 | } 56 | 57 | public void encodeDirectBinary(ByteBufAllocator allocator, ByteBuf out, Context context) { 58 | this.codec.encodeDirectBinary(allocator, out, this.value, context); 59 | } 60 | 61 | public Mono encodeText(ByteBufAllocator allocator, Context context) { 62 | return this.codec.encodeText(allocator, this.value, context); 63 | } 64 | 65 | public Mono encodeBinary(ByteBufAllocator allocator) { 66 | return this.codec.encodeBinary(allocator, this.value); 67 | } 68 | 69 | public Object getValue() { 70 | return this.value; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/Binding.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import java.util.Arrays; 7 | import java.util.Objects; 8 | import org.mariadb.r2dbc.MariadbCommonStatement; 9 | 10 | public final class Binding { 11 | private final int expectedSize; 12 | private BindValue[] binds; 13 | private int currentSize = 0; 14 | 15 | public Binding(int expectedSize) { 16 | this.expectedSize = expectedSize; 17 | this.binds = 18 | new BindValue[(expectedSize == MariadbCommonStatement.UNKNOWN_SIZE) ? 10 : expectedSize]; 19 | } 20 | 21 | public Binding add(int index, BindValue parameter) { 22 | if (index >= this.expectedSize) { 23 | if (expectedSize != MariadbCommonStatement.UNKNOWN_SIZE) { 24 | throw new IndexOutOfBoundsException( 25 | String.format( 26 | "Binding index %d when only %d parameters are expected", index, this.expectedSize)); 27 | } 28 | grow(index + 1); 29 | } 30 | if (index >= currentSize) currentSize = index + 1; 31 | this.binds[index] = parameter; 32 | return this; 33 | } 34 | 35 | private void grow(int minLength) { 36 | int currLength = this.binds.length; 37 | int newLength = Math.max(currLength + (currLength >> 1), minLength); 38 | this.binds = Arrays.copyOf(this.binds, newLength); 39 | } 40 | 41 | public void clear() { 42 | this.binds = new BindValue[expectedSize]; 43 | this.currentSize = 0; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) { 48 | if (this == o) { 49 | return true; 50 | } 51 | if (o == null || getClass() != o.getClass()) { 52 | return false; 53 | } 54 | Binding that = (Binding) o; 55 | return Objects.equals(this.binds, that.binds); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | int result = Objects.hash(expectedSize); 61 | result = 31 * result + Arrays.hashCode(binds); 62 | return result; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return Arrays.toString(this.binds); 68 | } 69 | 70 | public void validate(int expectedSize) { 71 | // valid parameters 72 | for (int i = 0; i < expectedSize; i++) { 73 | if (binds[i] == null) { 74 | throw new IllegalStateException(String.format("Parameter at position %d is not set", i)); 75 | } 76 | } 77 | } 78 | 79 | public BindValue[] getBindResultParameters(int paramNumber) { 80 | if (paramNumber == 0) { 81 | return new BindValue[0]; 82 | } 83 | 84 | if (paramNumber < this.binds.length) { 85 | throw new IllegalStateException( 86 | String.format("No parameter specified for index %d", this.binds.length)); 87 | } 88 | 89 | for (int i = 0; i < paramNumber; i++) { 90 | if (this.binds[i] == null) { 91 | throw new IllegalStateException(String.format("No parameter specified for index %d", i)); 92 | } 93 | } 94 | if (paramNumber == expectedSize) return binds; 95 | if (paramNumber < expectedSize) { 96 | return Arrays.copyOfRange(binds, 0, paramNumber); 97 | } 98 | throw new IllegalStateException( 99 | String.format("No parameter specified for index %d", expectedSize)); 100 | } 101 | 102 | public BindValue[] getBinds() { 103 | 104 | for (int i = 0; i < currentSize; i++) { 105 | if (this.binds[i] == null) { 106 | throw new IllegalStateException(String.format("No parameter specified for index %d", i)); 107 | } 108 | } 109 | return Arrays.copyOfRange(binds, 0, currentSize); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/HostAddress.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Objects; 10 | 11 | public class HostAddress { 12 | final String host; 13 | final int port; 14 | 15 | public HostAddress(String host, int port) { 16 | this.host = host; 17 | this.port = port; 18 | } 19 | 20 | public static List parse(String hosts, int defaultPort) { 21 | // parse host for multiple hosts. 22 | if (hosts != null) { 23 | List hostAddresses = new ArrayList<>(); 24 | String[] tmpHosts = hosts.split(","); 25 | for (String tmpHost : tmpHosts) { 26 | if (tmpHost.contains(":")) { 27 | hostAddresses.add( 28 | new HostAddress( 29 | tmpHost.substring(0, tmpHost.indexOf(":")), 30 | Integer.parseInt(tmpHost.substring(tmpHost.indexOf(":") + 1)))); 31 | } else { 32 | hostAddresses.add(new HostAddress(tmpHost, defaultPort)); 33 | } 34 | } 35 | return hostAddresses; 36 | } else { 37 | return Collections.singletonList(new HostAddress("localhost", defaultPort)); 38 | } 39 | } 40 | 41 | public String getHost() { 42 | return host; 43 | } 44 | 45 | public int getPort() { 46 | return port; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | if (this == o) return true; 52 | if (!(o instanceof HostAddress)) return false; 53 | HostAddress that = (HostAddress) o; 54 | return port == that.port && host.equals(that.host); 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | return Objects.hash(host, port); 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return host + ':' + port; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/MariadbType.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import io.r2dbc.spi.Blob; 7 | import io.r2dbc.spi.R2dbcType; 8 | import io.r2dbc.spi.Type; 9 | import java.math.BigDecimal; 10 | import java.math.BigInteger; 11 | import java.nio.ByteBuffer; 12 | import java.time.LocalDate; 13 | import java.time.LocalDateTime; 14 | import java.time.LocalTime; 15 | import java.util.BitSet; 16 | import org.mariadb.r2dbc.codec.Codec; 17 | import org.mariadb.r2dbc.codec.list.*; 18 | 19 | public enum MariadbType implements Type { 20 | TINYINT(R2dbcType.TINYINT.name(), Byte.class, ByteCodec.INSTANCE), 21 | UNSIGNED_TINYINT(R2dbcType.TINYINT.name(), Short.class, ShortCodec.INSTANCE), 22 | SMALLINT(R2dbcType.SMALLINT.name(), Short.class, ShortCodec.INSTANCE), 23 | UNSIGNED_SMALLINT(R2dbcType.SMALLINT.name(), Integer.class, IntCodec.INSTANCE), 24 | INTEGER(R2dbcType.INTEGER.name(), Integer.class, IntCodec.INSTANCE), 25 | UNSIGNED_INTEGER(R2dbcType.INTEGER.name(), Long.class, LongCodec.INSTANCE), 26 | FLOAT(R2dbcType.FLOAT.name(), Float.class, FloatCodec.INSTANCE), 27 | DOUBLE(R2dbcType.DOUBLE.name(), Double.class, DoubleCodec.INSTANCE), 28 | BIGINT(R2dbcType.BIGINT.name(), Long.class, LongCodec.INSTANCE), 29 | UNSIGNED_BIGINT(R2dbcType.BIGINT.name(), BigInteger.class, BigIntegerCodec.INSTANCE), 30 | TIME(R2dbcType.TIME.name(), LocalTime.class, LocalTimeCodec.INSTANCE), 31 | TIMESTAMP(R2dbcType.TIMESTAMP.name(), LocalDateTime.class, LocalDateTimeCodec.INSTANCE), 32 | DATE(R2dbcType.DATE.name(), LocalDate.class, LocalDateCodec.INSTANCE), 33 | BIT("BIT", BitSet.class, BitSetCodec.INSTANCE), 34 | BOOLEAN(R2dbcType.BOOLEAN.getName(), Boolean.class, BooleanCodec.INSTANCE), 35 | BYTES("BYTES", byte[].class, ByteArrayCodec.INSTANCE), 36 | BLOB(R2dbcType.BLOB.getName(), ByteBuffer.class, ByteBufferCodec.INSTANCE), 37 | VARCHAR(R2dbcType.VARCHAR.getName(), String.class, StringCodec.INSTANCE), 38 | CLOB(R2dbcType.CLOB.getName(), String.class, StringCodec.INSTANCE), 39 | BINARY(R2dbcType.BINARY.getName(), Blob.class, BlobCodec.INSTANCE), 40 | DECIMAL(R2dbcType.DECIMAL.getName(), BigDecimal.class, BigDecimalCodec.INSTANCE); 41 | 42 | private final String typeName; 43 | private final Class classType; 44 | private final Codec defaultCodec; 45 | 46 | MariadbType(String typeName, Class classType, Codec defaultCodec) { 47 | this.typeName = typeName; 48 | this.classType = classType; 49 | this.defaultCodec = defaultCodec; 50 | } 51 | 52 | @Override 53 | public Class getJavaType() { 54 | return classType; 55 | } 56 | 57 | @Override 58 | public String getName() { 59 | return typeName; 60 | } 61 | 62 | public Codec getDefaultCodec() { 63 | return defaultCodec; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/PrepareCache.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import java.util.LinkedHashMap; 7 | import java.util.Map; 8 | import org.mariadb.r2dbc.client.Client; 9 | 10 | public class PrepareCache extends LinkedHashMap { 11 | 12 | private static final long serialVersionUID = -8922905563713952695L; 13 | private final int maxSize; 14 | private final Client client; 15 | 16 | public PrepareCache(int size, Client client) { 17 | super(size, .75f, true); 18 | this.maxSize = size; 19 | this.client = client; 20 | } 21 | 22 | @Override 23 | public boolean removeEldestEntry(Map.Entry eldest) { 24 | if (this.size() > maxSize) { 25 | eldest.getValue().unCache(client); 26 | return true; 27 | } 28 | return false; 29 | } 30 | 31 | public synchronized ServerPrepareResult put(String key, ServerPrepareResult result) { 32 | ServerPrepareResult cached = super.get(key); 33 | 34 | // if there is already some cached data, return existing cached data 35 | if (cached != null) { 36 | cached.incrementUse(); 37 | result.unCache(client); 38 | return cached; 39 | } 40 | 41 | if (result.cache()) { 42 | super.put(key, result); 43 | } 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/PrepareResult.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | public interface PrepareResult { 7 | 8 | int getParamCount(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/Security.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class Security { 10 | 11 | /** 12 | * Parse the option "sessionVariable" to ensure having no injection. semi-column not in string 13 | * will be replaced by comma. 14 | * 15 | * @param sessionVariable option value 16 | * @return parsed String 17 | */ 18 | public static Map parseSessionVariables(String sessionVariable) { 19 | Map out = new HashMap<>(); 20 | StringBuilder sb = new StringBuilder(); 21 | Parse state = Parse.Normal; 22 | boolean iskey = true; 23 | boolean singleQuotes = true; 24 | String key = null; 25 | boolean isString = false; 26 | 27 | char[] chars = sessionVariable.toCharArray(); 28 | 29 | for (char car : chars) { 30 | 31 | if (state == Parse.Escape) { 32 | sb.append(car); 33 | state = Parse.String; 34 | continue; 35 | } 36 | 37 | switch (car) { 38 | case '"': 39 | if (state == Parse.Normal) { 40 | state = Parse.String; 41 | singleQuotes = false; 42 | isString = true; 43 | } else if (!singleQuotes) { 44 | state = Parse.Normal; 45 | } 46 | break; 47 | 48 | case '\'': 49 | if (state == Parse.Normal) { 50 | state = Parse.String; 51 | isString = true; 52 | singleQuotes = true; 53 | } else if (singleQuotes) { 54 | state = Parse.Normal; 55 | } 56 | break; 57 | 58 | case '\\': 59 | if (state == Parse.String) { 60 | state = Parse.Escape; 61 | } 62 | break; 63 | 64 | case ';': 65 | case ',': 66 | if (state == Parse.Normal) { 67 | if (!iskey) { 68 | String valStr = sb.substring(1); 69 | out.put(key, parseObject(valStr, isString)); 70 | isString = false; 71 | } else { 72 | key = sb.toString().trim(); 73 | if (!key.isEmpty()) { 74 | out.put(key, null); 75 | } 76 | } 77 | iskey = true; 78 | key = null; 79 | sb = new StringBuilder(); 80 | continue; 81 | } 82 | break; 83 | 84 | case '=': 85 | if (state == Parse.Normal && iskey) { 86 | key = sb.toString().trim(); 87 | iskey = false; 88 | isString = false; 89 | sb = new StringBuilder(); 90 | } 91 | break; 92 | 93 | default: 94 | // nothing 95 | } 96 | 97 | sb.append(car); 98 | } 99 | 100 | if (!iskey) { 101 | String valStr = sb.substring(1); 102 | out.put(key, parseObject(valStr, isString)); 103 | } else { 104 | String tmpkey = sb.toString().trim(); 105 | out.put(tmpkey, null); 106 | } 107 | return out; 108 | } 109 | 110 | private static Object parseObject(String valStr, boolean isString) { 111 | if (!isString) { 112 | if (valStr.indexOf(".") > 0) { 113 | try { 114 | return Double.parseDouble(valStr); 115 | } catch (Exception e) { 116 | } 117 | } else { 118 | try { 119 | return Integer.parseInt(valStr); 120 | } catch (Exception e) { 121 | } 122 | } 123 | } 124 | return valStr; 125 | } 126 | 127 | private enum Parse { 128 | Normal, 129 | String, /* inside string */ 130 | Escape /* found backslash */ 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/ServerPrepareResult.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import java.util.Objects; 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | import org.mariadb.r2dbc.client.Client; 10 | import org.mariadb.r2dbc.message.client.ClosePreparePacket; 11 | import org.mariadb.r2dbc.message.server.ColumnDefinitionPacket; 12 | 13 | public class ServerPrepareResult { 14 | 15 | private final int statementId; 16 | private final int numParams; 17 | private final AtomicBoolean closing = new AtomicBoolean(); 18 | private final AtomicInteger use = new AtomicInteger(1); 19 | private final AtomicBoolean cached = new AtomicBoolean(false); 20 | private ColumnDefinitionPacket[] columns; 21 | 22 | public ServerPrepareResult(int statementId, int numParams, ColumnDefinitionPacket[] columns) { 23 | this.statementId = statementId; 24 | this.numParams = numParams; 25 | this.columns = columns; 26 | } 27 | 28 | public int getStatementId() { 29 | return statementId; 30 | } 31 | 32 | public int getNumParams() { 33 | return numParams; 34 | } 35 | 36 | public ColumnDefinitionPacket[] getColumns() { 37 | return columns; 38 | } 39 | 40 | public void setColumns(ColumnDefinitionPacket[] columns) { 41 | this.columns = columns; 42 | } 43 | 44 | public void close(Client client) { 45 | if (!cached.get() && closing.compareAndSet(false, true)) { 46 | client.sendCommandWithoutResult(new ClosePreparePacket(this.statementId)); 47 | } 48 | } 49 | 50 | public void decrementUse(Client client) { 51 | if (use.decrementAndGet() <= 0 && !cached.get()) { 52 | close(client); 53 | } 54 | } 55 | 56 | public boolean incrementUse() { 57 | if (closing.get()) { 58 | return false; 59 | } 60 | use.getAndIncrement(); 61 | return true; 62 | } 63 | 64 | public void unCache(Client client) { 65 | cached.set(false); 66 | if (use.get() <= 0) { 67 | close(client); 68 | } 69 | } 70 | 71 | public boolean cache() { 72 | if (closing.get()) { 73 | return false; 74 | } 75 | return cached.compareAndSet(false, true); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "ServerPrepareResult{" 81 | + "statementId=" 82 | + statementId 83 | + ", numParams=" 84 | + numParams 85 | + ", numColumns=" 86 | + columns.length 87 | + ", closing=" 88 | + closing 89 | + ", use=" 90 | + use 91 | + ", cached=" 92 | + cached 93 | + '}'; 94 | } 95 | 96 | @Override 97 | public boolean equals(Object o) { 98 | if (this == o) { 99 | return true; 100 | } 101 | if (o == null || getClass() != o.getClass()) { 102 | return false; 103 | } 104 | ServerPrepareResult that = (ServerPrepareResult) o; 105 | return statementId == that.statementId; 106 | } 107 | 108 | @Override 109 | public int hashCode() { 110 | return Objects.hash(statementId); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/VersionFactory.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Properties; 9 | 10 | public final class VersionFactory { 11 | 12 | public static class VersionFactoryHolder { 13 | public static final String instance; 14 | 15 | static { 16 | String res = null; 17 | try (InputStream inputStream = 18 | VersionFactory.class.getClassLoader().getResourceAsStream("mariadb.properties")) { 19 | if (inputStream != null) { 20 | Properties prop = new Properties(); 21 | prop.load(inputStream); 22 | res = prop.getProperty("version"); 23 | } 24 | } catch (IOException e) { 25 | // eat 26 | } 27 | instance = res; 28 | } 29 | } 30 | 31 | public static String getInstance() { 32 | return VersionFactoryHolder.instance; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/constants/Capabilities.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util.constants; 5 | 6 | public class Capabilities { 7 | 8 | public static final int CLIENT_MYSQL = 1; 9 | public static final int FOUND_ROWS = 2; /* Found instead of affected rows */ 10 | public static final int LONG_FLAG = 4; /* Get all column flags */ 11 | public static final int CONNECT_WITH_DB = 8; /* One can specify db on connect */ 12 | public static final int NO_SCHEMA = 16; /* Don't allow database.table.column */ 13 | public static final int COMPRESS = 32; /* Can use compression protocol */ 14 | public static final int ODBC = 64; /* Odbc client */ 15 | public static final int LOCAL_FILES = 128; /* Can use LOAD DATA LOCAL */ 16 | public static final int IGNORE_SPACE = 256; /* Ignore spaces before '(' */ 17 | public static final int CLIENT_PROTOCOL_41 = 512; /* New 4.1 protocol */ 18 | public static final int CLIENT_INTERACTIVE = 1024; 19 | public static final int SSL = 2048; /* Switch to SSL after handshake */ 20 | public static final int IGNORE_SIGPIPE = 4096; /* IGNORE sigpipes */ 21 | public static final int TRANSACTIONS = 8192; 22 | public static final int RESERVED = 16384; /* Old flag for 4.1 protocol */ 23 | public static final int SECURE_CONNECTION = 32768; /* New 4.1 authentication */ 24 | public static final int MULTI_STATEMENTS = 1 << 16; /* Enable/disable multi-stmt support */ 25 | public static final int MULTI_RESULTS = 1 << 17; /* Enable/disable multi-results */ 26 | public static final int PS_MULTI_RESULTS = 27 | 1 << 18; /* Enable/disable multi-results for PrepareStatement */ 28 | public static final int PLUGIN_AUTH = 1 << 19; /* Client supports plugin authentication */ 29 | public static final int CONNECT_ATTRS = 1 << 20; /* Client send connection attributes */ 30 | public static final int PLUGIN_AUTH_LENENC_CLIENT_DATA = 31 | 1 << 21; /* authentication data length is a length auth integer */ 32 | public static final int CLIENT_SESSION_TRACK = 1 << 23; /* server send session tracking info */ 33 | public static final int CLIENT_DEPRECATE_EOF = 1 << 24; /* EOF packet deprecated */ 34 | public static final int PROGRESS_OLD = 35 | 1 << 29; /* Client support progress indicator (before 10.2)*/ 36 | 37 | /* MariaDB specific capabilities */ 38 | public static final long MARIADB_CLIENT_PROGRESS = 39 | 1L << 32; /* Client support progress indicator (since 10.2) */ 40 | public static final long MARIADB_CLIENT_COM_MULTI = 41 | 1L << 33; /* bundle command during connection */ 42 | 43 | /** permit COM_STMT_BULK commands */ 44 | public static final long MARIADB_CLIENT_STMT_BULK_OPERATIONS = 1L << 34; 45 | 46 | // permit skipping metadata 47 | public static final long MARIADB_CLIENT_CACHE_METADATA = 1L << 36; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/constants/ColumnFlags.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util.constants; 5 | 6 | public class ColumnFlags { 7 | public static final short NOT_NULL = 1; 8 | public static final short PRIMARY_KEY = 2; 9 | public static final short UNIQUE_KEY = 4; 10 | public static final short MULTIPLE_KEY = 8; 11 | public static final short BLOB = 16; 12 | public static final short UNSIGNED = 32; 13 | public static final short ZEROFILL = 64; 14 | public static final short BINARY_COLLATION = 128; 15 | public static final short ENUM = 256; 16 | public static final short AUTO_INCREMENT = 512; 17 | public static final short TIMESTAMP = 1024; 18 | public static final short SET = 2048; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/constants/ServerStatus.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util.constants; 5 | 6 | public class ServerStatus { 7 | public static final short IN_TRANSACTION = 1; 8 | public static final short AUTOCOMMIT = 2; 9 | public static final short MORE_RESULTS_EXISTS = 8; 10 | public static final short QUERY_NO_GOOD_INDEX_USED = 16; 11 | public static final short QUERY_NO_INDEX_USED = 32; 12 | public static final short CURSOR_EXISTS = 64; 13 | public static final short LAST_ROW_SENT = 128; 14 | public static final short DB_DROPPED = 256; 15 | public static final short NO_BACKSLASH_ESCAPES = 512; 16 | public static final short METADATA_CHANGED = 1024; 17 | public static final short QUERY_WAS_SLOW = 2048; 18 | public static final short PS_OUT_PARAMETERS = 4096; 19 | public static final short STATUS_IN_TRANS_READONLY = 1 << 13; 20 | public static final short SESSION_STATE_CHANGED = 1 << 14; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/mariadb/r2dbc/util/constants/StateChange.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.util.constants; 5 | 6 | public class StateChange { 7 | public static final short SESSION_TRACK_SYSTEM_VARIABLES = 0; 8 | public static final short SESSION_TRACK_SCHEMA = 1; 9 | public static final short SESSION_TRACK_STATE_CHANGE = 2; 10 | public static final short SESSION_TRACK_GTIDS = 3; 11 | public static final short SESSION_TRACK_TRANSACTION_CHARACTERISTICS = 4; 12 | public static final short SESSION_TRACK_TRANSACTION_STATE = 5; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java9/module-info.java: -------------------------------------------------------------------------------- 1 | module r2dbc.mariadb { 2 | requires transitive r2dbc.spi; 3 | requires transitive reactor.core; 4 | requires transitive io.netty.buffer; 5 | requires transitive io.netty.handler; 6 | requires transitive io.netty.transport.unix.common; 7 | requires transitive io.netty.common; 8 | requires transitive io.netty.transport; 9 | requires transitive io.netty.codec; 10 | requires transitive org.reactivestreams; 11 | requires transitive reactor.netty.core; 12 | requires transitive java.naming; 13 | 14 | exports org.mariadb.r2dbc; 15 | exports org.mariadb.r2dbc.api; 16 | exports org.mariadb.r2dbc.authentication; 17 | exports org.mariadb.r2dbc.message; 18 | 19 | uses org.mariadb.r2dbc.authentication.AuthenticationPlugin; 20 | uses io.r2dbc.spi.ConnectionFactoryProvider; 21 | 22 | provides io.r2dbc.spi.ConnectionFactoryProvider with 23 | org.mariadb.r2dbc.MariadbConnectionFactoryProvider; 24 | provides org.mariadb.r2dbc.authentication.AuthenticationPlugin with 25 | org.mariadb.r2dbc.authentication.standard.NativePasswordPluginFlow, 26 | org.mariadb.r2dbc.authentication.addon.ClearPasswordPluginFlow, 27 | org.mariadb.r2dbc.authentication.standard.Ed25519PasswordPluginFlow, 28 | org.mariadb.r2dbc.authentication.standard.Sha256PasswordPluginFlow, 29 | org.mariadb.r2dbc.authentication.standard.CachingSha2PasswordFlow, 30 | org.mariadb.r2dbc.authentication.standard.PamPluginFlow; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | 4 | org.mariadb.r2dbc.MariadbConnectionFactoryProvider 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.mariadb.r2dbc.authentication.AuthenticationPlugin: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | 4 | org.mariadb.r2dbc.authentication.standard.NativePasswordPluginFlow 5 | org.mariadb.r2dbc.authentication.addon.ClearPasswordPluginFlow 6 | org.mariadb.r2dbc.authentication.standard.Ed25519PasswordPluginFlow 7 | org.mariadb.r2dbc.authentication.standard.Sha256PasswordPluginFlow 8 | org.mariadb.r2dbc.authentication.standard.CachingSha2PasswordFlow 9 | org.mariadb.r2dbc.authentication.standard.PamPluginFlow 10 | org.mariadb.r2dbc.authentication.standard.ParsecPasswordPlugin -------------------------------------------------------------------------------- /src/main/resources/mariadb.properties: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright (c) 2020-2022 MariaDB Corporation Ab 3 | version=${project.version} 4 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/integration/ConnectionMetadataTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.integration; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | import io.r2dbc.spi.ConnectionFactoryMetadata; 10 | import io.r2dbc.spi.ConnectionMetadata; 11 | import org.junit.jupiter.api.Test; 12 | import org.mariadb.r2dbc.BaseConnectionTest; 13 | import org.mariadb.r2dbc.api.MariadbConnectionMetadata; 14 | 15 | public class ConnectionMetadataTest extends BaseConnectionTest { 16 | 17 | @Test 18 | void connectionMeta() { 19 | ConnectionMetadata meta = sharedConn.getMetadata(); 20 | System.out.println(meta.getDatabaseVersion()); 21 | assertEquals(meta.getDatabaseProductName(), isMariaDBServer() ? "MariaDB" : "MySQL"); 22 | if (isMariaDBServer() && !isXpand()) { 23 | assertTrue( 24 | meta.getDatabaseVersion().contains("10.") 25 | || meta.getDatabaseVersion().contains("11.") 26 | || meta.getDatabaseVersion().contains("23.")); 27 | } else { 28 | assertTrue( 29 | meta.getDatabaseVersion().contains("8.") || meta.getDatabaseVersion().contains("9.")); 30 | } 31 | String type = System.getenv("srv"); 32 | String version = System.getenv("v"); 33 | if (type != null && version != null && System.getenv("TRAVIS") != null) { 34 | if (version.endsWith("-rc")) version = version.replace("-rc", ""); 35 | if ("mariadb".equals(type) || "mysql".equals(type)) { 36 | assertTrue( 37 | meta.getDatabaseVersion().contains(version), 38 | "Error " + meta.getDatabaseVersion() + " doesn't contains " + version); 39 | assertEquals( 40 | type.toLowerCase(), 41 | meta.getDatabaseProductName().toLowerCase(), 42 | "Error comparing " + type + " with " + meta.getDatabaseProductName()); 43 | } 44 | } 45 | } 46 | 47 | @Test 48 | void factoryMeta() { 49 | ConnectionFactoryMetadata meta = factory.getMetadata(); 50 | assertEquals("MariaDB", meta.getName()); 51 | } 52 | 53 | @Test 54 | void metadataInfo() { 55 | MariadbConnectionMetadata meta = sharedConn.getMetadata(); 56 | assertTrue(meta.getMajorVersion() >= 5); 57 | assertTrue(meta.getMinorVersion() > -1); 58 | assertTrue(meta.getPatchVersion() > -1); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/integration/MariadbBinaryTestKit.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.integration; 5 | 6 | import io.r2dbc.spi.ConnectionFactory; 7 | import io.r2dbc.spi.test.TestKit; 8 | import java.sql.SQLException; 9 | import javax.sql.DataSource; 10 | import org.junit.jupiter.api.Assumptions; 11 | import org.mariadb.jdbc.MariaDbDataSource; 12 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 13 | import org.mariadb.r2dbc.MariadbConnectionFactory; 14 | import org.mariadb.r2dbc.TestConfiguration; 15 | import org.springframework.jdbc.core.JdbcOperations; 16 | import org.springframework.jdbc.core.JdbcTemplate; 17 | 18 | public class MariadbBinaryTestKit implements TestKit { 19 | private static final DataSource jdbcDatasource; 20 | 21 | static { 22 | String connString = 23 | String.format( 24 | "jdbc:mariadb://%s:%s/%s?user=%s&password=%s", 25 | TestConfiguration.host, 26 | TestConfiguration.port, 27 | TestConfiguration.database, 28 | TestConfiguration.username, 29 | TestConfiguration.password); 30 | try { 31 | jdbcDatasource = new MariaDbDataSource(connString); 32 | } catch (SQLException e) { 33 | throw new IllegalArgumentException( 34 | String.format("wrong initialization with %s", connString), e); 35 | } 36 | } 37 | 38 | @Override 39 | public ConnectionFactory getConnectionFactory() { 40 | // error crashing maxscale 6.1.x 41 | try (java.sql.Connection con = jdbcDatasource.getConnection()) { 42 | Assumptions.assumeTrue( 43 | !con.getMetaData().getDatabaseProductVersion().contains("maxScale-6.1.") 44 | && !"skysql-ha".equals(System.getenv("srv"))); 45 | } catch (SQLException e) { 46 | // eat 47 | } 48 | try { 49 | MariadbConnectionConfiguration confMulti = 50 | TestConfiguration.defaultBuilder 51 | .clone() 52 | .useServerPrepStmts(true) 53 | .allowMultiQueries(true) 54 | .build(); 55 | return new MariadbConnectionFactory(confMulti); 56 | } catch (CloneNotSupportedException e) { 57 | throw new IllegalStateException("Unexpected error"); 58 | } 59 | } 60 | 61 | @Override 62 | public String getPlaceholder(int i) { 63 | return ":v" + i; 64 | } 65 | 66 | @Override 67 | public String getIdentifier(int i) { 68 | return "v" + i; 69 | } 70 | 71 | @Override 72 | public JdbcOperations getJdbcOperations() { 73 | return new JdbcTemplate(MariadbBinaryTestKit.jdbcDatasource); 74 | } 75 | 76 | @Override 77 | public String doGetSql(TestStatement statement) { 78 | switch (statement) { 79 | case CREATE_TABLE_AUTOGENERATED_KEY: 80 | return TestStatement.CREATE_TABLE_AUTOGENERATED_KEY 81 | .getSql() 82 | .replaceAll("IDENTITY", "PRIMARY KEY AUTO_INCREMENT"); 83 | case INSERT_VALUE_AUTOGENERATED_KEY: 84 | case INSERT_VALUE100: 85 | return "INSERT INTO test(test_value) VALUES (100)"; 86 | case INSERT_VALUE200: 87 | return "INSERT INTO test(test_value) VALUES (200)"; 88 | default: 89 | if (statement.getSql().startsWith("CREATE TABLE ")) { 90 | return "CREATE TABLE IF NOT EXISTS " + statement.getSql().substring(13); 91 | } 92 | return statement.getSql(); 93 | } 94 | } 95 | 96 | @Override 97 | public String clobType() { 98 | return "TEXT"; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/integration/MariadbTextTestKit.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.integration; 5 | 6 | import io.r2dbc.spi.ConnectionFactory; 7 | import io.r2dbc.spi.test.TestKit; 8 | import java.sql.SQLException; 9 | import javax.sql.DataSource; 10 | import org.junit.jupiter.api.Assumptions; 11 | import org.mariadb.jdbc.MariaDbDataSource; 12 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 13 | import org.mariadb.r2dbc.MariadbConnectionFactory; 14 | import org.mariadb.r2dbc.TestConfiguration; 15 | import org.springframework.jdbc.core.JdbcOperations; 16 | import org.springframework.jdbc.core.JdbcTemplate; 17 | 18 | public class MariadbTextTestKit implements TestKit { 19 | private static final DataSource jdbcDatasource; 20 | 21 | static { 22 | String connString = 23 | String.format( 24 | "jdbc:mariadb://%s:%s/%s?user=%s&password=%s", 25 | TestConfiguration.host, 26 | TestConfiguration.port, 27 | TestConfiguration.database, 28 | TestConfiguration.username, 29 | TestConfiguration.password); 30 | try { 31 | jdbcDatasource = new MariaDbDataSource(connString); 32 | } catch (SQLException e) { 33 | throw new IllegalArgumentException( 34 | String.format("wrong initialization with %s", connString), e); 35 | } 36 | } 37 | 38 | @Override 39 | public ConnectionFactory getConnectionFactory() { 40 | // error crashing maxscale 6.1.x 41 | try (java.sql.Connection con = jdbcDatasource.getConnection()) { 42 | Assumptions.assumeTrue( 43 | !con.getMetaData().getDatabaseProductVersion().contains("maxScale-6.1.") 44 | && !"skysql-ha".equals(System.getenv("srv"))); 45 | } catch (SQLException e) { 46 | // eat 47 | } 48 | 49 | try { 50 | MariadbConnectionConfiguration confMulti = 51 | TestConfiguration.defaultBuilder.clone().allowMultiQueries(true).build(); 52 | return new MariadbConnectionFactory(confMulti); 53 | } catch (CloneNotSupportedException e) { 54 | throw new IllegalStateException("Unexpected error"); 55 | } 56 | } 57 | 58 | @Override 59 | public String getPlaceholder(int i) { 60 | return ":v" + i; 61 | } 62 | 63 | @Override 64 | public String getIdentifier(int i) { 65 | return "v" + i; 66 | } 67 | 68 | @Override 69 | public JdbcOperations getJdbcOperations() { 70 | return new JdbcTemplate(MariadbTextTestKit.jdbcDatasource); 71 | } 72 | 73 | @Override 74 | public String doGetSql(TestStatement statement) { 75 | switch (statement) { 76 | case CREATE_TABLE_AUTOGENERATED_KEY: 77 | return TestStatement.CREATE_TABLE_AUTOGENERATED_KEY 78 | .getSql() 79 | .replaceAll("IDENTITY", "PRIMARY KEY AUTO_INCREMENT"); 80 | case INSERT_VALUE_AUTOGENERATED_KEY: 81 | case INSERT_VALUE100: 82 | return "INSERT INTO test(test_value) VALUES (100)"; 83 | case INSERT_VALUE200: 84 | return "INSERT INTO test(test_value) VALUES (200)"; 85 | default: 86 | return statement.getSql(); 87 | } 88 | } 89 | 90 | @Override 91 | public String clobType() { 92 | return "TEXT"; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/integration/NoPipelineTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.integration; 5 | 6 | import java.math.BigInteger; 7 | import java.time.Duration; 8 | import java.time.Instant; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import org.junit.jupiter.api.Assumptions; 12 | import org.junit.jupiter.api.Test; 13 | import org.mariadb.r2dbc.BaseConnectionTest; 14 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 15 | import org.mariadb.r2dbc.MariadbConnectionFactory; 16 | import org.mariadb.r2dbc.TestConfiguration; 17 | import org.mariadb.r2dbc.api.MariadbConnection; 18 | import reactor.core.publisher.Flux; 19 | import reactor.test.StepVerifier; 20 | 21 | public class NoPipelineTest extends BaseConnectionTest { 22 | 23 | @Test 24 | void noPipelineConnect() throws Exception { 25 | // use sequence engine 26 | Assumptions.assumeTrue(!isMariaDBServer() && minVersion(10, 0, 3)); 27 | 28 | MariadbConnectionConfiguration confPipeline = 29 | TestConfiguration.defaultBuilder.clone().allowPipelining(true).build(); 30 | MariadbConnection connection = new MariadbConnectionFactory(confPipeline).create().block(); 31 | 32 | try { 33 | runWithPipeline(connection); 34 | } finally { 35 | connection.close().block(); 36 | } 37 | } 38 | 39 | private Duration runWithPipeline(MariadbConnection connection) { 40 | Instant initial = Instant.now(); 41 | int MAX = 100; 42 | List> fluxes = new ArrayList<>(); 43 | for (int i = 0; i < MAX; i++) { 44 | fluxes.add( 45 | connection 46 | .createStatement("SELECT * from seq_" + (100 * i) + "_to_" + (100 * (i + 1) - 1)) 47 | .execute() 48 | .flatMap(r -> r.map((row, metadata) -> row.get(0, BigInteger.class)))); 49 | } 50 | 51 | for (int i = 0; i < MAX - 1; i++) { 52 | fluxes.get(i).subscribe(); 53 | } 54 | Flux.concat(fluxes.get(MAX - 1)).as(StepVerifier::create).expectNextCount(100).verifyComplete(); 55 | return Duration.between(initial, Instant.now()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/integration/StatementBatchingTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.integration; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.mariadb.r2dbc.BaseConnectionTest; 8 | import org.mariadb.r2dbc.api.MariadbConnection; 9 | import reactor.test.StepVerifier; 10 | 11 | public class StatementBatchingTest extends BaseConnectionTest { 12 | 13 | @Test 14 | void batchStatement() { 15 | batchStatement(sharedConn); 16 | } 17 | 18 | @Test 19 | void batchStatementPrepare() { 20 | batchStatement(sharedConnPrepare); 21 | } 22 | 23 | void batchStatement(MariadbConnection connection) { 24 | connection 25 | .createStatement( 26 | "CREATE TEMPORARY TABLE batchStatement (id int not null primary key auto_increment," 27 | + " test varchar(10))") 28 | .execute() 29 | .blockLast(); 30 | connection.beginTransaction().block(); 31 | connection 32 | .createStatement("INSERT INTO batchStatement values (?, ?)") 33 | .bind(0, 1) 34 | .bind(1, "test") 35 | .add() 36 | .bind(1, "test2") 37 | .bind(0, 2) 38 | .execute() 39 | .blockLast(); 40 | 41 | connection 42 | .createStatement("SELECT * FROM batchStatement") 43 | .execute() 44 | .flatMap(r -> r.map((row, metadata) -> row.get(0, String.class) + row.get(1, String.class))) 45 | .as(StepVerifier::create) 46 | .expectNext("1test", "2test2") 47 | .verifyComplete(); 48 | connection.rollbackTransaction().block(); 49 | } 50 | 51 | @Test 52 | void batchStatementResultSet() { 53 | batchStatementResultSet(sharedConn); 54 | } 55 | 56 | @Test 57 | void batchStatementResultSetPrepare() { 58 | batchStatementResultSet(sharedConnPrepare); 59 | } 60 | 61 | void batchStatementResultSet(MariadbConnection connection) { 62 | connection 63 | .createStatement( 64 | "CREATE TEMPORARY TABLE batchStatementResultSet (id int not null primary key" 65 | + " auto_increment, test varchar(10))") 66 | .execute() 67 | .blockLast(); 68 | connection.beginTransaction().block(); 69 | connection 70 | .createStatement("INSERT INTO batchStatementResultSet values (1, 'test1'), (2, 'test2')") 71 | .execute() 72 | .blockLast(); 73 | 74 | connection 75 | .createStatement("SELECT test FROM batchStatementResultSet WHERE id = ?") 76 | .bind(0, 1) 77 | .add() 78 | .bind(0, 2) 79 | .add() 80 | .bind(0, 1) 81 | .execute() 82 | .flatMap(r -> r.map((row, metadata) -> row.get(0, String.class))) 83 | .as(StepVerifier::create) 84 | .expectNext("test1", "test2", "test1") 85 | .verifyComplete(); 86 | connection.rollbackTransaction().block(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/integration/authentication/PamPluginTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.integration.authentication; 5 | 6 | import org.junit.jupiter.api.Assumptions; 7 | import org.junit.jupiter.api.Test; 8 | import org.mariadb.r2dbc.BaseConnectionTest; 9 | import org.mariadb.r2dbc.MariadbConnectionConfiguration; 10 | import org.mariadb.r2dbc.MariadbConnectionFactory; 11 | import org.mariadb.r2dbc.TestConfiguration; 12 | import org.mariadb.r2dbc.api.MariadbConnection; 13 | 14 | public class PamPluginTest extends BaseConnectionTest { 15 | 16 | @Test 17 | public void pamAuthPlugin() throws Throwable { 18 | // https://mariadb.com/kb/en/authentication-plugin-pam/ 19 | // only test on travis, because only work on Unix-like operating systems. 20 | // /etc/pam.d/mariadb pam configuration is created beforehand 21 | Assumptions.assumeTrue( 22 | System.getenv("TRAVIS") != null 23 | && System.getenv("TEST_PAM_USER") != null 24 | && !"maxscale".equals(System.getenv("srv")) 25 | && !"skysql".equals(System.getenv("srv")) 26 | && !"mariadb-es".equals(System.getenv("srv")) 27 | && !"mariadb-es-test".equals(System.getenv("srv")) 28 | && !"skysql-ha".equals(System.getenv("srv"))); 29 | Assumptions.assumeTrue(isMariaDBServer()); 30 | String pamUser = System.getenv("TEST_PAM_USER"); 31 | sharedConn.createStatement("INSTALL PLUGIN pam SONAME 'auth_pam'").execute().blockLast(); 32 | sharedConn.createStatement("DROP USER IF EXISTS '" + pamUser + "'@'%'").execute().blockLast(); 33 | sharedConn 34 | .createStatement("CREATE USER '" + pamUser + "'@'%' IDENTIFIED VIA pam USING 'mariadb'") 35 | .execute() 36 | .blockLast(); 37 | sharedConn 38 | .createStatement("GRANT SELECT ON *.* TO '" + pamUser + "'@'%' IDENTIFIED VIA pam") 39 | .execute() 40 | .blockLast(); 41 | sharedConn.createStatement("FLUSH PRIVILEGES").execute().blockLast(); 42 | 43 | int testPort = TestConfiguration.port; 44 | if (System.getenv("TEST_PAM_PORT") != null) { 45 | testPort = Integer.parseInt(System.getenv("TEST_PAM_PORT")); 46 | } 47 | 48 | MariadbConnectionConfiguration conf = 49 | TestConfiguration.defaultBuilder 50 | .clone() 51 | .username(System.getenv("TEST_PAM_USER")) 52 | .password(System.getenv("TEST_PAM_PWD")) 53 | .port(testPort) 54 | .build(); 55 | MariadbConnection connection = new MariadbConnectionFactory(conf).create().block(); 56 | connection.close().block(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/integration/authentication/ParsecPluginTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.integration.authentication; 5 | 6 | import io.r2dbc.spi.R2dbcNonTransientResourceException; 7 | import java.sql.SQLException; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | import org.junit.jupiter.api.Assumptions; 10 | import org.junit.jupiter.api.BeforeAll; 11 | import org.junit.jupiter.api.Test; 12 | import org.mariadb.r2dbc.*; 13 | import org.mariadb.r2dbc.api.MariadbConnection; 14 | import org.mariadb.r2dbc.api.MariadbConnectionMetadata; 15 | import reactor.core.publisher.Flux; 16 | 17 | public class ParsecPluginTest extends BaseConnectionTest { 18 | 19 | static AtomicBoolean parsecPluginEnabled = new AtomicBoolean(true); 20 | 21 | @BeforeAll 22 | public static void before2() { 23 | MariadbConnectionMetadata meta = sharedConn.getMetadata(); 24 | if (meta.isMariaDBServer() 25 | && meta.minVersion(11, 6, 1) 26 | && !"mariadb-es".equals(System.getenv("srv"))) { 27 | sharedConn 28 | .createStatement("INSTALL SONAME 'auth_parsec'") 29 | .execute() 30 | .onErrorResume( 31 | e -> { 32 | parsecPluginEnabled.set(false); 33 | return Flux.empty(); 34 | }) 35 | .blockLast(); 36 | } else parsecPluginEnabled.set(false); 37 | } 38 | 39 | @Test 40 | public void parsecAuthPlugin() throws Throwable { 41 | Assumptions.assumeTrue( 42 | parsecPluginEnabled.get() 43 | && !"maxscale".equals(System.getenv("srv")) 44 | && !"mariadb-es".equals(System.getenv("srv"))); 45 | 46 | sharedConn.createStatement("drop user IF EXISTS verifParsec@'%'").execute().blockLast(); 47 | sharedConn 48 | .createStatement( 49 | "CREATE USER verifParsec@'%' IDENTIFIED VIA parsec USING PASSWORD('MySup8%rPassw@ord')") 50 | .execute() 51 | .blockLast(); 52 | sharedConn.createStatement("GRANT SELECT on *.* to verifParsec@'%'").execute().blockLast(); 53 | 54 | String version = System.getProperty("java.version"); 55 | int majorVersion = 56 | (version.indexOf(".") >= 0) 57 | ? Integer.parseInt(version.substring(0, version.indexOf("."))) 58 | : Integer.parseInt(version); 59 | MariadbConnectionConfiguration conf = 60 | TestConfiguration.defaultBuilder 61 | .clone() 62 | .username("verifParsec") 63 | .password("MySup8%rPassw@ord") 64 | .build(); 65 | 66 | if (majorVersion < 15) { 67 | // before java 15, Ed25519 is not supported 68 | // assuming, that BouncyCastle is not on test classpath 69 | MariadbConnection connection = new MariadbConnectionFactory(conf).create().block(); 70 | 71 | assertThrowsContains( 72 | SQLException.class, 73 | () -> new MariadbConnectionFactory(conf).create().block(), 74 | "Parsec authentication not available. Either use Java 15+ or add BouncyCastle" 75 | + " dependency"); 76 | } else { 77 | MariadbConnection connection = new MariadbConnectionFactory(conf).create().block(); 78 | connection.close().block(); 79 | } 80 | MariadbConnectionConfiguration conf2 = 81 | TestConfiguration.defaultBuilder 82 | .clone() 83 | .username("verifParsec") 84 | .password("MySup8%rPassw@ord") 85 | .restrictedAuth("mysql_native_password,ed25519") 86 | .build(); 87 | assertThrowsContains( 88 | R2dbcNonTransientResourceException.class, 89 | () -> new MariadbConnectionFactory(conf2).create().block(), 90 | "Unsupported authentication plugin parsec"); 91 | sharedConn.createStatement("drop user verifParsec@'%'").execute().blockLast(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/integration/codec/JsonParseTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.integration.codec; 5 | 6 | import java.util.Optional; 7 | import org.junit.jupiter.api.AfterAll; 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.Test; 10 | import org.mariadb.r2dbc.BaseConnectionTest; 11 | import org.mariadb.r2dbc.api.MariadbConnection; 12 | import reactor.test.StepVerifier; 13 | 14 | public class JsonParseTest extends BaseConnectionTest { 15 | @BeforeAll 16 | public static void before2() { 17 | afterAll2(); 18 | sharedConn.beginTransaction().block(); 19 | sharedConn.createStatement("DROP TABLE IF EXISTS JsonTable").execute().blockLast(); 20 | sharedConn.createStatement("CREATE TABLE JsonTable (t1 JSON)").execute().blockLast(); 21 | sharedConn 22 | .createStatement( 23 | "INSERT INTO JsonTable VALUES" + " ('{}'),('{\"val\": \"val1\"}')," + " (null)") 24 | .execute() 25 | .blockLast(); 26 | sharedConn.createStatement("FLUSH TABLES").execute().blockLast(); 27 | sharedConn.commitTransaction().block(); 28 | } 29 | 30 | @AfterAll 31 | public static void afterAll2() { 32 | // sharedConn.createStatement("DROP TABLE IF EXISTS JsonTable").execute().blockLast(); 33 | } 34 | 35 | @Test 36 | void defaultValue() { 37 | defaultValue(sharedConn); 38 | } 39 | 40 | @Test 41 | void defaultValuePrepare() { 42 | defaultValue(sharedConnPrepare); 43 | } 44 | 45 | private void defaultValue(MariadbConnection connection) { 46 | connection 47 | .createStatement("SELECT t1 FROM JsonTable WHERE 1 = ?") 48 | .bind(0, 1) 49 | .execute() 50 | .flatMap(r -> r.map((row, metadata) -> Optional.ofNullable(row.get(0)))) 51 | .as(StepVerifier::create) 52 | .expectNext(Optional.of("{}"), Optional.of("{\"val\": \"val1\"}"), Optional.empty()) 53 | .verifyComplete(); 54 | 55 | connection 56 | .createStatement("SELECT t1 FROM JsonTable WHERE 1 = ?") 57 | .bind(0, 1) 58 | .execute() 59 | .flatMap(r -> r.map((row, metadata) -> Optional.ofNullable(row.get(0, Object.class)))) 60 | .as(StepVerifier::create) 61 | .expectNext(Optional.of("{}"), Optional.of("{\"val\": \"val1\"}"), Optional.empty()) 62 | .verifyComplete(); 63 | } 64 | 65 | @Test 66 | void stringValue() { 67 | stringValue(sharedConn); 68 | } 69 | 70 | @Test 71 | void stringValuePrepare() { 72 | stringValue(sharedConnPrepare); 73 | } 74 | 75 | private void stringValue(MariadbConnection connection) { 76 | connection 77 | .createStatement("SELECT t1 FROM JsonTable WHERE 1 = ?") 78 | .bind(0, 1) 79 | .execute() 80 | .flatMap(r -> r.map((row, metadata) -> Optional.ofNullable(row.get(0, String.class)))) 81 | .as(StepVerifier::create) 82 | .expectNext(Optional.of("{}"), Optional.of("{\"val\": \"val1\"}"), Optional.empty()) 83 | .verifyComplete(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/tools/TcpProxy.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.tools; 5 | 6 | import java.io.IOException; 7 | import java.util.concurrent.Executors; 8 | import java.util.concurrent.ScheduledExecutorService; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | public class TcpProxy { 12 | 13 | private final String host; 14 | private final TcpProxySocket socket; 15 | private ScheduledExecutorService executorService; 16 | 17 | /** 18 | * Initialise proxy. 19 | * 20 | * @param host host (ip / dns) 21 | * @param remoteport port 22 | * @throws IOException exception 23 | */ 24 | public TcpProxy(String host, int remoteport) throws IOException { 25 | this.host = host; 26 | socket = new TcpProxySocket(host, remoteport); 27 | executorService = Executors.newSingleThreadScheduledExecutor(); 28 | executorService.schedule(socket, 0, TimeUnit.MILLISECONDS); 29 | } 30 | 31 | public void stop() throws InterruptedException { 32 | socket.kill(); 33 | executorService.shutdownNow(); 34 | } 35 | 36 | public void setDelay(int delay) { 37 | socket.setDelay(delay); 38 | } 39 | 40 | public void removeDelay() { 41 | socket.setDelay(1); 42 | } 43 | 44 | /** 45 | * Stop proxy and restart after X milliseconds. 46 | * 47 | * @param sleepTime sleep time in milliseconds 48 | */ 49 | public void restart(long sleepTime) throws InterruptedException { 50 | socket.kill(); 51 | executorService.shutdownNow(); 52 | 53 | executorService = Executors.newSingleThreadScheduledExecutor(); 54 | executorService.schedule(socket, sleepTime, TimeUnit.MILLISECONDS); 55 | } 56 | 57 | public void forceClose() { 58 | socket.sendRst(); 59 | executorService.shutdownNow(); 60 | } 61 | 62 | /** Restart proxy. */ 63 | public void restart() throws InterruptedException { 64 | socket.kill(); 65 | executorService = Executors.newSingleThreadScheduledExecutor(); 66 | executorService.execute(socket); 67 | try { 68 | Thread.sleep(10); 69 | } catch (InterruptedException e) { 70 | // eat Exception 71 | } 72 | } 73 | 74 | public int getLocalPort() { 75 | return socket.getLocalPort(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/unit/InitFinalClass.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.unit; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.mariadb.r2dbc.codec.Codecs; 8 | import org.mariadb.r2dbc.util.BufferUtils; 9 | import org.mariadb.r2dbc.util.constants.Capabilities; 10 | import org.mariadb.r2dbc.util.constants.ColumnFlags; 11 | import org.mariadb.r2dbc.util.constants.ServerStatus; 12 | import org.mariadb.r2dbc.util.constants.StateChange; 13 | 14 | public class InitFinalClass { 15 | 16 | @Test 17 | public void init() throws Exception { 18 | Codecs codecs = new Codecs(); 19 | BufferUtils buf = new BufferUtils(); 20 | Capabilities c = new Capabilities(); 21 | ColumnFlags c2 = new ColumnFlags(); 22 | ServerStatus c3 = new ServerStatus(); 23 | StateChange c4 = new StateChange(); 24 | System.out.println(codecs.hashCode() + buf.hashCode()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/unit/SslModeTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.unit; 5 | 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import org.mariadb.r2dbc.SslMode; 9 | 10 | public class SslModeTest { 11 | 12 | @Test 13 | public void parse() throws Exception { 14 | Assertions.assertEquals(SslMode.DISABLE, SslMode.from("disable")); 15 | Assertions.assertEquals(SslMode.DISABLE, SslMode.from("DISABLE")); 16 | Assertions.assertEquals(SslMode.DISABLE, SslMode.from("DISABLED")); 17 | Assertions.assertEquals(SslMode.DISABLE, SslMode.from("0")); 18 | Assertions.assertEquals(SslMode.DISABLE, SslMode.from("false")); 19 | 20 | Assertions.assertThrows(IllegalArgumentException.class, () -> SslMode.from("wrong")); 21 | 22 | Assertions.assertEquals(SslMode.TRUST, SslMode.from("trust")); 23 | Assertions.assertEquals(SslMode.TRUST, SslMode.from("REQUIRED")); 24 | Assertions.assertEquals(SslMode.TRUST, SslMode.from("enable_trust")); 25 | 26 | Assertions.assertEquals(SslMode.VERIFY_CA, SslMode.from("verify-ca")); 27 | Assertions.assertEquals(SslMode.VERIFY_CA, SslMode.from("VERIFY_CA")); 28 | Assertions.assertEquals( 29 | SslMode.VERIFY_CA, SslMode.from("ENABLE_WITHOUT_HOSTNAME_VERIFICATION")); 30 | 31 | Assertions.assertEquals(SslMode.VERIFY_FULL, SslMode.from("verify-full")); 32 | Assertions.assertEquals(SslMode.VERIFY_FULL, SslMode.from("VERIFY_FULL")); 33 | Assertions.assertEquals(SslMode.VERIFY_FULL, SslMode.from("VERIFY_IDENTITY")); 34 | Assertions.assertEquals(SslMode.VERIFY_FULL, SslMode.from("1")); 35 | Assertions.assertEquals(SslMode.VERIFY_FULL, SslMode.from("true")); 36 | Assertions.assertEquals(SslMode.VERIFY_FULL, SslMode.from("enable")); 37 | Assertions.assertEquals(SslMode.TUNNEL, SslMode.from("tunnel")); 38 | Assertions.assertEquals(SslMode.TUNNEL, SslMode.from("TUNNEL")); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/unit/client/ServerVersionTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.unit.client; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | import org.junit.jupiter.api.Test; 9 | import org.mariadb.r2dbc.client.ServerVersion; 10 | 11 | public class ServerVersionTest { 12 | @Test 13 | void testMinVersion() { 14 | ServerVersion sv = new ServerVersion("10.2.25-mariadb", true); 15 | assertEquals(10, sv.getMajorVersion()); 16 | assertEquals(2, sv.getMinorVersion()); 17 | assertEquals(25, sv.getPatchVersion()); 18 | assertTrue(sv.versionGreaterOrEqual(9, 8, 8)); 19 | assertTrue(sv.versionGreaterOrEqual(10, 1, 8)); 20 | assertTrue(sv.versionGreaterOrEqual(10, 2, 8)); 21 | assertTrue(sv.versionGreaterOrEqual(10, 2, 25)); 22 | assertFalse(sv.versionGreaterOrEqual(19, 8, 8)); 23 | assertFalse(sv.versionGreaterOrEqual(10, 3, 8)); 24 | assertFalse(sv.versionGreaterOrEqual(10, 2, 30)); 25 | 26 | ServerVersion sv2 = new ServerVersion("10.2.25", true); 27 | assertEquals(10, sv2.getMajorVersion()); 28 | assertEquals(2, sv2.getMinorVersion()); 29 | assertEquals(25, sv2.getPatchVersion()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/mariadb/r2dbc/unit/util/MariadbTypeTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (c) 2020-2024 MariaDB Corporation Ab 3 | 4 | package org.mariadb.r2dbc.unit.util; 5 | 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import org.mariadb.r2dbc.util.MariadbType; 9 | 10 | public class MariadbTypeTest { 11 | 12 | @Test 13 | void getName() { 14 | Assertions.assertEquals("VARCHAR", MariadbType.VARCHAR.getName()); 15 | Assertions.assertEquals("BIGINT", MariadbType.UNSIGNED_BIGINT.getName()); 16 | Assertions.assertEquals("UNSIGNED_BIGINT", MariadbType.UNSIGNED_BIGINT.name()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/conf.properties: -------------------------------------------------------------------------------- 1 | DB_HOST=localhost 2 | DB_PORT=3306 3 | DB_DATABASE=testr2 4 | DB_USER=root 5 | DB_PASSWORD= 6 | DB_OTHER= 7 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | %d{yyyy-MM-dd} | %d{HH:mm:ss.SSS} | %-20.20thread | %5p | %logger{25} | %m%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------