├── .github └── workflows │ ├── ci.yml │ ├── publish-docs.yml │ ├── pullrequests.yml │ └── release.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .travis.yml ├── CHANGELOG ├── LICENSE ├── NOTICE ├── README.md ├── ci └── build-and-deploy-to-maven-central.sh ├── intellij-style.xml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── settings.xml └── src ├── main ├── asciidoc │ ├── components.adoc │ ├── docinfo.html │ ├── features.adoc │ ├── getting-started.adoc │ ├── images │ │ ├── metrics-actuator-connection.png │ │ ├── metrics-jmx-connection.png │ │ ├── metrics-jmx-query.png │ │ ├── r2dbc-proxy-diagram.png │ │ ├── r2dbc-proxy-diagram2.png │ │ ├── zipkin-span-batch-query.png │ │ ├── zipkin-span-connection.png │ │ └── zipkin-tracing-rollback.png │ ├── index.adoc │ ├── introduction.adoc │ ├── overview.adoc │ ├── setup.adoc │ └── use-cases.adoc ├── java │ └── io │ │ └── r2dbc │ │ └── proxy │ │ ├── ProxyConnectionFactory.java │ │ ├── ProxyConnectionFactoryProvider.java │ │ ├── callback │ │ ├── AfterQueryCallbackInvoker.java │ │ ├── BatchCallbackHandler.java │ │ ├── CallbackHandler.java │ │ ├── CallbackHandlerSupport.java │ │ ├── ConnectionCallbackHandler.java │ │ ├── ConnectionFactoryCallbackHandler.java │ │ ├── ConnectionFactoryCreateMethodInvocationSubscriber.java │ │ ├── ConnectionHolder.java │ │ ├── ConnectionIdManager.java │ │ ├── DefaultConnectionIdManager.java │ │ ├── DefaultConnectionInfo.java │ │ ├── DelegatingContextView.java │ │ ├── JdkProxyFactory.java │ │ ├── JdkProxyFactoryFactory.java │ │ ├── MethodInvocationSubscriber.java │ │ ├── MutableBindInfo.java │ │ ├── MutableMethodExecutionInfo.java │ │ ├── MutableQueryExecutionInfo.java │ │ ├── MutableStatementInfo.java │ │ ├── ProxyConfig.java │ │ ├── ProxyConfigHolder.java │ │ ├── ProxyFactory.java │ │ ├── ProxyFactoryFactory.java │ │ ├── ProxyUtils.java │ │ ├── QueriesExecutionContext.java │ │ ├── QueryInvocationSubscriber.java │ │ ├── ResultCallbackHandler.java │ │ ├── ResultInvocationSubscriber.java │ │ ├── RowCallbackHandler.java │ │ ├── RowSegmentCallbackHandler.java │ │ ├── StatementCallbackHandler.java │ │ ├── StopWatch.java │ │ └── package-info.java │ │ ├── core │ │ ├── BindInfo.java │ │ ├── Binding.java │ │ ├── Bindings.java │ │ ├── BoundValue.java │ │ ├── ConnectionInfo.java │ │ ├── DefaultValueStore.java │ │ ├── ExecutionType.java │ │ ├── MethodExecutionInfo.java │ │ ├── ProxyEventType.java │ │ ├── QueryExecutionInfo.java │ │ ├── QueryInfo.java │ │ ├── R2dbcProxyException.java │ │ ├── StatementInfo.java │ │ ├── ValueStore.java │ │ └── package-info.java │ │ ├── listener │ │ ├── BindParameterConverter.java │ │ ├── CompositeProxyExecutionListener.java │ │ ├── LastExecutionAwareListener.java │ │ ├── ProxyExecutionListener.java │ │ ├── ProxyMethodExecutionListener.java │ │ ├── ProxyMethodExecutionListenerAdapter.java │ │ ├── ResultRowConverter.java │ │ └── package-info.java │ │ ├── observation │ │ ├── DefaultQueryParametersTagProvider.java │ │ ├── ObservationProxyExecutionListener.java │ │ ├── QueryContext.java │ │ ├── QueryObservationConvention.java │ │ ├── QueryParametersTagProvider.java │ │ ├── R2dbcObservationDocumentation.java │ │ ├── VirtualThreadQueryObservationConvention.java │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── support │ │ ├── FormatterUtils.java │ │ ├── MethodExecutionInfoFormatter.java │ │ ├── QueryExecutionInfoFormatter.java │ │ └── package-info.java │ │ ├── test │ │ ├── MockConnectionInfo.java │ │ ├── MockMethodExecutionInfo.java │ │ ├── MockQueryExecutionInfo.java │ │ ├── MockStatementInfo.java │ │ └── package-info.java │ │ └── util │ │ ├── Assert.java │ │ └── package-info.java └── resources │ └── META-INF │ ├── native-image │ └── io.r2dbc │ │ └── r2dbc-proxy │ │ └── native-image.properties │ └── services │ └── io.r2dbc.spi.ConnectionFactoryProvider └── test ├── java └── io │ └── r2dbc │ └── proxy │ ├── MockConnectionFactoryProvider.java │ ├── ProxyConnectionFactoryProviderTest.java │ ├── ProxyConnectionFactoryTest.java │ ├── callback │ ├── AfterQueryCallbackTest.java │ ├── BatchCallbackHandlerTest.java │ ├── CallbackHandlerSupportTest.java │ ├── ConnectionCallbackHandlerTest.java │ ├── ConnectionFactoryCallbackHandlerTest.java │ ├── DefaultConnectionIdManagerTest.java │ ├── JdkProxyFactoryTest.java │ ├── MethodInvocationSubscriberTest.java │ ├── ProxyConfigTest.java │ ├── ProxyUtilsTest.java │ ├── QueryInvocationSubscriberTest.java │ ├── ReactorContextPropagationIntegrationTest.java │ ├── ResultCallbackHandlerIntegrationTest.java │ ├── ResultCallbackHandlerTest.java │ ├── ResultInvocationSubscriberTest.java │ ├── RowCallbackHandlerTest.java │ ├── RowSegmentCallbackHandlerTest.java │ └── StatementCallbackHandlerTest.java │ ├── core │ └── DefaultValueStoreTest.java │ ├── listener │ ├── CompositeProxyExecutionListenerTest.java │ ├── ProxyClassesSource.java │ ├── ProxyExecutionListenerTest.java │ ├── ProxyMethodExecutionListenerAdapterTest.java │ ├── ProxyMethodExecutionListenerTest.java │ └── ResultRowConverterTest.java │ ├── observation │ ├── DefaultQueryParametersTagProviderTest.java │ ├── ObservationProxyExecutionListenerIntegrationTest.java │ ├── ObservationProxyExecutionListenerTest.java │ └── VirtualThreadQueryObservationConventionTest.java │ ├── support │ ├── FormatterUtilsTest.java │ ├── MethodExecutionInfoFormatterTest.java │ └── QueryExecutionInfoFormatterTest.java │ └── util │ └── ChangeLogReportGenerator.java └── resources └── META-INF └── services └── io.r2dbc.spi.ConnectionFactoryProvider /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main, 1.0.x, 0.8.x ] 9 | 10 | jobs: 11 | build: 12 | if: github.repository == 'r2dbc/r2dbc-proxy' 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | include: 17 | - os: ubuntu-latest 18 | mvn: ./mvnw 19 | - os: windows-latest 20 | mvn: mvn 21 | # Since macOS 14.4.1, macos-latest does not have java 8. 22 | # https://github.com/actions/runner-images/blob/main/images/macos/macos-144-arm64-Readme.md 23 | # - os: macos-latest 24 | # mvn: ./mvnw 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Set up JDK 1.8 28 | uses: actions/setup-java@v3 29 | with: 30 | java-version: 8 31 | distribution: temurin 32 | cache: 'maven' 33 | - name: Build with Maven 34 | env: 35 | SONATYPE_USER: ${{ secrets.SONATYPE_USER }} 36 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 37 | run: ${{ matrix.mvn }} -B deploy -P snapshot -s settings.xml 38 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Publish documentation to the project page 5 | 6 | on: 7 | push: 8 | branches: [ main, release-0.x ] 9 | 10 | jobs: 11 | publish: 12 | if: github.repository == 'r2dbc/r2dbc-proxy' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up JDK 1.8 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: 8 20 | distribution: temurin 21 | cache: 'maven' 22 | 23 | - name: Get project version 24 | run: | 25 | VERSION=$( ./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout ) 26 | echo "project_version=$VERSION" >> $GITHUB_ENV 27 | 28 | - name: Process asciidoc and javadoc 29 | run: ./mvnw -q exec:java@generate-micrometer-docs asciidoctor:process-asciidoc javadoc:javadoc 30 | 31 | # 32 | # construct a directory to be copied to "gh-pages" branch 33 | # target/deploy-documents/ -- map to "docs" dir in "gh-pages" 34 | # `-- -- e.g. "0.9.0.BUILD-SNAPSHOT" 35 | # `-- docs/html/ 36 | # `-- api/ 37 | # `-- CHANGELOG.txt 38 | # `-- current-snapshot -- for latest snapshot from main 39 | # `-- docs/html/ 40 | # `-- api/ 41 | # `-- CHANGELOG.txt 42 | # `-- current -- for latest release version 43 | # `-- docs/html/ 44 | # `-- api/ 45 | # `-- CHANGELOG.txt 46 | 47 | - name: Prepare "project-version" documents 48 | run: | 49 | mkdir -p target/deploy-documents/${{ env.project_version }}/docs/html 50 | mkdir -p target/deploy-documents/${{ env.project_version }}/api 51 | cp -Rf target/generated-docs/* target/deploy-documents/${{ env.project_version }}/docs/html/ 52 | cp -Rf target/site/apidocs/* target/deploy-documents/${{ env.project_version }}/api/ 53 | cp CHANGELOG target/deploy-documents/${{ env.project_version }}/CHANGELOG.txt 54 | 55 | - name: Prepare "current-snapshot" documents 56 | if: "github.ref == 'refs/heads/main' && contains(env.project_version, 'snapshot')" 57 | run: | 58 | mkdir -p target/deploy-documents/current-snapshot/docs/html 59 | mkdir -p target/deploy-documents/current-snapshot/api 60 | cp -Rf target/generated-docs/* target/deploy-documents/current-snapshot/docs/html/ 61 | cp -Rf target/site/apidocs/* target/deploy-documents/current-snapshot/api/ 62 | cp CHANGELOG target/deploy-documents/current-snapshot/CHANGELOG.txt 63 | 64 | - name: Prepare "current" documents 65 | if: "contains(env.project_version, 'release')" 66 | run: | 67 | mkdir -p target/deploy-documents/current/docs/html 68 | mkdir -p target/deploy-documents/current/api 69 | cp -Rf target/generated-docs/* target/deploy-documents/current/docs/html/ 70 | cp -Rf target/site/apidocs/* target/deploy-documents/current/api/ 71 | cp CHANGELOG target/deploy-documents/current/CHANGELOG.txt 72 | 73 | - name: Deploy documents 74 | uses: peaceiris/actions-gh-pages@v3 75 | with: 76 | github_token: ${{ secrets.GITHUB_TOKEN }} 77 | publish_branch: gh-pages 78 | publish_dir: target/deploy-documents 79 | destination_dir: docs 80 | keep_files: true 81 | full_commit_message: "Deploying documents(${{ env.project_version}}) to ${{ github.ref }} from ${{ github.repository }}@${{ github.sha }}" 82 | -------------------------------------------------------------------------------- /.github/workflows/pullrequests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Build Pull request with Maven 5 | 6 | on: [pull_request] 7 | 8 | jobs: 9 | pr-build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | include: 14 | - os: ubuntu-latest 15 | mvn: ./mvnw 16 | - os: windows-latest 17 | mvn: mvn 18 | - os: macos-latest 19 | mvn: ./mvnw 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Set up JDK 1.8 23 | uses: actions/setup-java@v3 24 | with: 25 | java-version: 8 26 | distribution: temurin 27 | cache: 'maven' 28 | - name: Build with Maven 29 | run: ${{ matrix.mvn }} -B verify 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Stage release to Maven Central 5 | 6 | on: 7 | push: 8 | branches: [ release-0.x ] 9 | 10 | jobs: 11 | release: 12 | if: github.repository == 'r2dbc/r2dbc-proxy' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up JDK 1.8 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: 8 20 | distribution: temurin 21 | cache: 'maven' 22 | - name: Initialize Maven Version 23 | run: ./mvnw -q org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version 24 | - name: GPG Check 25 | run: gpg -k 26 | - name: Release with Maven 27 | env: 28 | SONATYPE_USER: ${{ secrets.SONATYPE_USER }} 29 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 30 | GPG_KEY_BASE64: ${{ secrets.GPG_KEY_BASE64 }} 31 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 32 | run: ci/build-and-deploy-to-maven-central.sh 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Maven template 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | pom.xml.next 7 | release.properties 8 | dependency-reduced-pom.xml 9 | buildNumber.properties 10 | .mvn/timing.properties 11 | 12 | # IntelliJ 13 | out/ 14 | build/ 15 | .idea/ 16 | *.iml 17 | 18 | ### Java template 19 | # Compiled class file 20 | *.class 21 | 22 | # Log file 23 | *.log 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | .flattened-pom.xml 37 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | cache: 5 | directories: 6 | - $HOME/.m2 7 | notifications: 8 | slack: 9 | secure: QU4hnz7qoNW1B57WOv1VDWnjJRLQYV+sfBQvdQsrq89rAmDAX1fZptlQ/vPwwFz/X83VK0Ks3PY5WO4AGHWkKkqMcnOURQM31UJtEykS6EX0A7eivg5I6Q9stNEBSzyqK/qdDSqFUXp3i1Z9dk0hCnKURQ1Z5LpoIhnNsaQ+31/ZxrVqPTLqUlHkVUMKOS9J5YOxpjxl5S7L4vXLRCvn82/NzwZ0szZwQH29vkWy4j+eNyEC/Jc9TnHPdd+07mOq5Cl59c5UynJCFZ9M1r1C5/n0vNkRuUw15U1j6JKbr9pYVfNfAtMrOR4EDtOhtobUeXyUcd2TTqUByNCVpufb4iNABP9JyDeArRPw5K1C1YupfHuo1QwpAu6F5g68ru3/e9t/lwUFiv+5Vaj2hRwjrNXez9EViV1GeMm1ovvB/NFToHl6ckcpxPTlwYqyYrCgexRy6lCElP1dVU0icQL4nUcy4Fx726o4pqiSKFubbdNOQIVosyMgJktZlCEdnKK4yX2zgbbK7MNMRQnL3Nz/SjVD1UhEhR+e/nn4xtI6SDA+gxco1TPWl9VCWq4/dvrg7l+43eserdf/kCNZTHRZoZMI392oF6THfxQuRZOTjUD89nGq6Z3GL/WJdIsjOjg3upJN+Q5YgcNOus39m8WyPuva4o8yfyorx9E+cGkzE2c= 10 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Reactive Relational Database Connectivity 2 | 3 | Copyright 2017-2019 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /ci/build-and-deploy-to-maven-central.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | VERSION=$(./mvnw org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version -o | grep -v INFO) 6 | 7 | if [[ $VERSION =~ [^.*-SNAPSHOT$] ]] ; then 8 | 9 | echo "Cannot deploy a snapshot: $VERSION" 10 | exit 1 11 | fi 12 | 13 | if [[ $VERSION =~ [^(\d+\.)+(RC(\d+)|M(\d+)|RELEASE)$] ]] ; then 14 | 15 | # 16 | # Prepare GPG Key is expected to be in base64 17 | # Exported with gpg -a --export-secret-keys "your@email" | base64 > gpg.base64 18 | # 19 | printf "$GPG_KEY_BASE64" | base64 --decode > gpg.asc 20 | echo ${GPG_PASSPHRASE} | gpg --batch --yes --passphrase-fd 0 --import gpg.asc 21 | gpg -k 22 | 23 | # 24 | # Stage on Maven Central 25 | # 26 | echo "Staging $VERSION to Maven Central" 27 | 28 | ./mvnw \ 29 | -s settings.xml \ 30 | -Pcentral \ 31 | -Dmaven.test.skip=true \ 32 | -Dgpg.passphrase=${GPG_PASSPHRASE} \ 33 | clean deploy -B 34 | else 35 | 36 | echo "Not a release: $VERSION" 37 | exit 1 38 | fi 39 | 40 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | sonatype 8 | ${env.SONATYPE_USER} 9 | ${env.SONATYPE_PASSWORD} 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/asciidoc/docinfo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 22 | 23 | 68 | -------------------------------------------------------------------------------- /src/main/asciidoc/features.adoc: -------------------------------------------------------------------------------- 1 | [[features]] 2 | = Features 3 | 4 | [[features_accessing-reactor-context-values]] 5 | == Accessing reactor context values 6 | 7 | In the callbacks, `ValueSource` contains a reactor's `ContextView` object under `ContextView.class` key. 8 | This provides a readonly access to the reactor context. 9 | 10 | [source,java] 11 | ---- 12 | @Override 13 | public void beforeQuery(QueryExecutionInfo execInfo) { 14 | // query execution scoped context 15 | ContextView contextViewForQuery = execInfo.getValueStore().get(ContextView.class, ContextView.class); 16 | // connection scoped context 17 | ContextView contextViewForConnection = execInfo.getConnectionInfo().getValueStore().get(ContextView.class, ContextView.class); 18 | // ... 19 | } 20 | ---- 21 | 22 | === Available ContextView scopes 23 | 24 | While interacting with R2DBC APIs, there are multiple places that could put values into the reactor contexts. 25 | This example illustrates what context values are available in the corresponding callback APIs. 26 | 27 | [source,java] 28 | ---- 29 | Mono.from(connectionFactory.create()).contextWrite(context -> context.put("foo", "FOO")) // <1> 30 | .flatMapMany(connection -> Mono.from(connection 31 | .createStatement("SELECT id, name FROM emp WHERE id = ?") 32 | .bind(0, 20) 33 | .execute()).contextWrite(context -> context.put("bar", "BAR"))) // <2> 34 | .flatMap(result -> Mono.from(result 35 | .map((row, rowMetadata) -> row.get("name", String.class))) 36 | .contextWrite(context -> context.put("qux", "QUX")) // <3> 37 | ) 38 | .contextWrite(context -> context.put("baz", "BAZ")) // <4> 39 | ... 40 | ---- 41 | 42 | <1> Context for connection 43 | <2> Context for query execution 44 | <3> Context for result-set retrieval 45 | <4> Context for the entire flow 46 | 47 | *BindParameterConverter#onCreateStatement* 48 | 49 | * `StatementInfo#getValueStore` has no `ContextView` 50 | * `ConnectionInfo#getValueStore` has a `ContextView` that holds keys - `foo` and `baz` 51 | 52 | *BindParameterConverter#onBind* 53 | 54 | * `StatementInfo#getValueStore` has no `ContextView` 55 | * `ConnectionInfo#getValueStore` has a `ContextView` that holds keys - `foo` and `baz` 56 | 57 | *ProxyExecutionListener#[before|after]Query* 58 | 59 | * `QueryExecutionInfo#getValueStore` has a `ContextView` that holds keys - `bar` and `baz` 60 | * `ConnectionInfo#getValueStore` has a `ContextView` that holds keys - `foo` and `baz` 61 | 62 | *ProxyExecutionListener#[before|after]Method* 63 | 64 | * `MethodExecutionInfo#getValueStore` for `create` has a `ContextView` that holds keys - `foo` and `baz` 65 | * `MethodExecutionInfo#getValueStore` for `createStatement` has no `ContextView` 66 | * `MethodExecutionInfo#getValueStore` for `bind` has no `ContextView` 67 | * `MethodExecutionInfo#getValueStore` for `execute` has a `ContextView` that holds keys - `bar` and `baz` 68 | * `MethodExecutionInfo#getValueStore` for `map` has a `ContextView` that holds keys - `baz` and `qux` 69 | * `MethodExecutionInfo#getValueStore` for `get` has no `ContextView` 70 | * `ConnectionInfo#getValueStore` for all methods(`create`, `createStatement`, `bind`, `execute`, `map`, `get`) have a `ContextView` that holds keys - `foo` and `baz` 71 | 72 | [[features_micrometer-observation-support]] 73 | == Micrometer Observation support 74 | R2DBC Proxy has an optional dependency on Micrometer to provide https://micrometer.io/docs/observation[Micrometer Observation] support. 75 | The `ObservationProxyExecutionListener` creates observations for query executions. 76 | 77 | .Configuration Sample 78 | [source,java] 79 | ---- 80 | ConnectionFactory connectionFactory = ... 81 | ObservationRegistry observationRegistry = ... 82 | String r2dbcUrl = ... 83 | 84 | ObservationProxyExecutionListener listener = new ObservationProxyExecutionListener( 85 | observationRegistry, connectionFactory, r2dbcUrl); 86 | listener.setIncludeParameterValues(true); // to include binding params (default is false) 87 | 88 | ConnectionFactory proxyConnectionFactory = ProxyConnectionFactory.builder(connectionFactory) 89 | .listener(listener).build(); 90 | ---- 91 | 92 | All https://micrometer.io/docs/observation[Micrometer Observation] implementation classes are located under the `io.r2dbc.proxy.observation` package. 93 | Since the dependency is optional, users need to explicit add the `micrometer-observation` dependency. 94 | 95 | For Spring Boot 3, a separate project, _TBD_, provides auto-configuration for this. 96 | 97 | include::../../../target/observation-docs//_conventions.adoc[] 98 | [IMPORTANT] 99 | .Virtual Thread Environment 100 | ==== 101 | The default `QueryObservationConvention` includes the thread name as a low-cardinality tag in spans. 102 | 103 | In a virtual thread environment, each thread has a unique name. 104 | As a result, tagging thread names as a low-cardinality key-value can lead to cardinality explosion in tracing backends. 105 | 106 | To avoid this, use `VirtualThreadQueryObservationConvention`, a subclass of `QueryObservationConvention`, which omits the thread name tag. 107 | This convention is recommended when running in a virtual thread environment. 108 | ==== 109 | 110 | include::../../../target/observation-docs/_metrics.adoc[] 111 | include::../../../target/observation-docs/_spans.adoc[] 112 | 113 | -------------------------------------------------------------------------------- /src/main/asciidoc/getting-started.adoc: -------------------------------------------------------------------------------- 1 | [[getting-started]] 2 | = Getting Started 3 | 4 | [[getting-started_dependencies]] 5 | == Dependencies 6 | 7 | Artifacts are available on https://search.maven.org/search?q=r2dbc-proxy[Maven Central]: 8 | 9 | [source,xml] 10 | ---- 11 | 12 | io.r2dbc 13 | r2dbc-proxy 14 | ${version} 15 | 16 | ---- 17 | 18 | If you'd rather like the latest snapshots of the upcoming major version, use the Maven snapshot repository and declare the appropriate dependency version. 19 | 20 | [source,xml] 21 | ---- 22 | 23 | io.r2dbc 24 | r2dbc-proxy 25 | ${version}.BUILD-SNAPSHOT 26 | 27 | 28 | 29 | sonatype-nexus-snapshots 30 | Sonatype OSS Snapshot Repository 31 | https://oss.sonatype.org/content/repositories/snapshots 32 | 33 | ---- 34 | 35 | [[getting-started_native-image]] 36 | == Native Image Support 37 | 38 | R2DBC Proxy supports https://www.graalvm.org/reference-manual/native-image/[GraalVM native-image]. 39 | The proxy creation uses the JDK dynamic proxy by default. 40 | They need a configuration in the native image environment. 41 | 42 | R2DBC proxy jar ships with the following configuration files: 43 | 44 | * `META-INF/native-image/io.r2dbc/r2dbc-proxy/native-image.properties` 45 | * `META-INF/native-image/io.r2dbc/r2dbc-proxy/proxy-config.json` 46 | 47 | The native image build automatically detects these https://www.graalvm.org/reference-manual/native-image/BuildConfiguration/#embedding-a-configuration-file[embedded configuration files] and sets up the dynamic proxy usage for R2DBC Proxy. 48 | -------------------------------------------------------------------------------- /src/main/asciidoc/images/metrics-actuator-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/src/main/asciidoc/images/metrics-actuator-connection.png -------------------------------------------------------------------------------- /src/main/asciidoc/images/metrics-jmx-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/src/main/asciidoc/images/metrics-jmx-connection.png -------------------------------------------------------------------------------- /src/main/asciidoc/images/metrics-jmx-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/src/main/asciidoc/images/metrics-jmx-query.png -------------------------------------------------------------------------------- /src/main/asciidoc/images/r2dbc-proxy-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/src/main/asciidoc/images/r2dbc-proxy-diagram.png -------------------------------------------------------------------------------- /src/main/asciidoc/images/r2dbc-proxy-diagram2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/src/main/asciidoc/images/r2dbc-proxy-diagram2.png -------------------------------------------------------------------------------- /src/main/asciidoc/images/zipkin-span-batch-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/src/main/asciidoc/images/zipkin-span-batch-query.png -------------------------------------------------------------------------------- /src/main/asciidoc/images/zipkin-span-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/src/main/asciidoc/images/zipkin-span-connection.png -------------------------------------------------------------------------------- /src/main/asciidoc/images/zipkin-tracing-rollback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r2dbc/r2dbc-proxy/2e88d0206c5918684727b147450e8be662c10883/src/main/asciidoc/images/zipkin-tracing-rollback.png -------------------------------------------------------------------------------- /src/main/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = R2DBC Proxy - R2DBC Proxying Framework 2 | :author: Tadaya Tsuyukubo 3 | :revnumber: {version} 4 | :revdate: {localdate} 5 | ifdef::backend-pdf[] 6 | :pagenums: 7 | :toc: 8 | endif::[] 9 | 10 | (C) 2017-2022 The original authors. 11 | 12 | NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. 13 | 14 | toc::[] 15 | 16 | include::introduction.adoc[leveloffset=+1] 17 | include::overview.adoc[leveloffset=+1] 18 | include::getting-started.adoc[leveloffset=+1] 19 | include::setup.adoc[leveloffset=+1] 20 | include::components.adoc[leveloffset=+1] 21 | include::use-cases.adoc[leveloffset=+1] 22 | include::features.adoc[leveloffset=+1] 23 | -------------------------------------------------------------------------------- /src/main/asciidoc/introduction.adoc: -------------------------------------------------------------------------------- 1 | [[introduction]] 2 | = Introduction 3 | 4 | R2DBC Proxy is a proxy framework providing callbacks for query executions, 5 | method invocations, and parameter bindings. 6 | 7 | The proxy holds proxy listeners. 8 | When a caller(application or upper layer library) interacts with the proxy, the registered 9 | proxy listeners receive callbacks. 10 | 11 | Followings are the sample usages of the proxy listeners: 12 | 13 | - Logging on each query execution 14 | - Detect slow queries 15 | - Method tracing 16 | - Metrics 17 | - Distributed tracing 18 | - Assertion and verification 19 | - Own action 20 | 21 | The proxy is a thin transparent layer which suits to implement cross-cutting concerns, agnostic to 22 | the underlying implementation such as drivers; yet, it is viewed as R2DBC SPI from the above layer. 23 | 24 | image::images/r2dbc-proxy-diagram.png[R2DBC Proxy diagram] 25 | 26 | 27 | [[introduction_project-metadata]] 28 | == Project Metadata 29 | 30 | * Version control: https://github.com/r2dbc/r2dbc-proxy 31 | * Issue tracker: https://github.com/r2dbc/r2dbc-proxy/issues 32 | * Release repository: https://repo1.maven.org/maven2 33 | * Snapshot repository: https://oss.sonatype.org/content/repositories/snapshots 34 | -------------------------------------------------------------------------------- /src/main/asciidoc/overview.adoc: -------------------------------------------------------------------------------- 1 | [[overview]] 2 | = Overview 3 | 4 | R2DBC Proxy is a framework that generates proxies to R2DBC SPI objects via `ProxyConnectionFactory` 5 | and provides callbacks to the method and query invocations. 6 | 7 | R2DBC Proxy is a thin transparent layer. 8 | From callers(application or another library), proxies are simply viewed as R2DBC SPI objects. 9 | This is similar to how connection pooling is viewed from upper layer. 10 | 11 | This section describes the key concepts of the R2DBC Proxy: 12 | 13 | - <> 14 | - <> 15 | - <> 16 | - <> 17 | 18 | 19 | [[overview_proxies]] 20 | == Proxies 21 | 22 | Each SPI object(`Connection`, `Statement`, `Batch`, `Result`, and `Row`) created by `ProxyConnectionFactory` 23 | is wrapped by a proxy. 24 | 25 | When a caller invokes a method on the proxy, it triggers corresponding callback methods on the registered listeners. 26 | 27 | 28 | [[overview_listeners]] 29 | == Listeners 30 | 31 | Listeners are the callback implementation. 32 | Each listener implements the `ProxyExecutionListener` interface, which defines 33 | `[before|after]Query`, `[before|after]Method`, and `eachQueryResult` methods. 34 | 35 | When triggered, these callback methods receive contextual information as a parameter. 36 | The `MethodExecutionInfo` is a parameter that contains the information about the invoked method, and the 37 | `QueryExecutionInfo` holds the information of the query execution. 38 | 39 | Users would write a listener implementation to perform custom actions. 40 | 41 | 42 | [[overview_formatters]] 43 | == Formatters 44 | 45 | One of the main action performed by a listener is logging. 46 | When a listener received a callback, the passed contextual information 47 | requires a transformation to a `String` in order to be an entry for the logging. 48 | 49 | Formatters are utility classes that covert `MethodExecutionInfo` and `QueryExecutionInfo` to `String`. 50 | 51 | `QueryExecutionInfoFormatter` and `MethodExecutionInfoFormatter` are available out of the box 52 | to transform `QueryExecutionInfo` and `MethodExecutionInfo`, respectively. 53 | 54 | [[overview_converters]] 55 | == Converters 56 | 57 | Converters are the callbacks that could alter the original invocation. 58 | 59 | Currently, `BindParameterConverter` and `ResultRowConverter` are available. 60 | They represent bind operations(`Statement#[bind|bindNull]`) and result row get operations(`Row#get`), respectively. 61 | The converters can change the result, call alternative methods, or even not invoke the originally 62 | called method. 63 | -------------------------------------------------------------------------------- /src/main/asciidoc/setup.adoc: -------------------------------------------------------------------------------- 1 | [[setup]] 2 | = Setup 3 | 4 | To obtain a proxy `ConnectionFactory`, R2DBC Proxy provides two mechanisms: 5 | 6 | - <> 7 | - <> 8 | 9 | [[setup_connection-factory-discovery]] 10 | == Connection Factory Discovery 11 | 12 | R2DBC specifies two types of connection factory discovery, <> and 13 | <>. 14 | R2DBC Proxy supports both with `proxy` as the driver identifier. 15 | 16 | [[setup_connection-factory-discovery_url-based]] 17 | === URL-based 18 | 19 | When a connection URL contains `proxy` in its driver segment, `ConnectionFactories` 20 | delegates the `protocol` segment(required) to the another connection 21 | discovery.(pass-through) Then, it wraps the returned `ConnectionFactory` with a proxy. 22 | 23 | [source] 24 | ---- 25 | r2dbc:proxy:://:/[?proxyListener=] 26 | ---- 27 | 28 | 29 | [source] 30 | .Examples 31 | ---- 32 | # with driver 33 | r2dbc:proxy:postgresql://localhost:5432/myDB?proxyListener=com.example.MyListener 34 | 35 | # with pooling 36 | r2dbc:proxy:pool:postgresql://localhost:5432/myDB?proxyListener=com.example.MyListener&maxIdleTime=PT60S 37 | ---- 38 | 39 | [[setup_connection-factory-discovery_programmatic]] 40 | === Programmatic 41 | 42 | Another variant of the `ConnectionFactory` discovery is a programmatic creation of the `ConnectionFactoryOptions`. 43 | 44 | Same as URL based discovery, when the DRIVER Option is `proxy`, it delegates PROTOCOL Option(required) 45 | to another connection factory discovery request. The result of delegated discovery 46 | request is wrapped by a proxy. 47 | 48 | [source,java] 49 | ---- 50 | ConnectionFactory connectionFactory = ConnectionFactories.get(ConnectionFactoryOptions.builder() 51 | .option(ConnectionFactoryOptions.DRIVER, "proxy") 52 | .option(ConnectionFactoryOptions.PROTOCOL, "postgresql") 53 | .option(ConnectionFactoryOptions.HOST, "localhost") 54 | .option(ConnectionFactoryOptions.PORT, 5432) 55 | .option(ConnectionFactoryOptions.DATABASE, "myDB") 56 | .option(ProxyConnectionFactoryProvider.PROXY_LISTENERS, myListener) 57 | .build()); 58 | 59 | Mono connection = connectionFactory.create(); 60 | ---- 61 | 62 | .Supported Connection Factory Discovery options 63 | |=== 64 | | Option | Description 65 | 66 | | `driver` | Must be `proxy` 67 | | `protocol` | Delegating connection factory driver 68 | | `proxyListener` | Comma separated list of fully qualified proxy listener class names _(Optional)_ 69 | |=== 70 | 71 | When programmatically construct `ConnectionFactoryOptions`, in addition to the "comma separated listener class FQDN", 72 | `proxyListener` option allows following values: 73 | 74 | - Proxy listener class (java `Class` object) 75 | - Proxy listener instance 76 | - `Collection` of above 77 | 78 | 79 | [[setup_programmatic-proxy-construction]] 80 | == Programmatic Proxy Construction 81 | 82 | `ProxyConnectionFactory` provides a builder to construct a proxy `ConnectionFactory`. 83 | The builder has methods to register concrete and adhoc listeners. 84 | 85 | [source,java] 86 | ---- 87 | ConnectionFactory original = ... 88 | 89 | ConnectionFactory connectionFactory = ProxyConnectionFactory.builder(original) 90 | .onAfterQuery(queryInfo -> 91 | ... // after query callback logic 92 | ) 93 | .onBeforeMethod(methodInfo -> 94 | ... // before method callback logic 95 | ) 96 | .listener(...) // add listener 97 | .build(); 98 | 99 | ---- 100 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/AfterQueryCallbackInvoker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.core.ProxyEventType; 20 | import io.r2dbc.proxy.core.QueryExecutionInfo; 21 | import io.r2dbc.proxy.listener.ProxyExecutionListener; 22 | 23 | /** 24 | * Invoke {@link ProxyExecutionListener#afterQuery(QueryExecutionInfo)} callback. 25 | *

26 | * Extracted the logic to call "afterQuery" callback method. 27 | * This is because gh-94 exhibits the need to put afterQuery callback invocation 28 | * in both {@link QueryInvocationSubscriber} and {@link ResultInvocationSubscriber}. 29 | * 30 | * @author Tadaya Tsuyukubo 31 | * @see QueryInvocationSubscriber 32 | * @see ResultCallbackHandler 33 | */ 34 | class AfterQueryCallbackInvoker { 35 | 36 | private final MutableQueryExecutionInfo executionInfo; 37 | 38 | private final QueriesExecutionContext queriesExecutionContext; 39 | 40 | private final ProxyExecutionListener listener; 41 | 42 | public AfterQueryCallbackInvoker(MutableQueryExecutionInfo executionInfo, QueriesExecutionContext queriesExecutionContext, ProxyExecutionListener listener) { 43 | this.executionInfo = executionInfo; 44 | this.queriesExecutionContext = queriesExecutionContext; 45 | this.listener = listener; 46 | } 47 | 48 | public void afterQuery() { 49 | this.executionInfo.setExecuteDuration(this.queriesExecutionContext.getElapsedDuration()); 50 | this.executionInfo.setThreadName(Thread.currentThread().getName()); 51 | this.executionInfo.setThreadId(Thread.currentThread().getId()); 52 | this.executionInfo.setCurrentMappedResult(null); 53 | this.executionInfo.setProxyEventType(ProxyEventType.AFTER_QUERY); 54 | 55 | this.listener.afterQuery(this.executionInfo); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/BatchCallbackHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2023 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.core.ConnectionInfo; 20 | import io.r2dbc.proxy.core.ExecutionType; 21 | import io.r2dbc.proxy.core.QueryInfo; 22 | import io.r2dbc.proxy.util.Assert; 23 | import io.r2dbc.spi.Batch; 24 | import io.r2dbc.spi.Result; 25 | import org.reactivestreams.Publisher; 26 | import reactor.util.annotation.Nullable; 27 | 28 | import java.lang.reflect.Method; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | import static java.util.stream.Collectors.toList; 33 | 34 | /** 35 | * Proxy callback handler for {@link Batch}. 36 | * 37 | * @author Tadaya Tsuyukubo 38 | */ 39 | public final class BatchCallbackHandler extends CallbackHandlerSupport { 40 | 41 | private final Batch batch; 42 | 43 | private final ConnectionInfo connectionInfo; 44 | 45 | private final List queries = new ArrayList<>(); 46 | 47 | public BatchCallbackHandler(Batch batch, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) { 48 | super(proxyConfig); 49 | this.batch = Assert.requireNonNull(batch, "batch must not be null"); 50 | this.connectionInfo = Assert.requireNonNull(connectionInfo, "connectionInfo must not be null"); 51 | } 52 | 53 | @Override 54 | @SuppressWarnings("unchecked") 55 | public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { 56 | Assert.requireNonNull(proxy, "proxy must not be null"); 57 | Assert.requireNonNull(method, "method must not be null"); 58 | 59 | String methodName = method.getName(); 60 | 61 | if (isCommonMethod(methodName)) { 62 | return handleCommonMethod(methodName, this.batch, args, this.connectionInfo.getOriginalConnection()); 63 | } 64 | 65 | Object result = proceedExecution(method, this.batch, args, this.proxyConfig.getListeners(), this.connectionInfo, null); 66 | 67 | if ("add".equals(methodName)) { 68 | this.queries.add((String) args[0]); 69 | return proxy; 70 | } else if ("execute".equals(methodName)) { 71 | 72 | List queryInfoList = this.queries.stream() 73 | .map(QueryInfo::new) 74 | .collect(toList()); 75 | 76 | MutableQueryExecutionInfo execInfo = new MutableQueryExecutionInfo(); 77 | execInfo.setType(ExecutionType.BATCH); 78 | execInfo.setQueries(queryInfoList); 79 | execInfo.setBatchSize(this.queries.size()); 80 | execInfo.setMethod(method); 81 | execInfo.setMethodArgs(args); 82 | execInfo.setConnectionInfo(this.connectionInfo); 83 | 84 | // API defines "execute()" returns a publisher 85 | Publisher publisher = (Publisher) result; 86 | 87 | return interceptQueryExecution(publisher, execInfo); 88 | } 89 | 90 | return result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/CallbackHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.callback; 19 | 20 | import reactor.util.annotation.Nullable; 21 | 22 | import java.lang.reflect.Method; 23 | 24 | /** 25 | * Callback logic for proxy invocation. 26 | * 27 | * The logic is separated from proxy handler to implementation of this interface 28 | * in order to be reused in different proxy mechanism (JDK dynamic proxy, cglib, etc). 29 | * 30 | * @author Tadaya Tsuyukubo 31 | */ 32 | public interface CallbackHandler { 33 | 34 | /** 35 | * When proxy is invoked, actual implementation of the proxy handler delegates the 36 | * invocation to this method. 37 | * 38 | * @param proxy the proxy instance that the method was invoked on 39 | * @param method the method that has invoked on the proxy instance 40 | * @param args an array of objects that has passed to the method invocation. 41 | * this can be {@code null} when method is invoked with no argument. 42 | * @return result returned from the method invocation on the proxy instance. (can be {@code null}.) 43 | * @throws Throwable the exception thrown from the method invocation on the proxy instance. 44 | * @throws IllegalArgumentException if {@code proxy} is {@code null} 45 | * @throws IllegalArgumentException if {@code method} is {@code null} 46 | */ 47 | @Nullable 48 | Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/ConnectionCallbackHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2023 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.core.ConnectionInfo; 20 | import io.r2dbc.proxy.core.MethodExecutionInfo; 21 | import io.r2dbc.proxy.util.Assert; 22 | import io.r2dbc.spi.Batch; 23 | import io.r2dbc.spi.Connection; 24 | import io.r2dbc.spi.Statement; 25 | import reactor.util.annotation.Nullable; 26 | 27 | import java.lang.reflect.Method; 28 | import java.util.function.Consumer; 29 | 30 | /** 31 | * Proxy callback handler for {@link Connection}. 32 | * 33 | * @author Tadaya Tsuyukubo 34 | */ 35 | public final class ConnectionCallbackHandler extends CallbackHandlerSupport { 36 | 37 | private final Connection connection; 38 | 39 | private final ConnectionInfo connectionInfo; 40 | 41 | public ConnectionCallbackHandler(Connection connection, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) { 42 | super(proxyConfig); 43 | this.connection = Assert.requireNonNull(connection, "connection must not be null"); 44 | this.connectionInfo = Assert.requireNonNull(connectionInfo, "connectionInfo must not be null"); 45 | } 46 | 47 | @Override 48 | public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { 49 | Assert.requireNonNull(proxy, "proxy must not be null"); 50 | Assert.requireNonNull(method, "method must not be null"); 51 | 52 | String methodName = method.getName(); 53 | if (isCommonMethod(methodName)) { 54 | return handleCommonMethod(methodName, this.connection, args, this.connection); 55 | } 56 | 57 | Consumer onComplete = null; 58 | 59 | // since these methods return Publisher pass the callback for doOnComplete(). 60 | if ("beginTransaction".equals(methodName)) { 61 | onComplete = executionInfo -> { 62 | executionInfo.getConnectionInfo().incrementTransactionCount(); 63 | }; 64 | } else if ("commitTransaction".equals(methodName)) { 65 | onComplete = executionInfo -> { 66 | executionInfo.getConnectionInfo().incrementCommitCount(); 67 | }; 68 | } else if ("rollbackTransaction".equals(methodName)) { 69 | onComplete = executionInfo -> { 70 | executionInfo.getConnectionInfo().incrementRollbackCount(); 71 | }; 72 | } else if ("close".equals(methodName)) { 73 | onComplete = executionInfo -> { 74 | executionInfo.getConnectionInfo().setClosed(true); 75 | }; 76 | } 77 | 78 | if ("createStatement".equals(methodName)) { 79 | String query = (String) args[0]; 80 | 81 | // create a value store 82 | MutableStatementInfo statementInfo = new MutableStatementInfo(); 83 | statementInfo.setConnectionInfo(this.connectionInfo); 84 | statementInfo.setOriginalQuery(query); 85 | 86 | String updatedQuery = this.proxyConfig.getBindParameterConverter().onCreateStatement(query, statementInfo); 87 | statementInfo.setUpdatedQuery(updatedQuery); 88 | 89 | // replace the query 90 | args[0] = updatedQuery; 91 | Object result = proceedExecution(method, this.connection, args, this.proxyConfig.getListeners(), this.connectionInfo, null); 92 | 93 | return this.proxyConfig.getProxyFactory().wrapStatement((Statement) result, statementInfo, this.connectionInfo); 94 | } 95 | // TODO: createSavepoint, releaseSavepoint, rollbackTransactionToSavepoint 96 | 97 | Object result = proceedExecution(method, this.connection, args, this.proxyConfig.getListeners(), this.connectionInfo, onComplete); 98 | 99 | if ("createBatch".equals(methodName)) { 100 | return this.proxyConfig.getProxyFactory().wrapBatch((Batch) result, this.connectionInfo); 101 | } 102 | 103 | return result; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/ConnectionFactoryCreateMethodInvocationSubscriber.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.spi.ConnectionFactory; 20 | import reactor.core.CoreSubscriber; 21 | import reactor.util.context.ContextView; 22 | 23 | /** 24 | * Special subscriber for {@link ConnectionFactory#create()} to invoke before/after 25 | * method callbacks. 26 | * 27 | * @author Tadaya Tsuyukubo 28 | * @see MethodInvocationSubscriber 29 | * @see ConnectionFactoryCallbackHandler 30 | */ 31 | class ConnectionFactoryCreateMethodInvocationSubscriber extends MethodInvocationSubscriber { 32 | 33 | public ConnectionFactoryCreateMethodInvocationSubscriber(CoreSubscriber delegate, MutableMethodExecutionInfo executionInfo, ProxyConfig proxyConfig) { 34 | super(delegate, executionInfo, proxyConfig, null); 35 | } 36 | 37 | @Override 38 | public void onComplete() { 39 | // "doOnSuccess()" chained to this operator calls "afterMethod()" callback. 40 | // Therefore, do not call "afterMethod()" on "onComplete()" here. 41 | // see "ConnectionFactoryCallbackHandler" and how it handles "create" method. 42 | this.delegate.onComplete(); 43 | } 44 | 45 | @Override 46 | protected void beforeMethod() { 47 | // register reactor context as read only at connection-info level 48 | this.executionInfo.getConnectionInfo().getValueStore().put(ContextView.class, new DelegatingContextView(currentContext())); 49 | super.beforeMethod(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/ConnectionHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.spi.Connection; 20 | 21 | /** 22 | * Provide methods to retrieve original {@link Connection} from proxy object. 23 | * 24 | * @author Tadaya Tsuyukubo 25 | * @see Connection 26 | * @see ProxyUtils 27 | */ 28 | public interface ConnectionHolder { 29 | 30 | /** 31 | * Retrieve original {@link Connection}. 32 | * 33 | * @return original connection 34 | */ 35 | Connection unwrapConnection(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/ConnectionIdManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.spi.Connection; 20 | 21 | /** 22 | * {@link Connection} ID manager interface. 23 | * 24 | * @author Tadaya Tsuyukubo 25 | */ 26 | public interface ConnectionIdManager { 27 | 28 | /** 29 | * Create a default {@link ConnectionIdManager}. 30 | * 31 | * @return default connectionIdManager 32 | */ 33 | static ConnectionIdManager create() { 34 | return new DefaultConnectionIdManager(); 35 | } 36 | 37 | /** 38 | * Get ID for the input {@link Connection}. 39 | * 40 | * @param connection connection 41 | * @return ID 42 | * @throws IllegalArgumentException if {@code connection} is {@code null} 43 | */ 44 | String getId(Connection connection); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/DefaultConnectionIdManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.util.Assert; 20 | import io.r2dbc.spi.Connection; 21 | 22 | import java.util.concurrent.atomic.AtomicLongFieldUpdater; 23 | 24 | /** 25 | * Default implementation of {@link ConnectionIdManager}. 26 | * 27 | * This implementation uses increasing long as connection id. 28 | * 29 | * @author Tadaya Tsuyukubo 30 | */ 31 | final class DefaultConnectionIdManager implements ConnectionIdManager { 32 | 33 | private static final AtomicLongFieldUpdater ID_COUNT_INCREMENTER = 34 | AtomicLongFieldUpdater.newUpdater(DefaultConnectionIdManager.class, "idCount"); 35 | 36 | // access via ID_COUNT_INCREMENTER 37 | private volatile long idCount = 0; 38 | 39 | @Override 40 | public String getId(Connection connection) { 41 | Assert.requireNonNull(connection, "connection must not be null"); 42 | 43 | return String.valueOf(ID_COUNT_INCREMENTER.incrementAndGet(this)); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/DefaultConnectionInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.callback; 19 | 20 | import io.r2dbc.proxy.core.ConnectionInfo; 21 | import io.r2dbc.proxy.core.ValueStore; 22 | import io.r2dbc.proxy.util.Assert; 23 | import io.r2dbc.spi.Connection; 24 | import reactor.util.annotation.Nullable; 25 | 26 | import java.util.concurrent.atomic.AtomicBoolean; 27 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 28 | 29 | /** 30 | * Default implementation for {@link ConnectionInfo}. 31 | * 32 | * @author Tadaya Tsuyukubo 33 | */ 34 | final class DefaultConnectionInfo implements ConnectionInfo { 35 | 36 | private static final AtomicIntegerFieldUpdater TRANSACTION_COUNT_INCREMENTER = 37 | AtomicIntegerFieldUpdater.newUpdater(DefaultConnectionInfo.class, "transactionCount"); 38 | 39 | private static final AtomicIntegerFieldUpdater COMMIT_COUNT_INCREMENTER = 40 | AtomicIntegerFieldUpdater.newUpdater(DefaultConnectionInfo.class, "commitCount"); 41 | 42 | private static final AtomicIntegerFieldUpdater ROLLBACK_COUNT_INCREMENTER = 43 | AtomicIntegerFieldUpdater.newUpdater(DefaultConnectionInfo.class, "rollbackCount"); 44 | 45 | 46 | private Connection originalConnection; 47 | 48 | private String connectionId; 49 | 50 | private AtomicBoolean isClosed = new AtomicBoolean(); 51 | 52 | // access via TRANSACTION_COUNT_INCREMENTER 53 | private volatile int transactionCount = 0; 54 | 55 | // access via COMMIT_COUNT_INCREMENTER 56 | private volatile int commitCount = 0; 57 | 58 | // access via ROLLBACK_COUNT_INCREMENTER 59 | private volatile int rollbackCount = 0; 60 | 61 | private ValueStore valueStore = ValueStore.create(); 62 | 63 | // TODO: may keep transaction isolation level 64 | 65 | /** 66 | * Set original {@link Connection}. 67 | * 68 | * @param originalConnection original connection 69 | * @throws IllegalArgumentException if {@code originalConnection} is {@code null} 70 | */ 71 | public void setOriginalConnection(Connection originalConnection) { 72 | Assert.requireNonNull(originalConnection, "originalConnection must not be null"); 73 | 74 | this.originalConnection = originalConnection; 75 | } 76 | 77 | /** 78 | * Set connection ID. 79 | * 80 | * @param connectionId connection ID 81 | * @throws IllegalArgumentException if {@code connectionId} is {@code null} 82 | */ 83 | public void setConnectionId(String connectionId) { 84 | Assert.requireNonNull(connectionId, "connectionId must not be null"); 85 | 86 | this.connectionId = connectionId; 87 | } 88 | 89 | @Override 90 | public void setClosed(boolean closed) { 91 | this.isClosed.set(closed); 92 | } 93 | 94 | @Override 95 | @Nullable 96 | public Connection getOriginalConnection() { 97 | return this.originalConnection; 98 | } 99 | 100 | @Override 101 | @Nullable 102 | public String getConnectionId() { 103 | return this.connectionId; 104 | } 105 | 106 | @Override 107 | public void incrementTransactionCount() { 108 | TRANSACTION_COUNT_INCREMENTER.incrementAndGet(this); 109 | } 110 | 111 | @Override 112 | public void incrementCommitCount() { 113 | COMMIT_COUNT_INCREMENTER.incrementAndGet(this); 114 | } 115 | 116 | @Override 117 | public void incrementRollbackCount() { 118 | ROLLBACK_COUNT_INCREMENTER.incrementAndGet(this); 119 | } 120 | 121 | @Override 122 | public int getTransactionCount() { 123 | return this.transactionCount; 124 | } 125 | 126 | @Override 127 | public int getCommitCount() { 128 | return this.commitCount; 129 | } 130 | 131 | @Override 132 | public int getRollbackCount() { 133 | return this.rollbackCount; 134 | } 135 | 136 | @Override 137 | public boolean isClosed() { 138 | return this.isClosed.get(); 139 | } 140 | 141 | @Override 142 | public ValueStore getValueStore() { 143 | return this.valueStore; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/DelegatingContextView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import reactor.util.context.ContextView; 20 | 21 | import java.util.Map; 22 | import java.util.stream.Stream; 23 | 24 | /** 25 | * {@link ContextView} implementation that delegates to the given {@link ContextView} in constructor. 26 | * 27 | * @author Tadaya Tsuyukubo 28 | * @since 1.0.1 29 | */ 30 | public class DelegatingContextView implements ContextView { 31 | 32 | private final ContextView delegate; 33 | 34 | public DelegatingContextView(ContextView delegate) { 35 | this.delegate = delegate; 36 | } 37 | 38 | @Override 39 | public T get(Object key) { 40 | return this.delegate.get(key); 41 | } 42 | 43 | @Override 44 | public boolean hasKey(Object key) { 45 | return this.delegate.hasKey(key); 46 | } 47 | 48 | @Override 49 | public int size() { 50 | return this.delegate.size(); 51 | } 52 | 53 | @Override 54 | public Stream> stream() { 55 | return this.delegate.stream(); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "DelegatingContextView{" + delegate + '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/JdkProxyFactoryFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.callback; 19 | 20 | import io.r2dbc.proxy.util.Assert; 21 | 22 | /** 23 | * Factory to create a {@link JdkProxyFactory}. 24 | * 25 | * @author Tadaya Tsuyukubo 26 | */ 27 | final class JdkProxyFactoryFactory implements ProxyFactoryFactory { 28 | 29 | @Override 30 | public ProxyFactory create(ProxyConfig proxyConfig) { 31 | Assert.requireNonNull(proxyConfig, "proxyConfig must not be null"); 32 | 33 | return new JdkProxyFactory(proxyConfig); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/MutableBindInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.callback; 19 | 20 | import io.r2dbc.proxy.core.BindInfo; 21 | import io.r2dbc.proxy.core.Binding; 22 | import io.r2dbc.proxy.core.StatementInfo; 23 | 24 | /** 25 | * @author Tadaya Tsuyukubo 26 | */ 27 | final class MutableBindInfo implements BindInfo { 28 | 29 | private StatementInfo statementInfo; 30 | 31 | private Binding binding; 32 | 33 | @Override 34 | public StatementInfo getStatementInfo() { 35 | return this.statementInfo; 36 | } 37 | 38 | public void setStatementInfo(StatementInfo statementInfo) { 39 | this.statementInfo = statementInfo; 40 | } 41 | 42 | @Override 43 | public Binding getBinding() { 44 | return this.binding; 45 | } 46 | 47 | public void setBinding(Binding binding) { 48 | this.binding = binding; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/MutableMethodExecutionInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.callback; 19 | 20 | import io.r2dbc.proxy.core.ConnectionInfo; 21 | import io.r2dbc.proxy.core.MethodExecutionInfo; 22 | import io.r2dbc.proxy.core.ProxyEventType; 23 | import io.r2dbc.proxy.core.ValueStore; 24 | import reactor.util.annotation.Nullable; 25 | 26 | import java.lang.reflect.Method; 27 | import java.time.Duration; 28 | 29 | /** 30 | * Default implementation of the {@link MethodExecutionInfo}. 31 | * 32 | * @author Tadaya Tsuyukubo 33 | */ 34 | final class MutableMethodExecutionInfo implements MethodExecutionInfo { 35 | 36 | private Object target; 37 | 38 | private Method method; 39 | 40 | @Nullable 41 | private Object[] methodArgs; 42 | 43 | @Nullable 44 | private Object result; 45 | 46 | @Nullable 47 | private Throwable thrown; 48 | 49 | @Nullable 50 | private ConnectionInfo connectionInfo; 51 | 52 | private Duration executeDuration = Duration.ZERO; 53 | 54 | private String threadName; 55 | 56 | private long threadId; 57 | 58 | private ProxyEventType proxyEventType; 59 | 60 | private ValueStore valueStore = ValueStore.create(); 61 | 62 | public void setTarget(Object target) { 63 | this.target = target; 64 | } 65 | 66 | public void setMethod(Method method) { 67 | this.method = method; 68 | } 69 | 70 | public void setMethodArgs(@Nullable Object[] methodArgs) { 71 | this.methodArgs = methodArgs; 72 | } 73 | 74 | public void setResult(@Nullable Object result) { 75 | this.result = result; 76 | } 77 | 78 | public void setThrown(@Nullable Throwable thrown) { 79 | this.thrown = thrown; 80 | } 81 | 82 | public void setConnectionInfo(@Nullable ConnectionInfo connectionInfo) { 83 | this.connectionInfo = connectionInfo; 84 | } 85 | 86 | public void setExecuteDuration(Duration executeDuration) { 87 | this.executeDuration = executeDuration; 88 | } 89 | 90 | public void setThreadName(String threadName) { 91 | this.threadName = threadName; 92 | } 93 | 94 | public void setThreadId(long threadId) { 95 | this.threadId = threadId; 96 | } 97 | 98 | public void setProxyEventType(ProxyEventType proxyEventType) { 99 | this.proxyEventType = proxyEventType; 100 | } 101 | 102 | @Override 103 | public Object getTarget() { 104 | return target; 105 | } 106 | 107 | @Override 108 | public Method getMethod() { 109 | return method; 110 | } 111 | 112 | @Override 113 | public Object[] getMethodArgs() { 114 | return methodArgs; 115 | } 116 | 117 | @Override 118 | public Object getResult() { 119 | return result; 120 | } 121 | 122 | @Override 123 | public Throwable getThrown() { 124 | return thrown; 125 | } 126 | 127 | @Override 128 | public ConnectionInfo getConnectionInfo() { 129 | return this.connectionInfo; 130 | } 131 | 132 | @Override 133 | public Duration getExecuteDuration() { 134 | return executeDuration; 135 | } 136 | 137 | @Override 138 | public String getThreadName() { 139 | return threadName; 140 | } 141 | 142 | @Override 143 | public long getThreadId() { 144 | return threadId; 145 | } 146 | 147 | @Override 148 | public ProxyEventType getProxyEventType() { 149 | return proxyEventType; 150 | } 151 | 152 | @Override 153 | public ValueStore getValueStore() { 154 | return this.valueStore; 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/MutableStatementInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.callback; 19 | 20 | import io.r2dbc.proxy.core.ConnectionInfo; 21 | import io.r2dbc.proxy.core.StatementInfo; 22 | import io.r2dbc.proxy.core.ValueStore; 23 | 24 | /** 25 | * @author Tadaya Tsuyukubo 26 | */ 27 | final class MutableStatementInfo implements StatementInfo { 28 | 29 | private ConnectionInfo connectionInfo; 30 | 31 | private String originalQuery; 32 | 33 | private String updatedQuery; 34 | 35 | private ValueStore valueStore = ValueStore.create(); 36 | 37 | @Override 38 | public ConnectionInfo getConnectionInfo() { 39 | return this.connectionInfo; 40 | } 41 | 42 | public void setConnectionInfo(ConnectionInfo connectionInfo) { 43 | this.connectionInfo = connectionInfo; 44 | } 45 | 46 | @Override 47 | public String getOriginalQuery() { 48 | return this.originalQuery; 49 | } 50 | 51 | public void setOriginalQuery(String originalQuery) { 52 | this.originalQuery = originalQuery; 53 | } 54 | 55 | @Override 56 | public String getUpdatedQuery() { 57 | return this.updatedQuery; 58 | } 59 | 60 | public void setUpdatedQuery(String updatedQuery) { 61 | this.updatedQuery = updatedQuery; 62 | } 63 | 64 | @Override 65 | public ValueStore getValueStore() { 66 | return this.valueStore; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/ProxyConfigHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | /** 20 | * Provide a method to retrieve {@link ProxyConfig} from proxy object. 21 | * 22 | * @author Tadaya Tsuyukubo 23 | */ 24 | public interface ProxyConfigHolder { 25 | 26 | /** 27 | * Retrieve {@link ProxyConfig}. 28 | * 29 | * @return proxy config object 30 | */ 31 | ProxyConfig getProxyConfig(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/ProxyFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2023 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.core.ConnectionInfo; 20 | import io.r2dbc.proxy.core.QueryExecutionInfo; 21 | import io.r2dbc.proxy.core.StatementInfo; 22 | import io.r2dbc.spi.Batch; 23 | import io.r2dbc.spi.Connection; 24 | import io.r2dbc.spi.ConnectionFactory; 25 | import io.r2dbc.spi.Result; 26 | import io.r2dbc.spi.Row; 27 | import io.r2dbc.spi.Statement; 28 | 29 | /** 30 | * Defines factory methods to create proxy of SPI classes. 31 | * 32 | * @author Tadaya Tsuyukubo 33 | */ 34 | public interface ProxyFactory { 35 | 36 | /** 37 | * Create a proxy {@link ConnectionFactory}. 38 | * 39 | * @param connectionFactory original connectionFactory 40 | * @return proxy connectionFactory 41 | * @throws IllegalArgumentException if {@code connectionFactory} is {@code null} 42 | */ 43 | ConnectionFactory wrapConnectionFactory(ConnectionFactory connectionFactory); 44 | 45 | /** 46 | * Create a proxy {@link Connection}. 47 | * 48 | * @param connection original connection 49 | * @param connectionInfo connectionInfo 50 | * @return proxy connection 51 | * @throws IllegalArgumentException if {@code connection} is {@code null} 52 | * @throws IllegalArgumentException if {@code connectionInfo} is {@code null} 53 | */ 54 | Connection wrapConnection(Connection connection, ConnectionInfo connectionInfo); 55 | 56 | /** 57 | * Create a proxy {@link Batch}. 58 | * 59 | * @param batch original batch 60 | * @param connectionInfo connectionInfo 61 | * @return proxy batch 62 | * @throws IllegalArgumentException if {@code batch} is {@code null} 63 | * @throws IllegalArgumentException if {@code connectionInfo} is {@code null} 64 | */ 65 | Batch wrapBatch(Batch batch, ConnectionInfo connectionInfo); 66 | 67 | /** 68 | * Create a proxy {@link Statement}. 69 | * 70 | * @param statement original statement 71 | * @param statementInfo contextual information of creating the {@link Statement} 72 | * @param connectionInfo connectionInfo 73 | * @return proxy statement 74 | * @throws IllegalArgumentException if {@code statement} is {@code null} 75 | * @throws IllegalArgumentException if {@code originalQuery} is {@code null} 76 | * @throws IllegalArgumentException if {@code updatedQuery} is {@code null} 77 | * @throws IllegalArgumentException if {@code connectionInfo} is {@code null} 78 | */ 79 | Statement wrapStatement(Statement statement, StatementInfo statementInfo, ConnectionInfo connectionInfo); 80 | 81 | /** 82 | * Create a proxy {@link Result}. 83 | * 84 | * @param result original result 85 | * @param executionInfo executionInfo 86 | * @param queriesExecutionContext queries execution context 87 | * @return proxy result 88 | * @throws IllegalArgumentException if {@code result} is {@code null} 89 | * @throws IllegalArgumentException if {@code executionInfo} is {@code null} 90 | * @throws IllegalArgumentException if {@code queriesExecutionContext} is {@code null} 91 | */ 92 | Result wrapResult(Result result, QueryExecutionInfo executionInfo, QueriesExecutionContext queriesExecutionContext); 93 | 94 | /** 95 | * Create a proxy {@link Row}. 96 | * 97 | * @param row original row 98 | * @param executionInfo executionInfo 99 | * @return proxy row 100 | * @throws IllegalArgumentException if {@code row} is {@code null} 101 | * @throws IllegalArgumentException if {@code executionInfo} is {@code null} 102 | */ 103 | Row wrapRow(Row row, QueryExecutionInfo executionInfo); 104 | 105 | /** 106 | * Create a proxy {@link Result.RowSegment}. 107 | * 108 | * @param rowSegment original rowSegment 109 | * @param executionInfo executionInfo 110 | * @return proxy rowSegment 111 | * @throws IllegalArgumentException if {@code rowSegment} is {@code null} 112 | * @throws IllegalArgumentException if {@code executionInfo} is {@code null} 113 | * @since 1.1.3 114 | */ 115 | Result.RowSegment wrapRowSegment(Result.RowSegment rowSegment, QueryExecutionInfo executionInfo); 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/ProxyFactoryFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.callback; 19 | 20 | /** 21 | * Factory to create a {@link ProxyFactory}. 22 | * 23 | * @author Tadaya Tsuyukubo 24 | */ 25 | public interface ProxyFactoryFactory { 26 | 27 | /** 28 | * Create {@link ProxyFactory} with given {@link ProxyConfig}. 29 | * 30 | * @param proxyConfig proxy config 31 | * @return proxy factory 32 | * @throws IllegalArgumentException if {@code proxyConfig} is {@code null} 33 | */ 34 | ProxyFactory create(ProxyConfig proxyConfig); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/ProxyUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.util.Assert; 20 | import io.r2dbc.spi.Batch; 21 | import io.r2dbc.spi.Connection; 22 | import io.r2dbc.spi.Result; 23 | import io.r2dbc.spi.Statement; 24 | import io.r2dbc.spi.Wrapped; 25 | 26 | import java.util.Optional; 27 | 28 | /** 29 | * Utility methods to obtain original {@link Connection} from proxy class. 30 | * 31 | * @author Tadaya Tsuyukubo 32 | */ 33 | public class ProxyUtils { 34 | 35 | private ProxyUtils() { 36 | } 37 | 38 | /** 39 | * Get original {@link Connection} if given {@link Connection} has implemented {@link Wrapped}. 40 | * 41 | * @param connection a connection 42 | * @return optional of original connection or give connection 43 | * @throws IllegalArgumentException if {@code connection} is {@code null} 44 | */ 45 | @SuppressWarnings("unchecked") 46 | public static Optional unwrapConnection(Connection connection) { 47 | Assert.requireNonNull(connection, "connection must not be null"); 48 | 49 | if (connection instanceof Wrapped) { 50 | return Optional.of(((Wrapped) connection).unwrap()); 51 | } 52 | return Optional.of(connection); 53 | } 54 | 55 | /** 56 | * Get original {@link Connection} from proxy {@link Batch}. 57 | * 58 | * When provided {@link Batch} is a proxy that implements {@link ConnectionHolder}, retrieves original 59 | * {@link Connection}; otherwise, returns empty {@code Optional}. 60 | * 61 | * @param batch a batch 62 | * @return optional of original connection or empty 63 | * @throws IllegalArgumentException if {@code batch} is {@code null} 64 | */ 65 | public static Optional unwrapConnection(Batch batch) { 66 | Assert.requireNonNull(batch, "batch must not be null"); 67 | 68 | if (batch instanceof ConnectionHolder) { 69 | return Optional.of(((ConnectionHolder) batch).unwrapConnection()); 70 | } 71 | return Optional.empty(); 72 | } 73 | 74 | /** 75 | * Get original {@link Connection} from proxy {@link Statement}. 76 | * 77 | * When provided {@link Statement} is a proxy that implements {@link ConnectionHolder}, retrieves original 78 | * {@link Connection}; otherwise, returns empty {@code Optional}. 79 | * 80 | * @param statement a statement 81 | * @return optional of original connection or empty 82 | * @throws IllegalArgumentException if {@code statement} is {@code null} 83 | */ 84 | public static Optional unwrapConnection(Statement statement) { 85 | Assert.requireNonNull(statement, "statement must not be null"); 86 | 87 | if (statement instanceof ConnectionHolder) { 88 | return Optional.of(((ConnectionHolder) statement).unwrapConnection()); 89 | } 90 | return Optional.empty(); 91 | } 92 | 93 | /** 94 | * Get original {@link Connection} from proxy {@link Result}. 95 | * 96 | * When provided {@link Result} is a proxy that implements {@link ConnectionHolder}, retrieves original 97 | * {@link Connection}; otherwise, returns empty {@code Optional}. 98 | * 99 | * @param result a statement 100 | * @return optional of original connection or empty 101 | * @throws IllegalArgumentException if {@code result} is {@code null} 102 | */ 103 | public static Optional unwrapConnection(Result result) { 104 | Assert.requireNonNull(result, "result must not be null"); 105 | 106 | if (result instanceof ConnectionHolder) { 107 | return Optional.of(((ConnectionHolder) result).unwrapConnection()); 108 | } 109 | return Optional.empty(); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/QueriesExecutionContext.java: -------------------------------------------------------------------------------- 1 | package io.r2dbc.proxy.callback; 2 | 3 | import io.r2dbc.spi.Result; 4 | 5 | import java.time.Clock; 6 | import java.time.Duration; 7 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 8 | 9 | /** 10 | * The context of queries execution. 11 | *

12 | * Holds the count of {@link Result} produced by {@code Statement#execute} and count that has 13 | * consumed {@code Result#[map|getRowsUpdated]}. 14 | * 15 | * @author Thomas Deblock 16 | * @author Tadaya Tsuyukubo 17 | */ 18 | public class QueriesExecutionContext { 19 | 20 | private static final AtomicIntegerFieldUpdater PRODUCED_COUNT_INCREMENTER = 21 | AtomicIntegerFieldUpdater.newUpdater(QueriesExecutionContext.class, "resultProducedCount"); 22 | 23 | private static final AtomicIntegerFieldUpdater CONSUMED_COUNT_INCREMENTER = 24 | AtomicIntegerFieldUpdater.newUpdater(QueriesExecutionContext.class, "resultConsumedCount"); 25 | 26 | /** 27 | * Increment this count when a publisher from {@code Statement#execute} produced a {@link Result}. 28 | * Accessed via {@link #PRODUCED_COUNT_INCREMENTER}. 29 | */ 30 | private volatile int resultProducedCount; 31 | 32 | /** 33 | * Increment this count when a publisher from {@code Result#[map|getRowsUpdated]} is consumed. 34 | * Accessed via {@link #CONSUMED_COUNT_INCREMENTER}. 35 | */ 36 | private volatile int resultConsumedCount; 37 | 38 | private final StopWatch stopWatch; 39 | 40 | private boolean allProduced; 41 | 42 | public QueriesExecutionContext(Clock clock) { 43 | this.stopWatch = new StopWatch(clock); 44 | } 45 | 46 | /** 47 | * Increment the count of produced {@link Result} from {@code Statement#execute}. 48 | */ 49 | public void incrementProducedCount() { 50 | PRODUCED_COUNT_INCREMENTER.incrementAndGet(this); 51 | } 52 | 53 | /** 54 | * Increment the count of consumptions from {@code Result#[map|getRowsUpdated]}. 55 | */ 56 | public void incrementConsumedCount() { 57 | CONSUMED_COUNT_INCREMENTER.incrementAndGet(this); 58 | } 59 | 60 | /** 61 | * Retrieve the elapsed time from the stopwatch that has started by {@link #startStopwatch()}. 62 | * 63 | * @return duration from start 64 | */ 65 | public Duration getElapsedDuration() { 66 | return this.stopWatch.getElapsedDuration(); 67 | } 68 | 69 | /** 70 | * Start the stopwatch. 71 | */ 72 | public void startStopwatch() { 73 | this.stopWatch.start(); 74 | } 75 | 76 | /** 77 | * Whether the executed queries have finished and results are consumed. 78 | *

79 | * The query is considered finished when the publisher from {@code Statement#execute()} have produced 80 | * {@link Result}s and those are consumed via {@code Result#getRowsUpdated} or {@code Result#map}. 81 | * 82 | * @return {@code true} if all {@code Result} are produced and consumed. 83 | */ 84 | public boolean isQueryFinished() { 85 | return this.allProduced && isAllConsumed(); 86 | } 87 | 88 | /** 89 | * Whether currently all produced {@link Result}s are consumed. 90 | * 91 | * @return {@code true} if all produced {@link Result}s are consumed. 92 | */ 93 | public boolean isAllConsumed() { 94 | return this.resultConsumedCount >= this.resultProducedCount; 95 | } 96 | 97 | 98 | /** 99 | * When {@link QueryInvocationSubscriber} produced all {@link Result} objects. 100 | */ 101 | public void markAllProduced() { 102 | this.allProduced = true; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/RowCallbackHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2023 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.core.ConnectionInfo; 20 | import io.r2dbc.proxy.core.QueryExecutionInfo; 21 | import io.r2dbc.proxy.core.R2dbcProxyException; 22 | import io.r2dbc.proxy.listener.ResultRowConverter; 23 | import io.r2dbc.proxy.util.Assert; 24 | import io.r2dbc.spi.Row; 25 | import reactor.util.annotation.Nullable; 26 | 27 | import java.lang.reflect.Method; 28 | 29 | /** 30 | * Proxy callback handler for {@link Row}. 31 | * 32 | * @author Tadaya Tsuyukubo 33 | * @since 0.9.0 34 | */ 35 | public final class RowCallbackHandler extends CallbackHandlerSupport { 36 | 37 | private final Row row; 38 | 39 | private final QueryExecutionInfo queryExecutionInfo; 40 | 41 | /** 42 | * Callback handler logic for {@link Row}. 43 | * 44 | * @param row row 45 | * @param queryExecutionInfo query execution info 46 | * @param proxyConfig proxy config 47 | * @throws IllegalArgumentException if {@code row} is {@code null} 48 | * @throws IllegalArgumentException if {@code queryExecutionInfo} is {@code null} 49 | * @throws IllegalArgumentException if {@code proxyConfig} is {@code null} 50 | */ 51 | public RowCallbackHandler(Row row, QueryExecutionInfo queryExecutionInfo, ProxyConfig proxyConfig) { 52 | super(proxyConfig); 53 | this.row = Assert.requireNonNull(row, "row must not be null"); 54 | this.queryExecutionInfo = Assert.requireNonNull(queryExecutionInfo, "queryExecutionInfo must not be null"); 55 | } 56 | 57 | @Override 58 | public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { 59 | Assert.requireNonNull(proxy, "proxy must not be null"); 60 | Assert.requireNonNull(method, "method must not be null"); 61 | 62 | String methodName = method.getName(); 63 | ConnectionInfo connectionInfo = this.queryExecutionInfo.getConnectionInfo(); 64 | 65 | if (isCommonMethod(methodName)) { 66 | return handleCommonMethod(methodName, this.row, args, connectionInfo.getOriginalConnection()); 67 | } 68 | 69 | // when converter decides to perform the original call("getOperation.proceed()"), this lambda is called. 70 | ResultRowConverter.GetOperation onGet = () -> { 71 | try { 72 | Object result = proceedExecution(method, this.row, args, this.proxyConfig.getListeners(), connectionInfo, null); 73 | return result; 74 | } catch (Throwable throwable) { 75 | throw new R2dbcProxyException("Failed to perform " + methodName, throwable); 76 | } 77 | }; 78 | 79 | // callback for "Row#get(...)" 80 | Object result = this.proxyConfig.getResultRowConverter().onGet((Row) proxy, method, args, onGet); 81 | return result; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/RowSegmentCallbackHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.core.ConnectionInfo; 20 | import io.r2dbc.proxy.core.QueryExecutionInfo; 21 | import io.r2dbc.proxy.util.Assert; 22 | import io.r2dbc.spi.Result; 23 | import io.r2dbc.spi.Row; 24 | import reactor.util.annotation.Nullable; 25 | 26 | import java.lang.reflect.Method; 27 | 28 | /** 29 | * Proxy callback handler for {@link Result.RowSegment}. 30 | * 31 | * @author Tadaya Tsuyukubo 32 | * @since 1.1.3 33 | */ 34 | public final class RowSegmentCallbackHandler extends CallbackHandlerSupport { 35 | 36 | private final Result.RowSegment rowSegment; 37 | 38 | private final QueryExecutionInfo queryExecutionInfo; 39 | 40 | /** 41 | * Callback handler logic for {@link Row}. 42 | * 43 | * @param rowSegment row 44 | * @param queryExecutionInfo query execution info 45 | * @param proxyConfig proxy config 46 | * @throws IllegalArgumentException if {@code row} is {@code null} 47 | * @throws IllegalArgumentException if {@code queryExecutionInfo} is {@code null} 48 | * @throws IllegalArgumentException if {@code proxyConfig} is {@code null} 49 | */ 50 | public RowSegmentCallbackHandler(Result.RowSegment rowSegment, QueryExecutionInfo queryExecutionInfo, ProxyConfig proxyConfig) { 51 | super(proxyConfig); 52 | this.rowSegment = Assert.requireNonNull(rowSegment, "rowSegment must not be null"); 53 | this.queryExecutionInfo = Assert.requireNonNull(queryExecutionInfo, "queryExecutionInfo must not be null"); 54 | } 55 | 56 | @Override 57 | public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { 58 | Assert.requireNonNull(proxy, "proxy must not be null"); 59 | Assert.requireNonNull(method, "method must not be null"); 60 | 61 | String methodName = method.getName(); 62 | ConnectionInfo connectionInfo = this.queryExecutionInfo.getConnectionInfo(); 63 | 64 | if (isCommonMethod(methodName)) { 65 | return handleCommonMethod(methodName, this.rowSegment, args, connectionInfo.getOriginalConnection()); 66 | } 67 | 68 | Object result = proceedExecution(method, this.rowSegment, args, this.proxyConfig.getListeners(), connectionInfo, null); 69 | 70 | if ("row".equals(methodName)) { 71 | return this.proxyConfig.getProxyFactory().wrapRow((Row) result, queryExecutionInfo); 72 | } 73 | return result; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/StopWatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import reactor.util.annotation.Nullable; 20 | 21 | import java.time.Clock; 22 | import java.time.Duration; 23 | import java.time.Instant; 24 | 25 | /** 26 | * Utility class to get duration of executions. 27 | * 28 | * @author Tadaya Tsuyukubo 29 | */ 30 | class StopWatch { 31 | 32 | private final Clock clock; 33 | 34 | @Nullable 35 | private Instant startTime; 36 | 37 | StopWatch(Clock clock) { 38 | this.clock = clock; 39 | } 40 | 41 | public StopWatch start() { 42 | this.startTime = this.clock.instant(); 43 | return this; 44 | } 45 | 46 | public Duration getElapsedDuration() { 47 | if (this.startTime == null) { 48 | return Duration.ZERO; // when stopwatch has not started 49 | } 50 | return Duration.between(this.startTime, this.clock.instant()); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/callback/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Proxy related classes 19 | */ 20 | 21 | @NonNullApi 22 | package io.r2dbc.proxy.callback; 23 | 24 | import reactor.util.annotation.NonNullApi; -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/BindInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.core; 19 | 20 | import io.r2dbc.proxy.listener.BindParameterConverter; 21 | import io.r2dbc.spi.Statement; 22 | 23 | /** 24 | * Hold contextual information for bind operations({@code bind} and {@code bindNull}). 25 | * 26 | * @author Tadaya Tsuyukubo 27 | * @see BindParameterConverter 28 | */ 29 | public interface BindInfo { 30 | 31 | /** 32 | * Get {@link StatementInfo} where bind operation has happened. 33 | * 34 | * @return contextual info for {@link Statement} 35 | */ 36 | StatementInfo getStatementInfo(); 37 | 38 | /** 39 | * Get invoked bind operation details. 40 | * 41 | * @return binding details 42 | */ 43 | Binding getBinding(); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/Binding.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | /** 20 | * Represent an operation of {@link io.r2dbc.spi.Statement#bind} and {@link io.r2dbc.spi.Statement#bindNull}. 21 | * 22 | * @author Tadaya Tsuyukubo 23 | * @see Bindings.IndexBinding 24 | * @see Bindings.NamedBinding 25 | */ 26 | public interface Binding { 27 | 28 | /** 29 | * Get a key which represents index or name of the binding. 30 | * 31 | * @return an index or name 32 | */ 33 | Object getKey(); 34 | 35 | /** 36 | * Get a {@link BoundValue}. 37 | * 38 | * @return a bound value 39 | */ 40 | BoundValue getBoundValue(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/BoundValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | import io.r2dbc.proxy.util.Assert; 20 | 21 | /** 22 | * Represent a value for {@link io.r2dbc.spi.Statement#bind} and {@link io.r2dbc.spi.Statement#bindNull} operations. 23 | * 24 | * @author Tadaya Tsuyukubo 25 | */ 26 | public interface BoundValue { 27 | 28 | /** 29 | * Create a {@link BoundValue} that represents {@link io.r2dbc.spi.Statement#bind}. 30 | * 31 | * @param value value 32 | * @return a boundValue 33 | * @throws IllegalArgumentException if {@code value} is {@code null} 34 | */ 35 | static BoundValue value(Object value) { 36 | Assert.requireNonNull(value, "value must not be null"); 37 | 38 | DefaultBoundValue boundValue = new DefaultBoundValue(); 39 | boundValue.value = value; 40 | return boundValue; 41 | } 42 | 43 | /** 44 | * Create a {@link BoundValue} that represents {@link io.r2dbc.spi.Statement#bindNull}. 45 | * 46 | * @param nullType {@code null} type 47 | * @return a boundValue 48 | * @throws IllegalArgumentException if {@code nullType} is {@code null} 49 | */ 50 | static BoundValue nullValue(Class nullType) { 51 | Assert.requireNonNull(nullType, "nullType must not be null"); 52 | 53 | DefaultBoundValue boundValue = new DefaultBoundValue(); 54 | boundValue.nullType = nullType; 55 | return boundValue; 56 | } 57 | 58 | /** 59 | * Distinguish between the bound value is for {@link io.r2dbc.spi.Statement#bind} or {@link io.r2dbc.spi.Statement#bindNull} operation. 60 | * 61 | * @return {@code true} when this represents value of {@link io.r2dbc.spi.Statement#bindNull} operation 62 | */ 63 | boolean isNull(); 64 | 65 | /** 66 | * Get the bound value by {@link io.r2dbc.spi.Statement#bind}. 67 | * 68 | * @return bound value 69 | */ 70 | Object getValue(); 71 | 72 | /** 73 | * Get the bound {@code null} type by {@link io.r2dbc.spi.Statement#bindNull}. 74 | * 75 | * @return {@code null} type 76 | */ 77 | Class getNullType(); 78 | 79 | /** 80 | * Default implementation of {@link BoundValue}. 81 | */ 82 | final class DefaultBoundValue implements BoundValue { 83 | 84 | private Object value; 85 | 86 | private Class nullType; 87 | 88 | @Override 89 | public boolean isNull() { 90 | return this.nullType != null; 91 | } 92 | 93 | @Override 94 | public Class getNullType() { 95 | return this.nullType; 96 | } 97 | 98 | @Override 99 | public Object getValue() { 100 | return this.value; 101 | } 102 | 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/ConnectionInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | 20 | import io.r2dbc.proxy.listener.ProxyExecutionListener; 21 | import io.r2dbc.spi.Connection; 22 | import io.r2dbc.spi.ConnectionFactory; 23 | import reactor.util.annotation.Nullable; 24 | 25 | /** 26 | * Hold {@link Connection} related information. 27 | * 28 | * @author Tadaya Tsuyukubo 29 | */ 30 | public interface ConnectionInfo { 31 | /** 32 | * Retrieve original {@link Connection}. 33 | * 34 | * @return connection; {@code null} is returned when {@link ConnectionInfo} is evaluated 35 | * before it is associated with an actual connection, for example, during 36 | * {@link ProxyExecutionListener#beforeMethod(MethodExecutionInfo)} for 37 | * {@link ConnectionFactory#create()}. 38 | */ 39 | @Nullable 40 | Connection getOriginalConnection(); 41 | 42 | /** 43 | * Get ID for the connection. 44 | * 45 | * @return connection ID; {@code null} is returned when {@link ConnectionInfo} is evaluated 46 | * before it is associated with an actual connection, for example, during 47 | * {@link ProxyExecutionListener#beforeMethod(MethodExecutionInfo)} for 48 | * {@link ConnectionFactory#create()}. 49 | * @see io.r2dbc.proxy.callback.ConnectionIdManager 50 | */ 51 | @Nullable 52 | String getConnectionId(); 53 | 54 | /** 55 | * Increment transaction count. 56 | */ 57 | void incrementTransactionCount(); 58 | 59 | /** 60 | * Increment commit count. 61 | */ 62 | void incrementCommitCount(); 63 | 64 | /** 65 | * Increment rollback count. 66 | */ 67 | void incrementRollbackCount(); 68 | 69 | /** 70 | * Returns how many times {@link Connection#beginTransaction()} method is called. 71 | * 72 | * @return num of beginTransaction() method being called 73 | */ 74 | int getTransactionCount(); 75 | 76 | /** 77 | * Returns how many times {@link Connection#commitTransaction()} method is called. 78 | * 79 | * @return num of commitTransaction method being called 80 | */ 81 | int getCommitCount(); 82 | 83 | /** 84 | * Returns how many times {@link Connection#rollbackTransaction()} method is called. 85 | * 86 | * @return num of rollback methods being called 87 | */ 88 | int getRollbackCount(); 89 | 90 | /** 91 | * Returns whether connection is closed or not. 92 | * 93 | * @return {@code true} if connection is closed 94 | */ 95 | boolean isClosed(); 96 | 97 | /** 98 | * Set {@code boolean} to indicate whether the connection is closed or not. 99 | * 100 | * @param closed set {@code true} if {@link Connection} is closed 101 | */ 102 | void setClosed(boolean closed); 103 | 104 | /** 105 | * Retrieve {@link ValueStore} which is associated to the scope of logical connection. 106 | * 107 | *

Values can be stored or retrieved from this store while connection is available. 108 | * 109 | * @return value store 110 | */ 111 | ValueStore getValueStore(); 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/DefaultValueStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | import io.r2dbc.proxy.util.Assert; 20 | 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | /** 25 | * Default implementation of {@link ValueStore}. 26 | * 27 | *

Simply uses {@link Map} as underlying storage. 28 | * Primitive values will be autoboxed to corresponding wrapper values when they 29 | * are stored. 30 | * 31 | * @author Tadaya Tsuyukubo 32 | */ 33 | public class DefaultValueStore implements ValueStore { 34 | 35 | private Map map = new HashMap<>(); 36 | 37 | @Override 38 | public Object get(Object key) { 39 | Assert.requireNonNull(key, "key must not be null"); 40 | 41 | return this.map.get(key); 42 | } 43 | 44 | @Override 45 | public T get(Object key, Class type) { 46 | Assert.requireNonNull(key, "key must not be null"); 47 | Assert.requireNonNull(type, "type must not be null"); 48 | 49 | Object value = this.map.get(key); 50 | if (value == null) { 51 | return null; 52 | } 53 | return type.cast(value); 54 | } 55 | 56 | @Override 57 | @SuppressWarnings("unchecked") 58 | public T getOrDefault(Object key, T defaultValue) { 59 | Assert.requireNonNull(key, "key must not be null"); 60 | Assert.requireNonNull(defaultValue, "type must not be null"); 61 | 62 | Object value = this.map.get(key); 63 | if (value == null) { 64 | return defaultValue; 65 | } 66 | return (T) value; 67 | } 68 | 69 | @Override 70 | public void put(Object key, Object value) { 71 | Assert.requireNonNull(key, "key must not be null"); 72 | 73 | this.map.put(key, value); 74 | } 75 | 76 | @Override 77 | public void putAll(Map map) { 78 | Assert.requireNonNull(map, "map must not be null"); 79 | 80 | this.map.putAll(map); 81 | } 82 | 83 | @Override 84 | public Object remove(Object key) { 85 | Assert.requireNonNull(key, "key must not be null"); 86 | 87 | return this.map.remove(key); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/ExecutionType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | /** 20 | * Query execution type. 21 | * 22 | * @author Tadaya Tsuyukubo 23 | */ 24 | public enum ExecutionType { 25 | BATCH, STATEMENT 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/MethodExecutionInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | import io.r2dbc.proxy.listener.ProxyExecutionListener; 20 | import io.r2dbc.spi.Connection; 21 | import reactor.util.annotation.Nullable; 22 | 23 | import java.lang.reflect.Method; 24 | import java.time.Duration; 25 | 26 | /** 27 | * Hold method execution related information. 28 | * 29 | * @author Tadaya Tsuyukubo 30 | */ 31 | public interface MethodExecutionInfo { 32 | 33 | /** 34 | * Get the invoked object. 35 | * 36 | * @return the proxy instance that the method was invoked on 37 | */ 38 | Object getTarget(); 39 | 40 | /** 41 | * Get the invoked {@code Method}. 42 | * 43 | * @return invoked method 44 | */ 45 | Method getMethod(); 46 | 47 | /** 48 | * Get the arguments of the invocation. 49 | * 50 | * This can be {@code null} when method is invoked with no argument. 51 | * 52 | * @return argument lists or {@code null} if the invoked method did not take any arguments 53 | */ 54 | @Nullable 55 | Object[] getMethodArgs(); 56 | 57 | /** 58 | * Get the result of invocation. 59 | * For {@link ProxyExecutionListener#beforeMethod(MethodExecutionInfo)} callback, this returns {@code null}. 60 | * 61 | * @return result 62 | */ 63 | @Nullable 64 | Object getResult(); 65 | 66 | /** 67 | * Get the thrown exception. 68 | * For {@link ProxyExecutionListener#beforeMethod(MethodExecutionInfo)} callback or when the invocation 69 | * did't throw any error, this returns {@code null}. 70 | * 71 | * @return thrown exception 72 | */ 73 | @Nullable 74 | Throwable getThrown(); 75 | 76 | /** 77 | * Get the {@link ConnectionInfo}. 78 | * When invoked operation is not associated to the {@link Connection}, this returns {@code null}. 79 | * 80 | * @return connection info 81 | */ 82 | @Nullable 83 | ConnectionInfo getConnectionInfo(); 84 | 85 | /** 86 | * Get the duration of the method invocation. 87 | * For {@link ProxyExecutionListener#beforeMethod(MethodExecutionInfo)} callback, this returns {@link Duration#ZERO}. 88 | * 89 | * @return execution duration 90 | */ 91 | Duration getExecuteDuration(); 92 | 93 | /** 94 | * Get the thread name. 95 | * 96 | * @return thread name 97 | */ 98 | String getThreadName(); 99 | 100 | /** 101 | * Get the thread ID. 102 | * 103 | * @return thread ID 104 | */ 105 | long getThreadId(); 106 | 107 | /** 108 | * Get the proxy event type. 109 | * 110 | * @return proxy event type; either {@link ProxyEventType#BEFORE_METHOD} or {@link ProxyEventType#AFTER_METHOD} 111 | */ 112 | ProxyEventType getProxyEventType(); 113 | 114 | /** 115 | * Retrieve {@link ValueStore} which is associated to the scope of before/after method execution. 116 | * 117 | * Mainly used for passing values between {@link ProxyExecutionListener#beforeMethod(MethodExecutionInfo)} and 118 | * {@link ProxyExecutionListener#afterMethod(MethodExecutionInfo)}. 119 | * 120 | * @return value store 121 | */ 122 | ValueStore getValueStore(); 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/ProxyEventType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | /** 20 | * Proxy callback event type. 21 | * 22 | * @author Tadaya Tsuyukubo 23 | */ 24 | public enum ProxyEventType { 25 | BEFORE_METHOD, AFTER_METHOD, BEFORE_QUERY, AFTER_QUERY, EACH_QUERY_RESULT 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/QueryInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | import io.r2dbc.proxy.util.Assert; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | /** 25 | * Hold each query related info. 26 | * 27 | * @author Tadaya Tsuyukubo 28 | */ 29 | public class QueryInfo { 30 | 31 | private final String query; 32 | 33 | private final List bindingsList = new ArrayList<>(); 34 | 35 | /** 36 | * Construct the {@code QueryInfo} with query. 37 | * 38 | * @param query query 39 | * @throws IllegalArgumentException if {@code query} is {@code null} 40 | */ 41 | public QueryInfo(String query) { 42 | this.query = Assert.requireNonNull(query, "query must not be null"); 43 | } 44 | 45 | /** 46 | * Get the query. 47 | * 48 | * @return query; never {@code null} 49 | */ 50 | public String getQuery() { 51 | return this.query; 52 | } 53 | 54 | /** 55 | * Get the list of {@link Bindings}. 56 | * 57 | * @return list of bindings; never {@code null} 58 | */ 59 | public List getBindingsList() { 60 | return this.bindingsList; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/R2dbcProxyException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | import io.r2dbc.spi.R2dbcException; 20 | import reactor.util.annotation.Nullable; 21 | 22 | /** 23 | * Generic r2dbc-proxy exception. 24 | * 25 | * @author Tadaya Tsuyukubo 26 | */ 27 | public class R2dbcProxyException extends R2dbcException { 28 | 29 | /** 30 | * Create a new {@link R2dbcProxyException}. 31 | */ 32 | public R2dbcProxyException() { 33 | } 34 | 35 | /** 36 | * Create a new {@link R2dbcProxyException}. 37 | * 38 | * @param reason the reason for the error. 39 | */ 40 | public R2dbcProxyException(@Nullable String reason) { 41 | super(reason); 42 | } 43 | 44 | /** 45 | * Create a new {@link R2dbcProxyException}. 46 | * 47 | * @param reason the reason for the error. 48 | * @param cause the cause of the error. 49 | */ 50 | public R2dbcProxyException(@Nullable String reason, @Nullable Throwable cause) { 51 | super(reason, cause); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/StatementInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.core; 19 | 20 | import io.r2dbc.proxy.listener.BindParameterConverter; 21 | import io.r2dbc.spi.Connection; 22 | import io.r2dbc.spi.Statement; 23 | 24 | /** 25 | * Hold {@link Statement} related information. 26 | * 27 | * @author Tadaya Tsuyukubo 28 | */ 29 | public interface StatementInfo { 30 | 31 | /** 32 | * Get {@link ConnectionInfo} associated to this {@link Statement}. 33 | * 34 | * @return connection info 35 | */ 36 | ConnectionInfo getConnectionInfo(); 37 | 38 | /** 39 | * Get the sql statement that has originally specified on {@link Connection#createStatement(String)}. 40 | * 41 | * @return original sql statement 42 | */ 43 | String getOriginalQuery(); 44 | 45 | /** 46 | * Get the updated sql statement by {@link BindParameterConverter#onCreateStatement(String, StatementInfo)}. 47 | * 48 | * @return updated sql statement 49 | */ 50 | String getUpdatedQuery(); 51 | 52 | /** 53 | * Retrieve {@link ValueStore} which is associated to the scope of logical statement. 54 | * 55 | *

Values can be stored or retrieved from this store while statement is available. 56 | * 57 | * @return value store 58 | */ 59 | ValueStore getValueStore(); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/ValueStore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | import reactor.util.annotation.Nullable; 20 | 21 | import java.util.Map; 22 | 23 | /** 24 | * Custom value store. 25 | * 26 | * @author Tadaya Tsuyukubo 27 | * @see ConnectionInfo 28 | * @see MethodExecutionInfo 29 | * @see QueryExecutionInfo 30 | */ 31 | public interface ValueStore { 32 | 33 | /** 34 | * Create default {@link ValueStore}. 35 | * 36 | * @return value store 37 | */ 38 | static ValueStore create() { 39 | return new DefaultValueStore(); 40 | } 41 | 42 | /** 43 | * Get the value associated to the key. 44 | * 45 | * @param key key 46 | * @return value; can be {@code null} 47 | * @throws IllegalArgumentException if {@code key} is {@code null} 48 | */ 49 | @Nullable 50 | Object get(Object key); 51 | 52 | /** 53 | * Get the value associated to the key and cast to the type. 54 | * 55 | * @param key key 56 | * @param type value type to cast 57 | * @param value type 58 | * @return value; can be {@code null} 59 | * @throws IllegalArgumentException if {@code key} is {@code null} 60 | * @throws IllegalArgumentException if {@code type} is {@code null} 61 | */ 62 | @Nullable 63 | T get(Object key, Class type); 64 | 65 | /** 66 | * Get the value associated to the key; otherwise returns specified value. 67 | * 68 | * @param key key 69 | * @param defaultValue default value 70 | * @param value type 71 | * @return value 72 | * @throws IllegalArgumentException if {@code key} is {@code null} 73 | */ 74 | T getOrDefault(Object key, T defaultValue); 75 | 76 | /** 77 | * Store a value associating the provided key. 78 | * 79 | * @param key key 80 | * @param value value 81 | * @throws IllegalArgumentException if {@code key} is {@code null} 82 | * @throws IllegalArgumentException if {@code value} is {@code null} 83 | */ 84 | void put(Object key, Object value); 85 | 86 | /** 87 | * Store all key value pairs from provided map. 88 | * 89 | * @param map map 90 | * @throws IllegalArgumentException if {@code map} is {@code null} 91 | */ 92 | void putAll(Map map); 93 | 94 | /** 95 | * Remove the value associated to the provided key. 96 | * 97 | * @param key key 98 | * @return previously associated value or {@code null} if key did not exist 99 | * @throws IllegalArgumentException if {@code key} is {@code null} 100 | */ 101 | @Nullable 102 | Object remove(Object key); 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/core/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Domain classes 19 | */ 20 | 21 | @NonNullApi 22 | package io.r2dbc.proxy.core; 23 | 24 | import reactor.util.annotation.NonNullApi; -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/listener/BindParameterConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.listener; 19 | 20 | import io.r2dbc.proxy.core.BindInfo; 21 | import io.r2dbc.proxy.core.StatementInfo; 22 | import io.r2dbc.spi.Statement; 23 | 24 | /** 25 | * Callback for {@link io.r2dbc.spi.Connection#createStatement(String)} and bind operations({@code bind} and {@code bindNull}). 26 | * 27 | * @author Tadaya Tsuyukubo 28 | */ 29 | public interface BindParameterConverter { 30 | 31 | /** 32 | * Create default {@link BindParameterConverter}. 33 | * 34 | * @return a bindParameterConverter 35 | */ 36 | static BindParameterConverter create() { 37 | return new BindParameterConverter() { 38 | 39 | }; 40 | } 41 | 42 | /** 43 | * Convert the given query to another one at {@link io.r2dbc.spi.Connection#createStatement(String)}. 44 | * 45 | *

This method will be called when {@link io.r2dbc.spi.Connection#createStatement(String)} is called(before actually 46 | * performing), and the returned query will be used as its input. 47 | * Additionally, in this method, any information can be stored in {@link StatementInfo#getValueStore()} 48 | * and they will be available at {@link #onBind(BindInfo, Statement, BindOperation)}. 49 | * 50 | *

Typical usage would be parsing the parameters in query and convert it to the parameter placeholder that target 51 | * database supports. In addition, construct a map that contains parameter indexes by the place holder to avoid parsing 52 | * query again at {@link #onBind(BindInfo, Statement, BindOperation)}. 53 | * 54 | *

For example, convert a query that uses colon prefix for named parameters: 55 | * 56 | * "INSERT INTO names (first_name) VALUES (:FIRST_NAME)" to "INSERT INTO names (first_name) VALUES (?)", 57 | * and construct a map ":FIRST_NAME" = 0 (index). 58 | * 59 | * @param query original query 60 | * @param info contextual information for {@link io.r2dbc.spi.Connection#createStatement(String)}. 61 | * @return converted query 62 | */ 63 | default String onCreateStatement(String query, StatementInfo info) { 64 | return query; 65 | } 66 | 67 | 68 | /** 69 | * Callback method for bind operations({@code bind} and {@code bindNull}) on {@link Statement} before actually performing those methods. 70 | * 71 | * Implementation of this method can modify the actual behavior. 72 | * When calling the {@code defaultBinding.proceed()} performs the actual invocation of original bind operation, and returns proxy {@link Statement}. 73 | * To skip actual invocation of the original bind operation, simply returns {@code proxyStatement}. 74 | * 75 | * @param info contextual information for {@code bind} and {@code bindNull}. 76 | * @param proxyStatement proxy {@link Statement}. 77 | * @param defaultBinding perform default bind operations and returns a result of proxy {@link Statement} 78 | */ 79 | default void onBind(BindInfo info, Statement proxyStatement, BindOperation defaultBinding) { 80 | defaultBinding.proceed(); // just perform default behavior 81 | } 82 | 83 | /** 84 | * Represent bind operation({@code bind} and {@code bindNull}). 85 | */ 86 | @FunctionalInterface 87 | interface BindOperation { 88 | 89 | /** 90 | * Perform the bind operation. 91 | * 92 | * @return result of bind operation which is a {@link Statement}. 93 | */ 94 | Statement proceed(); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/listener/CompositeProxyExecutionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.listener; 18 | 19 | import io.r2dbc.proxy.core.MethodExecutionInfo; 20 | import io.r2dbc.proxy.core.QueryExecutionInfo; 21 | import io.r2dbc.proxy.util.Assert; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.Collection; 26 | import java.util.List; 27 | 28 | /** 29 | * Delegate to multiple of {@link ProxyExecutionListener ProxyExecutionListeners}. 30 | * 31 | * @author Tadaya Tsuyukubo 32 | */ 33 | public class CompositeProxyExecutionListener implements ProxyExecutionListener { 34 | 35 | private final List listeners = new ArrayList<>(); 36 | 37 | public CompositeProxyExecutionListener(ProxyExecutionListener... listeners) { 38 | this.listeners.addAll(Arrays.asList(listeners)); 39 | } 40 | 41 | @Override 42 | public void beforeMethod(MethodExecutionInfo executionInfo) { 43 | this.listeners.forEach(listener -> listener.beforeMethod(executionInfo)); 44 | } 45 | 46 | @Override 47 | public void afterMethod(MethodExecutionInfo executionInfo) { 48 | this.listeners.forEach(listener -> listener.afterMethod(executionInfo)); 49 | } 50 | 51 | @Override 52 | public void beforeQuery(QueryExecutionInfo execInfo) { 53 | this.listeners.forEach(listener -> listener.beforeQuery(execInfo)); 54 | } 55 | 56 | @Override 57 | public void afterQuery(QueryExecutionInfo execInfo) { 58 | this.listeners.forEach(listener -> listener.afterQuery(execInfo)); 59 | } 60 | 61 | @Override 62 | public void eachQueryResult(QueryExecutionInfo execInfo) { 63 | this.listeners.forEach(listener -> listener.eachQueryResult(execInfo)); 64 | } 65 | 66 | /** 67 | * Add a {@link ProxyExecutionListener}. 68 | * 69 | * @param listener a listener 70 | * @return {@code true} as specified by {@link List#add(Object)} 71 | * @throws IllegalArgumentException if {@code listener} is {@code null} 72 | */ 73 | public boolean add(ProxyExecutionListener listener) { 74 | Assert.requireNonNull(listener, "listener must not be null"); 75 | 76 | if (listener instanceof ProxyMethodExecutionListener) { 77 | return this.listeners.add(new ProxyMethodExecutionListenerAdapter((ProxyMethodExecutionListener) listener)); 78 | } 79 | return this.listeners.add(listener); 80 | } 81 | 82 | /** 83 | * Add a list of {@link ProxyExecutionListener}. 84 | * 85 | * @param listeners collection of listeners 86 | * @return {@code true} if this list changed as a result of the call 87 | * @throws IllegalArgumentException if {@code listeners} is {@code null} 88 | */ 89 | public boolean addAll(Collection listeners) { 90 | Assert.requireNonNull(listeners, "listeners must not be null"); 91 | boolean result = false; 92 | for (ProxyExecutionListener listener : listeners) { 93 | result |= add(listener); // perform "ProxyMethodExecutionListener" conversion 94 | } 95 | return result; 96 | } 97 | 98 | /** 99 | * Get registered listeners. 100 | * 101 | * @return registered listeners 102 | */ 103 | public List getListeners() { 104 | return this.listeners; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/listener/LastExecutionAwareListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.listener; 18 | 19 | import io.r2dbc.proxy.core.MethodExecutionInfo; 20 | import io.r2dbc.proxy.core.QueryExecutionInfo; 21 | 22 | /** 23 | * Keep the last invoked execution. 24 | * 25 | * Used for validating last execution. 26 | * 27 | * @author Tadaya Tsuyukubo 28 | */ 29 | public class LastExecutionAwareListener implements ProxyExecutionListener { 30 | 31 | private QueryExecutionInfo beforeQueryExecutionInfo; 32 | 33 | private QueryExecutionInfo afterQueryExecutionInfo; 34 | 35 | private QueryExecutionInfo eachQueryResultExecutionInfo; 36 | 37 | private MethodExecutionInfo beforeMethodExecutionInfo; 38 | 39 | private MethodExecutionInfo afterMethodExecutionInfo; 40 | 41 | @Override 42 | public void beforeQuery(QueryExecutionInfo execInfo) { 43 | this.beforeQueryExecutionInfo = execInfo; 44 | } 45 | 46 | @Override 47 | public void afterQuery(QueryExecutionInfo execInfo) { 48 | this.afterQueryExecutionInfo = execInfo; 49 | } 50 | 51 | @Override 52 | public void eachQueryResult(QueryExecutionInfo execInfo) { 53 | this.eachQueryResultExecutionInfo = execInfo; 54 | } 55 | 56 | @Override 57 | public void beforeMethod(MethodExecutionInfo executionInfo) { 58 | this.beforeMethodExecutionInfo = executionInfo; 59 | } 60 | 61 | @Override 62 | public void afterMethod(MethodExecutionInfo executionInfo) { 63 | this.afterMethodExecutionInfo = executionInfo; 64 | } 65 | 66 | /** 67 | * Get the last used {@link QueryExecutionInfo} in {@link ProxyExecutionListener#beforeQuery(QueryExecutionInfo)}. 68 | * 69 | * @return last used {@link QueryExecutionInfo}. Can be {@code null} if not invoked yet. 70 | */ 71 | public QueryExecutionInfo getBeforeQueryExecutionInfo() { 72 | return beforeQueryExecutionInfo; 73 | } 74 | 75 | /** 76 | * Get the last used {@link QueryExecutionInfo} in {@link ProxyExecutionListener#afterQuery(QueryExecutionInfo)}. 77 | * 78 | * @return last used {@link QueryExecutionInfo}. Can be {@code null} if not invoked yet. 79 | */ 80 | public QueryExecutionInfo getAfterQueryExecutionInfo() { 81 | return afterQueryExecutionInfo; 82 | } 83 | 84 | /** 85 | * Get the last used {@link QueryExecutionInfo} in {@link ProxyExecutionListener#eachQueryResult(QueryExecutionInfo)}. 86 | * 87 | * @return last used {@link QueryExecutionInfo}. Can be {@code null} if not invoked yet. 88 | */ 89 | public QueryExecutionInfo getEachQueryResultExecutionInfo() { 90 | return eachQueryResultExecutionInfo; 91 | } 92 | 93 | /** 94 | * Get the last used {@link MethodExecutionInfo} in {@link ProxyExecutionListener#beforeMethod(MethodExecutionInfo)}. 95 | * 96 | * @return last used {@link MethodExecutionInfo}. Can be {@code null} if not invoked yet. 97 | */ 98 | public MethodExecutionInfo getBeforeMethodExecutionInfo() { 99 | return beforeMethodExecutionInfo; 100 | } 101 | 102 | /** 103 | * Get the last used {@link MethodExecutionInfo} in {@link ProxyExecutionListener#afterMethod(MethodExecutionInfo)}. 104 | * 105 | * @return last used {@link MethodExecutionInfo}. Can be {@code null} if not invoked yet. 106 | */ 107 | public MethodExecutionInfo getAfterMethodExecutionInfo() { 108 | return afterMethodExecutionInfo; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/listener/ProxyExecutionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.listener; 18 | 19 | import io.r2dbc.proxy.core.MethodExecutionInfo; 20 | import io.r2dbc.proxy.core.QueryExecutionInfo; 21 | import io.r2dbc.spi.Batch; 22 | import io.r2dbc.spi.Statement; 23 | import reactor.core.publisher.Hooks; 24 | 25 | /** 26 | * Listener interface that is called when proxy is invoked. 27 | * 28 | * @author Tadaya Tsuyukubo 29 | */ 30 | public interface ProxyExecutionListener { 31 | 32 | /** 33 | * Called before every invocation of methods. 34 | *

35 | * Exception Handling: 36 | * Exceptions thrown by this method are dropped and do not affect the original subscription's publisher flow. 37 | * Such exceptions are reported to {@link Hooks#onErrorDropped(java.util.function.Consumer)}. 38 | * 39 | * @param executionInfo method execution context 40 | */ 41 | default void beforeMethod(MethodExecutionInfo executionInfo) { 42 | } 43 | 44 | /** 45 | * Called after every invocation of methods. 46 | *

47 | * Exception Handling: 48 | * Exceptions thrown by this method are dropped and do not affect the original subscription's publisher flow. 49 | * Such exceptions are reported to {@link Hooks#onErrorDropped(java.util.function.Consumer)}. 50 | * 51 | * @param executionInfo method execution context 52 | */ 53 | default void afterMethod(MethodExecutionInfo executionInfo) { 54 | } 55 | 56 | /** 57 | * Called before executing a query ({@link Batch#execute()} or {@link Statement#execute()}). 58 | *

59 | * Note: this callback is called when the publisher, result of the {@code execute()}, is being 60 | * subscribed. Not at the time of {@code execute()} is called, 61 | *

62 | * Exception Handling: 63 | * Exceptions thrown by this method are dropped and do not affect the original subscription's publisher flow. 64 | * Such exceptions are reported to {@link Hooks#onErrorDropped(java.util.function.Consumer)}. 65 | * 66 | * @param execInfo query execution context 67 | */ 68 | default void beforeQuery(QueryExecutionInfo execInfo) { 69 | } 70 | 71 | /** 72 | * Called after executing a query ({@link Batch#execute()} or {@link Statement#execute()}). 73 | *

74 | * The callback order is: 75 | *

    76 | *
  • {@link #beforeQuery(QueryExecutionInfo)} 77 | *
  • {@link #eachQueryResult(QueryExecutionInfo)} for 1st result 78 | *
  • {@link #eachQueryResult(QueryExecutionInfo)} for 2nd result 79 | *
  • ... 80 | *
  • {@link #eachQueryResult(QueryExecutionInfo)} for Nth result 81 | *
  • {@link #afterQuery(QueryExecutionInfo)} 82 | *
83 | * {@link QueryExecutionInfo#getExecuteDuration()} is available in this callback and it holds 84 | * the duration since {@link #beforeQuery(QueryExecutionInfo)}. 85 | *

86 | * Note: this callback is called when the publisher, result of the {@code execute()}, is being 87 | * subscribed. Not at the time of {@code execute()} is called, 88 | *

89 | * Exception Handling: 90 | * Exceptions thrown by this method are dropped and do not affect the original subscription's publisher flow. 91 | * Such exceptions are reported to {@link Hooks#onErrorDropped(java.util.function.Consumer)}. 92 | * 93 | * @param execInfo query execution context 94 | */ 95 | default void afterQuery(QueryExecutionInfo execInfo) { 96 | } 97 | 98 | /** 99 | * Called on processing each query {@link io.r2dbc.spi.Result}. 100 | *

101 | * While processing query results {@link io.r2dbc.spi.Result}, this callback 102 | * is called per result. 103 | *

104 | * {@link QueryExecutionInfo#getCurrentMappedResult()} contains the mapped result. 105 | *

106 | * Exception Handling: 107 | * Exceptions thrown by this method are dropped and do not affect the original subscription's publisher flow. 108 | * Such exceptions are reported to {@link Hooks#onErrorDropped(java.util.function.Consumer)}. 109 | * 110 | * @param execInfo query execution context 111 | */ 112 | default void eachQueryResult(QueryExecutionInfo execInfo) { 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/listener/ResultRowConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.listener; 19 | 20 | import io.r2dbc.spi.Row; 21 | 22 | import javax.annotation.Nullable; 23 | import java.lang.reflect.Method; 24 | 25 | /** 26 | * Callback for {@link Row Row#get(...)} methods. 27 | * 28 | * @author Tadaya Tsuyukubo 29 | * @since 0.9.0 30 | */ 31 | @FunctionalInterface 32 | public interface ResultRowConverter { 33 | 34 | /** 35 | * Create a default {@link ResultRowConverter}. 36 | * 37 | * @return a bindParameterConverter 38 | */ 39 | static ResultRowConverter create() { 40 | return (proxyRow, method, args, getOperation) -> getOperation.proceed(); 41 | } 42 | 43 | /** 44 | * Callback method for {@code Row#get(...)} before performing the original {@code Row#get(...)} method. 45 | *

46 | * Implementation of this method can modify the actual behavior. For example, the callback can modify arguments and 47 | * return value, determine whether to call the original method or alternative methods, etc. 48 | *

49 | * To perform the original {@code Row#get(...)} method, invoke {@code getOperation.proceed()}. 50 | * 51 | * @param proxyRow proxy {@link Row} 52 | * @param method invoked method 53 | * @param args arguments of the original {@code Row#get(...)} call 54 | * @param getOperation perform {@code Row#get(...)} operation and returns its result 55 | * @return return value from {@code Row#get(...)} operation 56 | */ 57 | @Nullable 58 | Object onGet(Row proxyRow, Method method, Object[] args, GetOperation getOperation); 59 | 60 | /** 61 | * Represent {@code Row#get(...)} operation. 62 | */ 63 | @FunctionalInterface 64 | interface GetOperation { 65 | 66 | /** 67 | * Perform the original {@code Row#get(...)} operation. 68 | * 69 | * @return result of the {@code Row#get(...)} operation 70 | */ 71 | @Nullable 72 | Object proceed(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/listener/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Listener classes 19 | */ 20 | 21 | @NonNullApi 22 | package io.r2dbc.proxy.listener; 23 | 24 | import reactor.util.annotation.NonNullApi; -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/observation/DefaultQueryParametersTagProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.observation; 18 | 19 | import io.r2dbc.proxy.core.Bindings; 20 | import io.r2dbc.proxy.support.QueryExecutionInfoFormatter; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * Default implementation for {@link QueryParametersTagProvider}. 26 | * 27 | * @author Tadaya Tsuyukubo 28 | * @since 1.1.0 29 | */ 30 | public class DefaultQueryParametersTagProvider implements QueryParametersTagProvider { 31 | 32 | private final QueryExecutionInfoFormatter formatter = new QueryExecutionInfoFormatter(); 33 | 34 | public String getTagValue(List bindingsList) { 35 | if (bindingsList.isEmpty()) { 36 | return "()"; 37 | } 38 | StringBuilder sb = new StringBuilder(); 39 | for (Bindings bindings : bindingsList) { 40 | sb.append("("); 41 | if (!bindings.getIndexBindings().isEmpty()) { 42 | this.formatter.onIndexBindings.accept(bindings.getIndexBindings(), sb); 43 | } else if (!bindings.getNamedBindings().isEmpty()) { 44 | this.formatter.onNamedBindings.accept(bindings.getNamedBindings(), sb); 45 | } 46 | sb.append("),"); 47 | } 48 | chompIfEndWith(sb, ','); 49 | return sb.toString(); 50 | } 51 | 52 | protected void chompIfEndWith(StringBuilder sb, char c) { 53 | final int lastCharIndex = sb.length() - 1; 54 | if (lastCharIndex >= 0 && sb.charAt(lastCharIndex) == c) { 55 | sb.deleteCharAt(lastCharIndex); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/observation/QueryContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.observation; 18 | 19 | import io.micrometer.observation.Observation; 20 | import io.micrometer.observation.transport.Kind; 21 | import io.micrometer.observation.transport.SenderContext; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * {@link Observation.Context} for r2dbc query. 28 | * 29 | * @author Tadaya Tsuyukubo 30 | * @since 1.1.0 31 | */ 32 | public class QueryContext extends SenderContext { 33 | 34 | private String connectionName; 35 | 36 | private String threadName; 37 | 38 | private List queries = new ArrayList<>(); 39 | 40 | private List params = new ArrayList<>(); 41 | 42 | public QueryContext() { 43 | super((carrier, key, value) -> { 44 | // no-op setter 45 | }, Kind.CLIENT); 46 | } 47 | 48 | public String getConnectionName() { 49 | return this.connectionName; 50 | } 51 | 52 | public void setConnectionName(String connectionName) { 53 | this.connectionName = connectionName; 54 | } 55 | 56 | public String getThreadName() { 57 | return this.threadName; 58 | } 59 | 60 | public void setThreadName(String threadName) { 61 | this.threadName = threadName; 62 | } 63 | 64 | public List getQueries() { 65 | return this.queries; 66 | } 67 | 68 | public void setQueries(List queries) { 69 | this.queries = queries; 70 | } 71 | 72 | public List getParams() { 73 | return this.params; 74 | } 75 | 76 | public void setParams(List params) { 77 | this.params = params; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/observation/QueryObservationConvention.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.observation; 18 | 19 | import io.micrometer.common.KeyValue; 20 | import io.micrometer.common.KeyValues; 21 | import io.micrometer.observation.Observation; 22 | import io.micrometer.observation.ObservationConvention; 23 | 24 | import java.util.HashSet; 25 | import java.util.Set; 26 | 27 | /** 28 | * Default {@link ObservationConvention} for R2DBC query execution. 29 | * 30 | * @author Tadaya Tsuyukubo 31 | * @since 1.1.0 32 | */ 33 | public interface QueryObservationConvention extends ObservationConvention { 34 | 35 | @Override 36 | default boolean supportsContext(Observation.Context context) { 37 | return context instanceof QueryContext; 38 | } 39 | 40 | @Override 41 | default String getName() { 42 | return "r2dbc.query"; 43 | } 44 | 45 | @Override 46 | default String getContextualName(QueryContext context) { 47 | return "query"; 48 | } 49 | 50 | @Override 51 | default KeyValues getLowCardinalityKeyValues(QueryContext context) { 52 | Set keyValues = new HashSet<>(); 53 | keyValues.add(KeyValue.of(R2dbcObservationDocumentation.LowCardinalityKeys.CONNECTION, context.getConnectionName())); 54 | keyValues.add(KeyValue.of(R2dbcObservationDocumentation.LowCardinalityKeys.THREAD, context.getThreadName())); 55 | return KeyValues.of(keyValues); 56 | } 57 | 58 | @Override 59 | default KeyValues getHighCardinalityKeyValues(QueryContext context) { 60 | Set keyValues = new HashSet<>(); 61 | for (int i = 0; i < context.getQueries().size(); i++) { 62 | String key = context.getQueries().get(i); 63 | String queryKey = String.format(R2dbcObservationDocumentation.HighCardinalityKeys.QUERY.asString(), i); 64 | keyValues.add(KeyValue.of(queryKey, key)); 65 | } 66 | // params could be empty when "includeParameterValues=false" in the listener. 67 | for (int i = 0; i < context.getParams().size(); i++) { 68 | String params = context.getParams().get(i); 69 | String key = String.format(R2dbcObservationDocumentation.HighCardinalityKeys.QUERY_PARAMETERS.asString(), i); 70 | keyValues.add(KeyValue.of(key, params)); 71 | } 72 | return KeyValues.of(keyValues); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/observation/QueryParametersTagProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.observation; 18 | 19 | import io.r2dbc.proxy.core.Bindings; 20 | 21 | import java.util.List; 22 | 23 | /** 24 | * Construct a tag value for query parameters. 25 | * 26 | * @author Tadaya Tsuyukubo 27 | * @since 1.1.0 28 | */ 29 | public interface QueryParametersTagProvider { 30 | 31 | /** 32 | * Provide query parameter values. 33 | * 34 | * @param bindingsList list of bind operations 35 | * @return string representation 36 | */ 37 | String getTagValue(List bindingsList); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/observation/R2dbcObservationDocumentation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.observation; 18 | 19 | import io.micrometer.common.docs.KeyName; 20 | import io.micrometer.observation.Observation; 21 | import io.micrometer.observation.ObservationConvention; 22 | import io.micrometer.observation.docs.ObservationDocumentation; 23 | 24 | /** 25 | * {@link ObservationConvention} for r2dbc operations. 26 | * 27 | * @author Marcin Grzejszczak 28 | * @author Tadaya Tsuyukubo 29 | * @since 1.1.0 30 | */ 31 | public enum R2dbcObservationDocumentation implements ObservationDocumentation { 32 | 33 | /** 34 | * R2DBC Query Observation. 35 | */ 36 | R2DBC_QUERY_OBSERVATION { 37 | @Override 38 | public Class> getDefaultConvention() { 39 | return QueryObservationConvention.class; 40 | } 41 | 42 | @Override 43 | public KeyName[] getLowCardinalityKeyNames() { 44 | return LowCardinalityKeys.values(); 45 | } 46 | 47 | @Override 48 | public KeyName[] getHighCardinalityKeyNames() { 49 | return HighCardinalityKeys.values(); 50 | } 51 | 52 | @Override 53 | public Observation.Event[] getEvents() { 54 | return Events.values(); 55 | } 56 | 57 | @Override 58 | public String getPrefix() { 59 | return "r2dbc."; 60 | } 61 | }; 62 | 63 | public enum Events implements Observation.Event { 64 | 65 | /** 66 | * Retrieving query result. 67 | */ 68 | QUERY_RESULT { 69 | @Override 70 | public String getName() { 71 | return "r2dbc.query_result"; 72 | } 73 | 74 | @Override 75 | public String getContextualName() { 76 | return "Query Result"; 77 | } 78 | } 79 | 80 | } 81 | 82 | public enum LowCardinalityKeys implements KeyName { 83 | 84 | /** 85 | * Name of the R2DBC connection. 86 | */ 87 | CONNECTION { 88 | @Override 89 | public String asString() { 90 | return "r2dbc.connection"; 91 | } 92 | }, 93 | 94 | /** 95 | * Name of the R2DBC thread. 96 | */ 97 | THREAD { 98 | @Override 99 | public String asString() { 100 | return "r2dbc.thread"; 101 | } 102 | } 103 | 104 | } 105 | 106 | public enum HighCardinalityKeys implements KeyName { 107 | 108 | /** 109 | * Name of the R2DBC query. 110 | */ 111 | QUERY { 112 | @Override 113 | public String asString() { 114 | return "r2dbc.query[%s]"; 115 | } 116 | }, 117 | 118 | /** 119 | * Query parameter values. 120 | */ 121 | QUERY_PARAMETERS { 122 | @Override 123 | public String asString() { 124 | return "r2dbc.params[%s]"; 125 | } 126 | } 127 | 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/observation/VirtualThreadQueryObservationConvention.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.observation; 18 | 19 | import io.micrometer.common.KeyValue; 20 | import io.micrometer.common.KeyValues; 21 | 22 | /** 23 | * An extended {@link QueryObservationConvention} that excludes the thread name from low cardinality tags. 24 | * This is particularly useful in environments using virtual threads, where thread names are always unique 25 | * and no longer suitable for low cardinality tags. 26 | * 27 | * @author Tadaya Tsuyukubo 28 | * @since 1.1.6 29 | */ 30 | public interface VirtualThreadQueryObservationConvention extends QueryObservationConvention { 31 | 32 | @Override 33 | default KeyValues getLowCardinalityKeyValues(QueryContext context) { 34 | // does not include thread-name 35 | return KeyValues.of(KeyValue.of(R2dbcObservationDocumentation.LowCardinalityKeys.CONNECTION, context.getConnectionName())); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/observation/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @NonNullApi 18 | package io.r2dbc.proxy.observation; 19 | 20 | import reactor.util.annotation.NonNullApi; 21 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @NonNullApi 18 | package io.r2dbc.proxy; 19 | 20 | import reactor.util.annotation.NonNullApi; -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/support/FormatterUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.support; 19 | 20 | import io.r2dbc.proxy.util.Assert; 21 | 22 | /** 23 | * Utility class for formatters. 24 | * 25 | * @author Tadaya Tsuyukubo 26 | */ 27 | final class FormatterUtils { 28 | 29 | private FormatterUtils() { 30 | } 31 | 32 | /** 33 | * Remove the matching {@code String} from the end of the given {@code StringBuilder} 34 | * 35 | * @param sb {@code StringBuilder} 36 | * @param s removing {@code String} from tail 37 | * @throws IllegalArgumentException if {@code sb} is {@code null} 38 | * @throws IllegalArgumentException if {@code s} is {@code null} 39 | */ 40 | public static void chompIfEndWith(StringBuilder sb, String s) { 41 | Assert.requireNonNull(sb, "sb must not be null"); 42 | Assert.requireNonNull(s, "s must not be null"); 43 | 44 | if (sb.length() < s.length()) { 45 | return; 46 | } 47 | final int startIndex = sb.length() - s.length(); 48 | if (sb.substring(startIndex, sb.length()).equals(s)) { 49 | sb.delete(startIndex, sb.length()); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/support/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Misc support classes 19 | */ 20 | 21 | @NonNullApi 22 | package io.r2dbc.proxy.support; 23 | 24 | import reactor.util.annotation.NonNullApi; -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/test/MockStatementInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.test; 19 | 20 | import io.r2dbc.proxy.core.ConnectionInfo; 21 | import io.r2dbc.proxy.core.StatementInfo; 22 | import io.r2dbc.proxy.core.ValueStore; 23 | 24 | /** 25 | * Mock implementation of {@link StatementInfo}. 26 | * 27 | * @author Tadaya Tsuyukubo 28 | */ 29 | public final class MockStatementInfo implements StatementInfo { 30 | 31 | /** 32 | * Provide a builder for {@link MockStatementInfo}. 33 | * 34 | * @return builder 35 | */ 36 | public static Builder builder() { 37 | return new Builder(); 38 | } 39 | 40 | /** 41 | * Provide an empty {@link MockStatementInfo}. 42 | * 43 | * @return a {@link MockStatementInfo} 44 | */ 45 | public static MockStatementInfo empty() { 46 | return builder().build(); 47 | } 48 | 49 | private ConnectionInfo connectionInfo; 50 | 51 | private String originalQuery; 52 | 53 | private String updatedQuery; 54 | 55 | private ValueStore valueStore; 56 | 57 | 58 | private MockStatementInfo(Builder builder) { 59 | this.connectionInfo = builder.connectionInfo; 60 | this.originalQuery = builder.originalQuery; 61 | this.updatedQuery = builder.updatedQuery; 62 | this.valueStore = builder.valueStore; 63 | } 64 | 65 | @Override 66 | public ConnectionInfo getConnectionInfo() { 67 | return this.connectionInfo; 68 | } 69 | 70 | @Override 71 | public String getOriginalQuery() { 72 | return this.originalQuery; 73 | } 74 | 75 | @Override 76 | public String getUpdatedQuery() { 77 | return this.updatedQuery; 78 | } 79 | 80 | @Override 81 | public ValueStore getValueStore() { 82 | return this.valueStore; 83 | } 84 | 85 | public static final class Builder { 86 | 87 | private ConnectionInfo connectionInfo; 88 | 89 | private String originalQuery; 90 | 91 | private String updatedQuery; 92 | 93 | private ValueStore valueStore = ValueStore.create(); 94 | 95 | public ConnectionInfo getConnectionInfo() { 96 | return this.connectionInfo; 97 | } 98 | 99 | public Builder connectionInfo(ConnectionInfo connectionInfo) { 100 | this.connectionInfo = connectionInfo; 101 | return this; 102 | } 103 | 104 | public Builder originalQuery(String originalQuery) { 105 | this.originalQuery = originalQuery; 106 | return this; 107 | } 108 | 109 | public Builder updatedQuery(String updatedQuery) { 110 | this.updatedQuery = updatedQuery; 111 | return this; 112 | } 113 | 114 | public Builder valueStore(ValueStore valueStore) { 115 | this.valueStore = valueStore; 116 | return this; 117 | } 118 | 119 | public MockStatementInfo build() { 120 | return new MockStatementInfo(this); 121 | } 122 | 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/test/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /** 19 | * Utility classes for testing 20 | */ 21 | 22 | @NonNullApi 23 | package io.r2dbc.proxy.test; 24 | 25 | import reactor.util.annotation.NonNullApi; -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/util/Assert.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.util; 18 | 19 | import reactor.util.annotation.Nullable; 20 | 21 | /** 22 | * Assertion library for the implementation. 23 | */ 24 | public final class Assert { 25 | 26 | private Assert() { 27 | } 28 | 29 | /** 30 | * Checks that a specified object reference is not {@code null} and throws a customized {@link IllegalArgumentException} if it is. 31 | * 32 | * @param t the object reference to check for nullity 33 | * @param message the detail message to be used in the event that an {@link IllegalArgumentException} is thrown 34 | * @param the type of the reference 35 | * @return {@code t} if not {@code null} 36 | * @throws IllegalArgumentException if {@code t} is {code null} 37 | */ 38 | public static T requireNonNull(@Nullable T t, String message) { 39 | if (t == null) { 40 | throw new IllegalArgumentException(message); 41 | } 42 | 43 | return t; 44 | } 45 | 46 | /** 47 | * Checks that the specified value is of a specific type. 48 | * 49 | * @param value the value to check 50 | * @param type the type to require 51 | * @param message the message to use in exception if type is not as required 52 | * @param the type being required 53 | * @return the value casted to the required type 54 | * @throws IllegalArgumentException if {@code value} is not of the required type 55 | * @throws IllegalArgumentException if {@code value}, {@code type}, or {@code message} is {@code null} 56 | */ 57 | @SuppressWarnings("unchecked") 58 | public static T requireType(Object value, Class type, String message) { 59 | requireNonNull(value, "value must not be null"); 60 | requireNonNull(type, "type must not be null"); 61 | requireNonNull(message, "message must not be null"); 62 | 63 | if (!type.isInstance(value)) { 64 | throw new IllegalArgumentException(message); 65 | } 66 | 67 | return (T) value; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/r2dbc/proxy/util/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /** 19 | * Utility code used throughout the project. 20 | */ 21 | 22 | @NonNullApi 23 | package io.r2dbc.proxy.util; 24 | 25 | import reactor.util.annotation.NonNullApi; -------------------------------------------------------------------------------- /src/main/resources/META-INF/native-image/io.r2dbc/r2dbc-proxy/native-image.properties: -------------------------------------------------------------------------------- 1 | Args = --initialize-at-build-time=io.r2dbc.spi,io.r2dbc.proxy 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider: -------------------------------------------------------------------------------- 1 | io.r2dbc.proxy.ProxyConnectionFactoryProvider -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/MockConnectionFactoryProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy; 19 | 20 | import io.r2dbc.proxy.util.Assert; 21 | import io.r2dbc.spi.ConnectionFactory; 22 | import io.r2dbc.spi.ConnectionFactoryOptions; 23 | import io.r2dbc.spi.ConnectionFactoryProvider; 24 | 25 | import java.util.ServiceLoader; 26 | import java.util.function.Function; 27 | 28 | /** 29 | * Mock implementation of {@link ConnectionFactoryProvider} for testing. 30 | * 31 | *

This class provides static access to the callbacks for implemented {@link ConnectionFactory} methods. 32 | * When this class is registered to {@link ServiceLoader} discovery mechanism, those static methods can set 33 | * behavior for the callbacks and have access to the invoked parameters. 34 | * 35 | * @author Tadaya Tsuyukubo 36 | * @see ServiceLoader 37 | */ 38 | public class MockConnectionFactoryProvider implements ConnectionFactoryProvider { 39 | 40 | private static Function DEFAULT_CREATE_CALLBACK = connectionFactoryOptions -> null; 41 | 42 | private static Function DEFAULT_SUPPORTS_CALLBACK = connectionFactoryOptions -> false; 43 | 44 | private static Function createCallback = DEFAULT_CREATE_CALLBACK; 45 | 46 | private static Function supportsCallback = DEFAULT_SUPPORTS_CALLBACK; 47 | 48 | /** 49 | * Reset the registered callbacks to the default. 50 | */ 51 | public static void reset() { 52 | createCallback = DEFAULT_CREATE_CALLBACK; 53 | supportsCallback = DEFAULT_SUPPORTS_CALLBACK; 54 | } 55 | 56 | /** 57 | * Make {@link #supports(ConnectionFactoryOptions)} always return {@code true}. 58 | */ 59 | public static void setSupportsAlways() { 60 | supportsCallback = connectionFactoryOptions -> true; 61 | } 62 | 63 | /** 64 | * Make {@link #create(ConnectionFactoryOptions)} to return the given {@link ConnectionFactory} instance. 65 | * 66 | * @param connectionFactory connectionFactory to return 67 | * @throws IllegalArgumentException if {@code connectionFactory} is {@code null} 68 | */ 69 | public static void setCreateCallbackReturn(ConnectionFactory connectionFactory) { 70 | Assert.requireNonNull(connectionFactory, "connectionFactory must not be null"); 71 | createCallback = connectionFactoryOptions -> connectionFactory; 72 | } 73 | 74 | /** 75 | * Set the callback which is invoked when {@link #supports(ConnectionFactoryOptions)} is called. 76 | * 77 | * @param callback a callback for {@link #supports(ConnectionFactoryOptions)} 78 | * @throws IllegalArgumentException if {@code callback} is {@code null} 79 | */ 80 | public static void setSupportsCallback(Function callback) { 81 | supportsCallback = Assert.requireNonNull(callback, "callback must not be null"); 82 | } 83 | 84 | /** 85 | * Set the callback which is invoked when {@link #create(ConnectionFactoryOptions)} is called. 86 | * 87 | * @param callback a callback for {@link #create(ConnectionFactoryOptions)} 88 | * @throws IllegalArgumentException if {@code callback} is {@code null} 89 | */ 90 | public static void setCreateCallback(Function callback) { 91 | createCallback = Assert.requireNonNull(callback, "callback must not be null"); 92 | } 93 | 94 | @Override 95 | public ConnectionFactory create(ConnectionFactoryOptions connectionFactoryOptions) { 96 | return createCallback.apply(connectionFactoryOptions); 97 | } 98 | 99 | @Override 100 | public boolean supports(ConnectionFactoryOptions connectionFactoryOptions) { 101 | return supportsCallback.apply(connectionFactoryOptions); 102 | } 103 | 104 | @Override 105 | public String getDriver() { 106 | return ProxyConnectionFactoryProvider.PROXY_DRIVER; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/ProxyConnectionFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy; 19 | 20 | import io.r2dbc.proxy.callback.ProxyConfig; 21 | import io.r2dbc.proxy.listener.ProxyExecutionListener; 22 | import io.r2dbc.proxy.test.MockMethodExecutionInfo; 23 | import io.r2dbc.proxy.test.MockQueryExecutionInfo; 24 | import io.r2dbc.spi.ConnectionFactory; 25 | import io.r2dbc.spi.Wrapped; 26 | import io.r2dbc.spi.test.MockConnectionFactory; 27 | import org.junit.jupiter.api.Test; 28 | import org.mockito.ArgumentCaptor; 29 | 30 | import java.util.List; 31 | import java.util.concurrent.atomic.AtomicBoolean; 32 | 33 | import static org.assertj.core.api.Assertions.assertThat; 34 | import static org.mockito.Mockito.mock; 35 | import static org.mockito.Mockito.times; 36 | import static org.mockito.Mockito.verify; 37 | 38 | /** 39 | * @author Tadaya Tsuyukubo 40 | */ 41 | public class ProxyConnectionFactoryTest { 42 | 43 | @Test 44 | void build() { 45 | ConnectionFactory connectionFactory = MockConnectionFactory.builder().build(); 46 | 47 | ProxyConnectionFactory.Builder builder = ProxyConnectionFactory.builder(connectionFactory); 48 | ConnectionFactory result = builder.build(); 49 | 50 | assertThat(result) 51 | .isNotSameAs(connectionFactory) 52 | .isInstanceOf(Wrapped.class); 53 | 54 | Object unwrapped = ((Wrapped) result).unwrap(); 55 | assertThat(unwrapped).isSameAs(connectionFactory); 56 | } 57 | 58 | @Test 59 | void builder() { 60 | ConnectionFactory connectionFactory = MockConnectionFactory.builder().build(); 61 | ProxyConfig mockProxyConfig = mock(ProxyConfig.class); 62 | 63 | AtomicBoolean onBeforeMethod = new AtomicBoolean(); 64 | AtomicBoolean onAfterMethod = new AtomicBoolean(); 65 | AtomicBoolean onBeforeQuery = new AtomicBoolean(); 66 | AtomicBoolean onAfterQuery = new AtomicBoolean(); 67 | AtomicBoolean onEachQueryResult = new AtomicBoolean(); 68 | 69 | // create a builder and register callbacks 70 | ProxyConnectionFactory.Builder builder = ProxyConnectionFactory.builder(connectionFactory, mockProxyConfig); 71 | builder.onBeforeMethod(execInfo -> onBeforeMethod.set(true)); 72 | builder.onAfterMethod(execInfo -> onAfterMethod.set(true)); 73 | builder.onBeforeQuery(execInfo -> onBeforeQuery.set(true)); 74 | builder.onAfterQuery(execInfo -> onAfterQuery.set(true)); 75 | builder.onEachQueryResult(execInfo -> onEachQueryResult.set(true)); 76 | 77 | // create an invocation captor for "proxyConfig.addListener()" 78 | ArgumentCaptor addListenerCaptor = ArgumentCaptor.forClass(ProxyExecutionListener.class); 79 | 80 | verify(mockProxyConfig, times(5)).addListener(addListenerCaptor.capture()); 81 | 82 | // at this point, none of the listener methods are invoked yet 83 | assertThat(onBeforeMethod).isFalse(); 84 | assertThat(onAfterMethod).isFalse(); 85 | assertThat(onBeforeQuery).isFalse(); 86 | assertThat(onAfterQuery).isFalse(); 87 | assertThat(onEachQueryResult).isFalse(); 88 | 89 | List listeners = addListenerCaptor.getAllValues(); 90 | 91 | // invoke first listener and verify callback is called 92 | listeners.get(0).beforeMethod(MockMethodExecutionInfo.empty()); 93 | assertThat(onBeforeMethod).isTrue(); 94 | 95 | // invoke second listener and verify callback is called 96 | listeners.get(1).afterMethod(MockMethodExecutionInfo.empty()); 97 | assertThat(onAfterMethod).isTrue(); 98 | 99 | // invoke third listener and verify callback is called 100 | listeners.get(2).beforeQuery(MockQueryExecutionInfo.empty()); 101 | assertThat(onBeforeQuery).isTrue(); 102 | 103 | // invoke fourth listener and verify callback is called 104 | listeners.get(3).afterQuery(MockQueryExecutionInfo.empty()); 105 | assertThat(onAfterQuery).isTrue(); 106 | 107 | // invoke fifth listener and verify callback is called 108 | listeners.get(4).eachQueryResult(MockQueryExecutionInfo.empty()); 109 | assertThat(onEachQueryResult).isTrue(); 110 | 111 | // getters 112 | assertThat(builder.getConnectionFactory()).isSameAs(connectionFactory); 113 | assertThat(builder.getProxyConfig()).isSameAs(mockProxyConfig); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/callback/DefaultConnectionIdManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.spi.Connection; 20 | import org.junit.jupiter.api.Test; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | import static org.mockito.Mockito.mock; 24 | 25 | /** 26 | * Test for {@link DefaultConnectionIdManager}. 27 | * 28 | * @author Tadaya Tsuyukubo 29 | */ 30 | public class DefaultConnectionIdManagerTest { 31 | 32 | @Test 33 | void getId() { 34 | DefaultConnectionIdManager idManager = new DefaultConnectionIdManager(); 35 | Connection connection = mock(Connection.class); 36 | 37 | assertThat(idManager.getId(connection)).isEqualTo("1"); 38 | assertThat(idManager.getId(connection)).isEqualTo("2"); 39 | assertThat(idManager.getId(connection)).isEqualTo("3"); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/callback/ProxyConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.callback; 19 | 20 | import io.r2dbc.proxy.listener.ProxyExecutionListener; 21 | import io.r2dbc.proxy.listener.ProxyMethodExecutionListener; 22 | import io.r2dbc.proxy.listener.ProxyMethodExecutionListenerAdapter; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import java.time.Clock; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | import static org.mockito.Mockito.mock; 29 | import static org.mockito.Mockito.when; 30 | 31 | /** 32 | * @author Tadaya Tsuyukubo 33 | */ 34 | public class ProxyConfigTest { 35 | 36 | @Test 37 | void setProxyFactoryFactory() { 38 | ProxyConfig proxyConfig = new ProxyConfig(); 39 | 40 | ProxyFactory proxyFactory = mock(ProxyFactory.class); 41 | ProxyFactoryFactory proxyFactoryFactory = mock(ProxyFactoryFactory.class); 42 | 43 | when(proxyFactoryFactory.create(proxyConfig)).thenReturn(proxyFactory); 44 | 45 | proxyConfig.setProxyFactoryFactory(proxyFactoryFactory); 46 | 47 | assertThat(proxyConfig.getProxyFactory()).isSameAs(proxyFactory); 48 | 49 | assertThat(proxyConfig.getProxyFactory()) 50 | .as("Second time calling getProxyFactory() should return same instance") 51 | .isSameAs(proxyFactory); 52 | } 53 | 54 | @Test 55 | void builder() { 56 | ConnectionIdManager connectionIdManager = mock(ConnectionIdManager.class); 57 | Clock clock = mock(Clock.class); 58 | ProxyExecutionListener listener = mock(ProxyExecutionListener.class); 59 | ProxyFactory proxyFactory = mock(ProxyFactory.class); 60 | ProxyFactoryFactory proxyFactoryFactory = config -> proxyFactory; 61 | 62 | ProxyConfig.Builder builder = ProxyConfig.builder(); 63 | builder.connectionIdManager(connectionIdManager) 64 | .clock(clock) 65 | .listener(listener) 66 | .proxyFactoryFactory(proxyFactoryFactory); 67 | 68 | ProxyConfig proxyConfig = builder.build(); 69 | 70 | assertThat(proxyConfig.getConnectionIdManager()).isSameAs(connectionIdManager); 71 | assertThat(proxyConfig.getClock()).isSameAs(clock); 72 | assertThat(proxyConfig.getListeners().getListeners()) 73 | .hasSize(1) 74 | .contains(listener); 75 | assertThat(proxyConfig.getProxyFactory()).isSameAs(proxyFactory); 76 | } 77 | 78 | @Test 79 | void builderWithDefaultValues() { 80 | ProxyConfig proxyConfig = ProxyConfig.builder().build(); 81 | 82 | assertThat(proxyConfig.getConnectionIdManager()).isNotNull(); 83 | assertThat(proxyConfig.getClock()).isNotNull(); 84 | assertThat(proxyConfig.getListeners()).isNotNull(); 85 | assertThat(proxyConfig.getProxyFactory()).isNotNull(); 86 | } 87 | 88 | @Test 89 | void builderWithProxyMethodExecutionListener() { 90 | ProxyMethodExecutionListener methodListener = mock(ProxyMethodExecutionListener.class); 91 | 92 | ProxyConfig proxyConfig = ProxyConfig.builder().listener(methodListener).build(); 93 | assertThat(proxyConfig.getListeners().getListeners()) 94 | .hasSize(1) 95 | .first() 96 | .isInstanceOfSatisfying(ProxyMethodExecutionListenerAdapter.class, listener -> { 97 | assertThat(listener.getDelegate()).isSameAs(methodListener); 98 | }); 99 | } 100 | 101 | @Test 102 | void addListener() { 103 | ProxyConfig proxyConfig = ProxyConfig.builder().build(); 104 | 105 | ProxyExecutionListener listener = mock(ProxyExecutionListener.class); 106 | proxyConfig.addListener(listener); 107 | 108 | assertThat(proxyConfig.getListeners().getListeners()) 109 | .hasSize(1) 110 | .first() 111 | .isSameAs(listener); 112 | } 113 | 114 | @Test 115 | void addListenerWithProxyMethodExecutionListener() { 116 | ProxyConfig proxyConfig = ProxyConfig.builder().build(); 117 | 118 | ProxyMethodExecutionListener methodListener = mock(ProxyMethodExecutionListener.class); 119 | proxyConfig.addListener(methodListener); 120 | 121 | assertThat(proxyConfig.getListeners().getListeners()) 122 | .hasSize(1) 123 | .first() 124 | .isInstanceOfSatisfying(ProxyMethodExecutionListenerAdapter.class, listener -> { 125 | assertThat(listener.getDelegate()).isSameAs(methodListener); 126 | }); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/callback/ProxyUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.core.StatementInfo; 20 | import io.r2dbc.spi.Batch; 21 | import io.r2dbc.spi.Connection; 22 | import io.r2dbc.spi.Result; 23 | import io.r2dbc.spi.Statement; 24 | import io.r2dbc.spi.test.MockBatch; 25 | import io.r2dbc.spi.test.MockConnection; 26 | import io.r2dbc.spi.test.MockResult; 27 | import io.r2dbc.spi.test.MockStatement; 28 | import org.junit.jupiter.api.Test; 29 | 30 | import java.util.Optional; 31 | 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | import static org.mockito.Mockito.mock; 34 | 35 | /** 36 | * @author Tadaya Tsuyukubo 37 | */ 38 | public class ProxyUtilsTest { 39 | 40 | @Test 41 | void unwrapConnection() { 42 | Connection originalConnection = MockConnection.empty(); 43 | Batch originalBatch = MockBatch.empty(); 44 | Statement originalStatement = MockStatement.empty(); 45 | Result originalResult = MockResult.empty(); 46 | StatementInfo statementInfo = mock(StatementInfo.class); 47 | 48 | String query = "QUERY"; 49 | 50 | ProxyConfig proxyConfig = new ProxyConfig(); 51 | 52 | DefaultConnectionInfo connectionInfo = new DefaultConnectionInfo(); 53 | connectionInfo.setOriginalConnection(originalConnection); 54 | 55 | Connection proxyConnection = proxyConfig.getProxyFactory().wrapConnection(originalConnection, connectionInfo); 56 | 57 | MutableQueryExecutionInfo queryExecutionInfo = new MutableQueryExecutionInfo(); 58 | queryExecutionInfo.setConnectionInfo(connectionInfo); 59 | QueriesExecutionContext queriesExecutionContext = new QueriesExecutionContext(proxyConfig.getClock()); 60 | 61 | Batch proxyBatch = proxyConfig.getProxyFactory().wrapBatch(originalBatch, connectionInfo); 62 | Statement proxyStatement = proxyConfig.getProxyFactory().wrapStatement(originalStatement, statementInfo, connectionInfo); 63 | Result proxyResult = proxyConfig.getProxyFactory().wrapResult(originalResult, queryExecutionInfo, queriesExecutionContext); 64 | 65 | Optional result; 66 | 67 | result = ProxyUtils.unwrapConnection(proxyConnection); 68 | assertThat(result).hasValue(originalConnection); 69 | 70 | result = ProxyUtils.unwrapConnection(proxyBatch); 71 | assertThat(result).hasValue(originalConnection); 72 | 73 | result = ProxyUtils.unwrapConnection(proxyStatement); 74 | assertThat(result).hasValue(originalConnection); 75 | 76 | result = ProxyUtils.unwrapConnection(proxyResult); 77 | assertThat(result).hasValue(originalConnection); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/callback/RowSegmentCallbackHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.callback; 18 | 19 | import io.r2dbc.proxy.test.MockConnectionInfo; 20 | import io.r2dbc.spi.Result; 21 | import io.r2dbc.spi.Row; 22 | import io.r2dbc.spi.Wrapped; 23 | import io.r2dbc.spi.test.MockRow; 24 | import org.junit.jupiter.api.Test; 25 | import org.springframework.util.ReflectionUtils; 26 | 27 | import java.lang.reflect.Method; 28 | 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import static org.mockito.Mockito.mock; 31 | import static org.mockito.Mockito.when; 32 | import static org.mockito.Mockito.withSettings; 33 | 34 | /** 35 | * Test for {@link RowSegmentCallbackHandler}. 36 | * 37 | * @author Tadaya Tsuyukubo 38 | */ 39 | public class RowSegmentCallbackHandlerTest { 40 | 41 | private static Method ROW_METHOD = ReflectionUtils.findMethod(Result.RowSegment.class, "row"); 42 | 43 | private static Method UNWRAP_METHOD = ReflectionUtils.findMethod(Wrapped.class, "unwrap"); 44 | 45 | private static Method GET_PROXY_CONFIG_METHOD = ReflectionUtils.findMethod(ProxyConfigHolder.class, "getProxyConfig"); 46 | 47 | 48 | @Test 49 | void row() throws Throwable { 50 | MockRow mockRow = MockRow.empty(); 51 | Result.RowSegment rowSegment = mock(Result.RowSegment.class); 52 | when(rowSegment.row()).thenReturn(mockRow); 53 | MutableQueryExecutionInfo queryExecutionInfo = new MutableQueryExecutionInfo(); 54 | queryExecutionInfo.setConnectionInfo(MockConnectionInfo.empty()); 55 | ProxyConfig proxyConfig = new ProxyConfig(); 56 | 57 | RowSegmentCallbackHandler callback = new RowSegmentCallbackHandler(rowSegment, queryExecutionInfo, proxyConfig); 58 | Object result = callback.invoke(rowSegment, ROW_METHOD, null); 59 | 60 | assertThat(result).isInstanceOf(Row.class).isInstanceOf(Wrapped.class); 61 | assertThat(((Wrapped) result).unwrap()).isSameAs(mockRow); 62 | } 63 | 64 | @Test 65 | void unwrap() throws Throwable { 66 | MockRow mockRow = MockRow.empty(); 67 | Result.RowSegment rowSegment = mock(Result.RowSegment.class); 68 | when(rowSegment.row()).thenReturn(mockRow); 69 | MutableQueryExecutionInfo queryExecutionInfo = new MutableQueryExecutionInfo(); 70 | queryExecutionInfo.setConnectionInfo(MockConnectionInfo.empty()); 71 | ProxyConfig proxyConfig = new ProxyConfig(); 72 | 73 | RowSegmentCallbackHandler callback = new RowSegmentCallbackHandler(rowSegment, queryExecutionInfo, proxyConfig); 74 | Object result = callback.invoke(rowSegment, UNWRAP_METHOD, null); 75 | 76 | assertThat(result).isSameAs(rowSegment); 77 | } 78 | 79 | @Test 80 | void unwrapToInvalid() throws Throwable { 81 | Result.RowSegment rowSegment = mock(Result.RowSegment.class, withSettings().extraInterfaces(Wrapped.class)); 82 | when(((Wrapped) rowSegment).unwrap(String.class)).thenReturn("foo"); 83 | MutableQueryExecutionInfo queryExecutionInfo = new MutableQueryExecutionInfo(); 84 | queryExecutionInfo.setConnectionInfo(MockConnectionInfo.empty()); 85 | ProxyConfig proxyConfig = new ProxyConfig(); 86 | 87 | RowSegmentCallbackHandler callback = new RowSegmentCallbackHandler(rowSegment, queryExecutionInfo, proxyConfig); 88 | Object result = callback.invoke(rowSegment, UNWRAP_METHOD, new Object[]{String.class}); 89 | 90 | assertThat(result).isEqualTo("foo"); 91 | } 92 | 93 | @Test 94 | void getProxyConfig() throws Throwable { 95 | MockRow mockRow = MockRow.empty(); 96 | Result.RowSegment rowSegment = mock(Result.RowSegment.class); 97 | when(rowSegment.row()).thenReturn(mockRow); 98 | MutableQueryExecutionInfo queryExecutionInfo = new MutableQueryExecutionInfo(); 99 | queryExecutionInfo.setConnectionInfo(MockConnectionInfo.empty()); 100 | ProxyConfig proxyConfig = new ProxyConfig(); 101 | 102 | RowSegmentCallbackHandler callback = new RowSegmentCallbackHandler(rowSegment, queryExecutionInfo, proxyConfig); 103 | 104 | Object result = callback.invoke(mockRow, GET_PROXY_CONFIG_METHOD, null); 105 | assertThat(result).isSameAs(proxyConfig); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/core/DefaultValueStoreTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.core; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 23 | 24 | /** 25 | * @author Tadaya Tsuyukubo 26 | */ 27 | public class DefaultValueStoreTest { 28 | 29 | @Test 30 | void get() { 31 | DefaultValueStore store = new DefaultValueStore(); 32 | 33 | store.put("foo", "FOO"); 34 | 35 | assertThat(store.get("foo")).isEqualTo("FOO"); 36 | assertThat(store.get("foo", String.class)).isEqualTo("FOO"); 37 | 38 | store.put("foo", "FOO-UPDATED"); 39 | assertThat(store.get("foo")).isEqualTo("FOO-UPDATED"); 40 | 41 | assertThat(store.get("bar")).isNull(); 42 | assertThat(store.get("bar", String.class)).isNull(); 43 | 44 | assertThatThrownBy(() -> store.get("foo", int.class)) 45 | .isExactlyInstanceOf(ClassCastException.class); 46 | } 47 | 48 | @Test 49 | void remove() { 50 | DefaultValueStore store = new DefaultValueStore(); 51 | 52 | store.put("foo", "FOO"); 53 | 54 | assertThat(store.remove("foo")).isEqualTo("FOO"); 55 | assertThat(store.remove("foo")).isNull(); 56 | } 57 | 58 | @Test 59 | void autoboxing() { 60 | DefaultValueStore store = new DefaultValueStore(); 61 | 62 | store.put("foo", 100); 63 | 64 | store.get("foo", Integer.class); 65 | 66 | assertThatThrownBy(() -> store.get("foo", int.class)) 67 | .isExactlyInstanceOf(ClassCastException.class); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/listener/ProxyClassesSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.listener; 18 | 19 | import io.r2dbc.spi.Batch; 20 | import io.r2dbc.spi.Connection; 21 | import io.r2dbc.spi.ConnectionFactory; 22 | import io.r2dbc.spi.Result; 23 | import io.r2dbc.spi.Row; 24 | import io.r2dbc.spi.Statement; 25 | import org.junit.jupiter.params.provider.ValueSource; 26 | 27 | import java.lang.annotation.Retention; 28 | import java.lang.annotation.RetentionPolicy; 29 | 30 | /** 31 | * Provides classes that datasource-proxy-r2dbc generates proxies. 32 | * 33 | * Composed annotation for {@link org.junit.jupiter.params.ParameterizedTest}. 34 | * 35 | * @author Tadaya Tsuyukubo 36 | */ 37 | @Retention(RetentionPolicy.RUNTIME) 38 | @ValueSource(classes = {ConnectionFactory.class, Connection.class, Batch.class, Statement.class, Result.class, Row.class}) 39 | public @interface ProxyClassesSource { 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/listener/ProxyMethodExecutionListenerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.listener; 18 | 19 | import org.junit.jupiter.params.ParameterizedTest; 20 | import org.springframework.util.StringUtils; 21 | 22 | import java.lang.reflect.Method; 23 | import java.util.Set; 24 | import java.util.stream.Stream; 25 | 26 | import static java.util.stream.Collectors.toSet; 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | /** 30 | * Test for {@link ProxyMethodExecutionListener}. 31 | * 32 | * @author Tadaya Tsuyukubo 33 | */ 34 | public class ProxyMethodExecutionListenerTest { 35 | 36 | @ParameterizedTest 37 | @ProxyClassesSource 38 | void verifyMethodNames(Class clazz) { 39 | String className = clazz.getSimpleName(); 40 | 41 | // to cover methods defined on the parent class (e.g. Row#get), use "Class#getMethods" 42 | Set expected = Stream.of(clazz.getMethods()) 43 | .filter((method) -> !method.isSynthetic()) 44 | .map(Method::getName) 45 | .flatMap(methodName -> { 46 | // beforeXxxOnYyy / afterXxxOnYyy 47 | String name = StringUtils.capitalize(methodName) + "On" + StringUtils.capitalize(className); 48 | return Stream.of("before" + name, "after" + name); 49 | }) 50 | .collect(toSet()); 51 | 52 | assertThat(ProxyMethodExecutionListener.class.getDeclaredMethods()) 53 | .extracting(Method::getName) 54 | .containsAll(expected); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/listener/ResultRowConverterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.listener; 18 | 19 | import io.r2dbc.proxy.callback.RowCallbackHandlerTest; 20 | import io.r2dbc.spi.Row; 21 | import io.r2dbc.spi.test.MockRow; 22 | import org.junit.jupiter.api.Test; 23 | import org.springframework.util.ReflectionUtils; 24 | 25 | import java.lang.reflect.Method; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | import static org.mockito.Mockito.mock; 29 | import static org.mockito.Mockito.verify; 30 | import static org.mockito.Mockito.when; 31 | 32 | /** 33 | * Test for {@link ResultRowConverter}. 34 | * 35 | * @author Tadaya Tsuyukubo 36 | * @see RowCallbackHandlerTest 37 | */ 38 | class ResultRowConverterTest { 39 | 40 | private static Method GET_BY_INDEX_METHOD = ReflectionUtils.findMethod(Row.class, "get", int.class); 41 | 42 | @Test 43 | void defaultConverter() { 44 | ResultRowConverter converter = ResultRowConverter.create(); 45 | 46 | Method method = GET_BY_INDEX_METHOD; 47 | Object[] args = new Object[0]; 48 | 49 | MockRow mockRow = MockRow.empty(); 50 | ResultRowConverter.GetOperation getOperation = mock(ResultRowConverter.GetOperation.class); 51 | when(getOperation.proceed()).thenReturn("foo"); 52 | 53 | Object result = converter.onGet(mockRow, method, args, getOperation); 54 | assertThat(result).isEqualTo("foo"); 55 | 56 | verify(getOperation).proceed(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/observation/DefaultQueryParametersTagProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.observation; 18 | 19 | import io.r2dbc.proxy.core.Bindings; 20 | import io.r2dbc.proxy.core.BoundValue; 21 | import org.junit.jupiter.api.Named; 22 | import org.junit.jupiter.params.ParameterizedTest; 23 | import org.junit.jupiter.params.provider.Arguments; 24 | import org.junit.jupiter.params.provider.MethodSource; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | import java.util.stream.Stream; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | 33 | /** 34 | * Test for {@link DefaultQueryParametersTagProvider}. 35 | * 36 | * @author Tadaya Tsuyukubo 37 | */ 38 | class DefaultQueryParametersTagProviderTest { 39 | 40 | @ParameterizedTest 41 | @MethodSource 42 | void getTagValue(List bindings, String expected) { 43 | DefaultQueryParametersTagProvider provider = new DefaultQueryParametersTagProvider(); 44 | String result = provider.getTagValue(bindings); 45 | assertThat(result).isEqualTo(expected); 46 | } 47 | 48 | 49 | static Stream getTagValue() { 50 | return Stream.of( 51 | Arguments.of(Named.of("empty", new ArrayList<>()), "()"), 52 | Arguments.of(Named.of("bindByIndexSingle", bindingsByIndexSingle()), "(foo,100)"), 53 | Arguments.of(Named.of("bindByIndexMulti", bindingsByIndexMulti()), "(foo,100),(bar,200),(baz,300)"), 54 | Arguments.of(Named.of("bindByNameSingle", bindingsByNameSingle()), "(id=100,name=foo)"), 55 | Arguments.of(Named.of("bindByNameMulti", bindingsByNameMulti()), "(id=100,name=foo),(id=200,name=bar),(id=300,name=baz)") 56 | ); 57 | } 58 | 59 | static List bindingsByIndexSingle() { 60 | Bindings first = new Bindings(); 61 | first.addIndexBinding(Bindings.indexBinding(0, BoundValue.value("foo"))); 62 | first.addIndexBinding(Bindings.indexBinding(1, BoundValue.value(100))); 63 | return Arrays.asList(first); 64 | } 65 | 66 | static List bindingsByIndexMulti() { 67 | Bindings first = new Bindings(); 68 | first.addIndexBinding(Bindings.indexBinding(0, BoundValue.value("foo"))); 69 | first.addIndexBinding(Bindings.indexBinding(1, BoundValue.value(100))); 70 | Bindings second = new Bindings(); 71 | second.addIndexBinding(Bindings.indexBinding(0, BoundValue.value("bar"))); 72 | second.addIndexBinding(Bindings.indexBinding(1, BoundValue.value(200))); 73 | Bindings third = new Bindings(); 74 | third.addIndexBinding(Bindings.indexBinding(0, BoundValue.value("baz"))); 75 | third.addIndexBinding(Bindings.indexBinding(1, BoundValue.value(300))); 76 | return Arrays.asList(first, second, third); 77 | } 78 | 79 | static List bindingsByNameSingle() { 80 | Bindings first = new Bindings(); 81 | first.addNamedBinding(Bindings.namedBinding("name", BoundValue.value("foo"))); 82 | first.addNamedBinding(Bindings.namedBinding("id", BoundValue.value(100))); 83 | return Arrays.asList(first); 84 | } 85 | 86 | static List bindingsByNameMulti() { 87 | Bindings first = new Bindings(); 88 | first.addNamedBinding(Bindings.namedBinding("name", BoundValue.value("foo"))); 89 | first.addNamedBinding(Bindings.namedBinding("id", BoundValue.value(100))); 90 | Bindings second = new Bindings(); 91 | second.addNamedBinding(Bindings.namedBinding("name", BoundValue.value("bar"))); 92 | second.addNamedBinding(Bindings.namedBinding("id", BoundValue.value(200))); 93 | Bindings third = new Bindings(); 94 | third.addNamedBinding(Bindings.namedBinding("name", BoundValue.value("baz"))); 95 | third.addNamedBinding(Bindings.namedBinding("id", BoundValue.value(300))); 96 | return Arrays.asList(first, second, third); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/observation/VirtualThreadQueryObservationConventionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.observation; 18 | 19 | import io.micrometer.common.KeyValue; 20 | import io.micrometer.common.KeyValues; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | /** 26 | * Test for {@link VirtualThreadQueryObservationConvention}. 27 | * 28 | * @author Tadaya Tsuyukubo 29 | */ 30 | class VirtualThreadQueryObservationConventionTest { 31 | 32 | VirtualThreadQueryObservationConvention convention = new VirtualThreadQueryObservationConvention() { 33 | 34 | }; 35 | 36 | @Test 37 | void keyValues() { 38 | QueryContext context = new QueryContext(); 39 | context.setConnectionName("my-connection"); 40 | context.setThreadName("my-thread"); 41 | 42 | KeyValues keyValues = convention.getLowCardinalityKeyValues(context); 43 | 44 | assertThat(keyValues).containsOnly(KeyValue.of(R2dbcObservationDocumentation.LowCardinalityKeys.CONNECTION.asString(), "my-connection")); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/support/FormatterUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package io.r2dbc.proxy.support; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | 24 | /** 25 | * @author Tadaya Tsuyukubo 26 | */ 27 | public class FormatterUtilsTest { 28 | 29 | @Test 30 | void chompIfEndWith() { 31 | StringBuilder sb; 32 | 33 | sb = new StringBuilder("fooFOOfoo"); 34 | FormatterUtils.chompIfEndWith(sb, "foo"); 35 | assertThat(sb.toString()).isEqualTo("fooFOO"); 36 | 37 | sb = new StringBuilder("fooFOOfoo"); 38 | FormatterUtils.chompIfEndWith(sb, "bar"); 39 | assertThat(sb.toString()).isEqualTo("fooFOOfoo"); 40 | 41 | sb = new StringBuilder("fooFOOfoo"); 42 | FormatterUtils.chompIfEndWith(sb, ""); 43 | assertThat(sb.toString()).isEqualTo("fooFOOfoo"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/support/MethodExecutionInfoFormatterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.support; 18 | 19 | import io.r2dbc.proxy.core.ConnectionInfo; 20 | import io.r2dbc.proxy.core.MethodExecutionInfo; 21 | import io.r2dbc.proxy.test.MockConnectionInfo; 22 | import io.r2dbc.proxy.test.MockMethodExecutionInfo; 23 | import io.r2dbc.spi.ConnectionFactory; 24 | import org.junit.jupiter.api.Test; 25 | import org.springframework.util.ReflectionUtils; 26 | 27 | import java.lang.reflect.Method; 28 | import java.time.Duration; 29 | import java.time.temporal.ChronoUnit; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | 33 | /** 34 | * @author Tadaya Tsuyukubo 35 | */ 36 | public class MethodExecutionInfoFormatterTest { 37 | 38 | @Test 39 | void withDefault() { 40 | 41 | // String#indexOf(int) method 42 | Method method = ReflectionUtils.findMethod(String.class, "indexOf", int.class); 43 | 44 | Long target = 100L; 45 | 46 | MethodExecutionInfo executionInfo = MockMethodExecutionInfo.builder() 47 | .threadId(5L) 48 | .connectionInfo(MockConnectionInfo.builder().connectionId("ABC").build()) 49 | .executeDuration(Duration.of(23, ChronoUnit.MILLIS)) 50 | .method(method) 51 | .target(target) 52 | .build(); 53 | 54 | MethodExecutionInfoFormatter formatter = MethodExecutionInfoFormatter.withDefault(); 55 | String result = formatter.format(executionInfo); 56 | 57 | assertThat(result).isEqualTo(" 1: Thread:5 Connection:ABC Time:23 Long#indexOf()"); 58 | 59 | // second time should increase the sequence 60 | result = formatter.format(executionInfo); 61 | assertThat(result).isEqualTo(" 2: Thread:5 Connection:ABC Time:23 Long#indexOf()"); 62 | 63 | } 64 | 65 | @Test 66 | void nullConnectionId() { 67 | 68 | // connection id is null for before execution of "ConnectionFactory#create" 69 | Method method = ReflectionUtils.findMethod(ConnectionFactory.class, "create"); 70 | 71 | Long target = 100L; 72 | 73 | // null ConnectionInfo 74 | MethodExecutionInfo executionInfo = MockMethodExecutionInfo.builder() 75 | .threadId(5L) 76 | .connectionInfo(null) 77 | .executeDuration(Duration.of(23, ChronoUnit.MILLIS)) 78 | .method(method) 79 | .target(target) 80 | .build(); 81 | 82 | MethodExecutionInfoFormatter formatter = MethodExecutionInfoFormatter.withDefault(); 83 | String result = formatter.format(executionInfo); 84 | 85 | assertThat(result).isEqualTo(" 1: Thread:5 Connection:n/a Time:23 Long#create()"); 86 | 87 | // null ConnectionId 88 | ConnectionInfo connectionInfo = MockConnectionInfo.builder().connectionId(null).build(); 89 | 90 | executionInfo = MockMethodExecutionInfo.builder() 91 | .threadId(5L) 92 | .connectionInfo(connectionInfo) 93 | .executeDuration(Duration.of(23, ChronoUnit.MILLIS)) 94 | .method(method) 95 | .target(target) 96 | .build(); 97 | 98 | result = formatter.format(executionInfo); 99 | 100 | assertThat(result).isEqualTo(" 2: Thread:5 Connection:n/a Time:23 Long#create()"); 101 | } 102 | 103 | @Test 104 | void customConsumer() { 105 | 106 | MethodExecutionInfo methodExecutionInfo = MockMethodExecutionInfo.empty(); 107 | 108 | MethodExecutionInfoFormatter formatter = new MethodExecutionInfoFormatter(); 109 | formatter.addConsumer((executionInfo, sb) -> { 110 | sb.append("ABC"); 111 | }); 112 | String result = formatter.format(methodExecutionInfo); 113 | 114 | assertThat(result).isEqualTo("ABC"); 115 | 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/io/r2dbc/proxy/util/ChangeLogReportGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.r2dbc.proxy.util; 18 | 19 | import java.time.Duration; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | 23 | import net.minidev.json.JSONArray; 24 | import org.springframework.hateoas.IanaLinkRelations; 25 | import org.springframework.hateoas.Links; 26 | import org.springframework.http.HttpEntity; 27 | import org.springframework.http.HttpHeaders; 28 | import org.springframework.web.reactive.function.client.WebClient; 29 | 30 | import com.jayway.jsonpath.JsonPath; 31 | 32 | /** 33 | * Changelog report generator. 34 | */ 35 | final class ChangeLogReportGenerator { 36 | 37 | private static final int MILESTONE_ID = 3; 38 | private static final String URI_TEMPLATE = "https://api.github.com/repos/r2dbc/r2dbc-proxy/issues?milestone={id}&state=closed"; 39 | 40 | public static void main(String... args) { 41 | 42 | /* 43 | * If you run into github rate limiting issues, you can always use a Github Personal Token by adding 44 | * {@code .header(HttpHeaders.AUTHORIZATION, "token your-github-token")} to the webClient call. 45 | */ 46 | 47 | WebClient webClient = WebClient.create(); 48 | 49 | HttpEntity response = webClient // 50 | .get().uri(URI_TEMPLATE, MILESTONE_ID) // 51 | .exchangeToMono(clientResponse -> clientResponse.toEntity(String.class)) // 52 | .block(Duration.ofSeconds(10)); 53 | 54 | Assert.requireNonNull(response, "response must not be null"); 55 | 56 | boolean keepChecking = true; 57 | boolean printHeader = true; 58 | 59 | while (keepChecking) { 60 | 61 | String content = response.getBody(); 62 | Assert.requireNonNull(content, "response must not be null"); 63 | 64 | readPage(content, printHeader); 65 | printHeader = false; 66 | 67 | List linksInHeader = response.getHeaders().get(HttpHeaders.LINK); 68 | Links links = linksInHeader == null ? Links.NONE : Links.parse(linksInHeader.get(0)); 69 | 70 | if (links.getLink(IanaLinkRelations.NEXT).isPresent()) { 71 | 72 | response = webClient // 73 | .get().uri(links.getRequiredLink(IanaLinkRelations.NEXT).expand().getHref()) // 74 | .exchangeToMono(clientResponse -> clientResponse.toEntity(String.class)) 75 | .block(Duration.ofSeconds(10)); 76 | 77 | Assert.requireNonNull(response, "response must not be null"); 78 | 79 | } else { 80 | keepChecking = false; 81 | } 82 | } 83 | } 84 | 85 | private static void readPage(String content, boolean header) { 86 | Assert.requireNonNull(content, "content must not be null"); 87 | 88 | JsonPath titlePath = JsonPath.compile("$[*].title"); 89 | JsonPath idPath = JsonPath.compile("$[*].number"); 90 | 91 | JSONArray titles = titlePath.read(content); 92 | Iterator ids = ((JSONArray) idPath.read(content)).iterator(); 93 | 94 | if (header) { 95 | System.out.println(JsonPath.read(content, "$[1].milestone.title").toString()); 96 | System.out.println("------------------"); 97 | } 98 | 99 | for (Object title : titles) { 100 | 101 | String format = String.format("* %s #%s", title, ids.next()); 102 | System.out.println(format.endsWith(".") ? format : format.concat(".")); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider: -------------------------------------------------------------------------------- 1 | io.r2dbc.proxy.MockConnectionFactoryProvider --------------------------------------------------------------------------------