├── .DS_Store ├── .editorconfig ├── .github ├── maven-cd-settings.xml ├── maven-ci-settings.xml └── workflows │ ├── ci-4.x.yml │ ├── ci-5.x-stable.yml │ ├── ci-5.x.yml │ ├── ci-matrix-5.x.yml │ ├── ci.yml │ └── deploy.yml ├── .gitignore ├── LICENSE ├── README.adoc ├── pom.xml └── src ├── main ├── asciidoc │ ├── index.adoc │ └── override │ │ └── rxjava2.adoc ├── generated │ └── io │ │ └── vertx │ │ └── cassandra │ │ └── CassandraClientOptionsConverter.java └── java │ ├── examples │ ├── CassandraClientExamples.java │ └── package-info.java │ ├── io │ └── vertx │ │ └── cassandra │ │ ├── CassandraClient.java │ │ ├── CassandraClientOptions.java │ │ ├── CassandraRowStream.java │ │ ├── ResultSet.java │ │ ├── impl │ │ ├── CassandraClientImpl.java │ │ ├── CassandraRowStreamImpl.java │ │ ├── ResultSetImpl.java │ │ ├── SessionHolder.java │ │ └── tracing │ │ │ ├── QueryRequest.java │ │ │ └── RequestTags.java │ │ └── package-info.java │ └── module-info.java └── test ├── java └── io │ └── vertx │ └── cassandra │ └── tests │ ├── AuthenticationTest.java │ ├── CassandraClientTestBase.java │ ├── CloseHookTest.java │ ├── ExecutionTest.java │ ├── SharedTest.java │ ├── StreamingTest.java │ ├── ThreadingCheckTest.java │ ├── TracingTest.java │ ├── VerticleWithCassandraClient.java │ └── impl │ └── SessionHolderMapCleaningTest.java └── resources ├── auth-container └── Dockerfile └── logback.xml /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vert-x3/vertx-cassandra-client/aa67874bd59a4f8e328ffaa1098b0ab4e85c28b4/.DS_Store -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true -------------------------------------------------------------------------------- /.github/maven-cd-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | vertx-snapshots-repository 24 | ${env.VERTX_NEXUS_USERNAME} 25 | ${env.VERTX_NEXUS_PASSWORD} 26 | 27 | 28 | 29 | 30 | 31 | google-mirror 32 | 33 | true 34 | 35 | 36 | 37 | google-maven-central 38 | GCS Maven Central mirror EU 39 | https://maven-central.storage-download.googleapis.com/maven2/ 40 | 41 | true 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | google-maven-central 51 | GCS Maven Central mirror 52 | https://maven-central.storage-download.googleapis.com/maven2/ 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.github/maven-ci-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | google-mirror 24 | 25 | true 26 | 27 | 28 | 29 | google-maven-central 30 | GCS Maven Central mirror EU 31 | https://maven-central.storage-download.googleapis.com/maven2/ 32 | 33 | true 34 | 35 | 36 | false 37 | 38 | 39 | 40 | 41 | 42 | google-maven-central 43 | GCS Maven Central mirror 44 | https://maven-central.storage-download.googleapis.com/maven2/ 45 | 46 | true 47 | 48 | 49 | false 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/ci-4.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-cassandra-client (4.x) 2 | on: 3 | schedule: 4 | - cron: '0 4 * * *' 5 | jobs: 6 | CI: 7 | strategy: 8 | matrix: 9 | include: 10 | - os: ubuntu-latest 11 | jdk: 8 12 | - os: ubuntu-latest 13 | jdk: 17 14 | uses: ./.github/workflows/ci.yml 15 | with: 16 | branch: 4.x 17 | jdk: ${{ matrix.jdk }} 18 | os: ${{ matrix.os }} 19 | secrets: inherit 20 | Deploy: 21 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }} 22 | needs: CI 23 | uses: ./.github/workflows/deploy.yml 24 | with: 25 | branch: 4.x 26 | jdk: 8 27 | secrets: inherit 28 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x-stable.yml: -------------------------------------------------------------------------------- 1 | name: vertx-cassandra-client (5.x-stable) 2 | on: 3 | push: 4 | branches: 5 | - '5.[0-9]+' 6 | pull_request: 7 | branches: 8 | - '5.[0-9]+' 9 | schedule: 10 | - cron: '0 6 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event_name == 'schedule' && vars.VERTX_5_STABLE_BRANCH || github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-cassandra-client (5.x) 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | schedule: 10 | - cron: '0 5 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-matrix-5.x.yml: -------------------------------------------------------------------------------- 1 | name: CI matrix (5.x) 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jobs: 9 | CI: 10 | strategy: 11 | matrix: 12 | include: 13 | - os: ubuntu-latest 14 | jdk: 11 15 | - os: ubuntu-latest 16 | jdk: 21 17 | uses: ./.github/workflows/ci.yml 18 | with: 19 | branch: ${{ inputs.branch }} 20 | jdk: ${{ matrix.jdk }} 21 | os: ${{ matrix.os }} 22 | secrets: inherit 23 | Deploy: 24 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }} 25 | needs: CI 26 | uses: ./.github/workflows/deploy.yml 27 | with: 28 | branch: ${{ inputs.branch }} 29 | jdk: 11 30 | secrets: inherit 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | os: 12 | default: ubuntu-latest 13 | type: string 14 | jobs: 15 | Test: 16 | name: Run tests 17 | runs-on: ${{ inputs.os }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Run tests 29 | run: mvn -s .github/maven-ci-settings.xml -q clean verify -B 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | jobs: 12 | Deploy: 13 | name: Deploy to OSSRH 14 | runs-on: ubuntu-latest 15 | env: 16 | VERTX_NEXUS_USERNAME: ${{ secrets.VERTX_NEXUS_USERNAME }} 17 | VERTX_NEXUS_PASSWORD: ${{ secrets.VERTX_NEXUS_PASSWORD }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Get project version 29 | run: echo "PROJECT_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout | grep -v '\[')" >> $GITHUB_ENV 30 | - name: Maven deploy 31 | if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }} 32 | run: mvn deploy -s .github/maven-cd-settings.xml -DskipTests -B 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/maven,vertx,eclipse,intellij,intellij+all,intellij+iml 3 | 4 | ### Eclipse ### 5 | 6 | .metadata 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | .recommenders 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # PyDev specific (Python IDE for Eclipse) 25 | *.pydevproject 26 | 27 | # CDT-specific (C/C++ Development Tooling) 28 | .cproject 29 | 30 | # Java annotation processor (APT) 31 | .factorypath 32 | 33 | # PDT-specific (PHP Development Tools) 34 | .buildpath 35 | 36 | # sbteclipse plugin 37 | .target 38 | 39 | # Tern plugin 40 | .tern-project 41 | 42 | # TeXlipse plugin 43 | .texlipse 44 | 45 | # STS (Spring Tool Suite) 46 | .springBeans 47 | 48 | # Code Recommenders 49 | .recommenders/ 50 | 51 | # Scala IDE specific (Scala & Java development for Eclipse) 52 | .cache-main 53 | .scala_dependencies 54 | .worksheet 55 | 56 | ### Eclipse Patch ### 57 | # Eclipse Core 58 | .project 59 | 60 | # JDT-specific (Eclipse Java Development Tools) 61 | .classpath 62 | 63 | # Annotation Processing 64 | .apt_generated 65 | 66 | ### Intellij ### 67 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 68 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 69 | 70 | # User-specific stuff: 71 | .idea/**/workspace.xml 72 | .idea/**/tasks.xml 73 | .idea/dictionaries 74 | 75 | # Sensitive or high-churn files: 76 | .idea/**/dataSources/ 77 | .idea/**/dataSources.ids 78 | .idea/**/dataSources.xml 79 | .idea/**/dataSources.local.xml 80 | .idea/**/sqlDataSources.xml 81 | .idea/**/dynamic.xml 82 | .idea/**/uiDesigner.xml 83 | 84 | # Gradle: 85 | .idea/**/gradle.xml 86 | .idea/**/libraries 87 | 88 | # CMake 89 | cmake-build-debug/ 90 | 91 | # Mongo Explorer plugin: 92 | .idea/**/mongoSettings.xml 93 | 94 | ## File-based project format: 95 | *.iws 96 | 97 | ## Plugin-specific files: 98 | 99 | # IntelliJ 100 | /out/ 101 | 102 | # mpeltonen/sbt-idea plugin 103 | .idea_modules/ 104 | 105 | # JIRA plugin 106 | atlassian-ide-plugin.xml 107 | 108 | # Cursive Clojure plugin 109 | .idea/replstate.xml 110 | 111 | # Ruby plugin and RubyMine 112 | /.rakeTasks 113 | 114 | # Crashlytics plugin (for Android Studio and IntelliJ) 115 | com_crashlytics_export_strings.xml 116 | crashlytics.properties 117 | crashlytics-build.properties 118 | fabric.properties 119 | 120 | ### Intellij Patch ### 121 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 122 | 123 | # *.iml 124 | # modules.xml 125 | # .idea/misc.xml 126 | # *.ipr 127 | 128 | # Sonarlint plugin 129 | .idea/sonarlint 130 | 131 | ### Intellij+all ### 132 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 133 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 134 | 135 | # User-specific stuff: 136 | 137 | # Sensitive or high-churn files: 138 | 139 | # Gradle: 140 | 141 | # CMake 142 | 143 | # Mongo Explorer plugin: 144 | 145 | ## File-based project format: 146 | 147 | ## Plugin-specific files: 148 | 149 | # IntelliJ 150 | 151 | # mpeltonen/sbt-idea plugin 152 | 153 | # JIRA plugin 154 | 155 | # Cursive Clojure plugin 156 | 157 | # Ruby plugin and RubyMine 158 | 159 | # Crashlytics plugin (for Android Studio and IntelliJ) 160 | 161 | ### Intellij+all Patch ### 162 | # Ignores the whole .idea folder and all .iml files 163 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 164 | 165 | .idea/ 166 | 167 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 168 | 169 | *.iml 170 | modules.xml 171 | .idea/misc.xml 172 | *.ipr 173 | 174 | ### Intellij+iml ### 175 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 176 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 177 | 178 | # User-specific stuff: 179 | 180 | # Sensitive or high-churn files: 181 | 182 | # Gradle: 183 | 184 | # CMake 185 | 186 | # Mongo Explorer plugin: 187 | 188 | ## File-based project format: 189 | 190 | ## Plugin-specific files: 191 | 192 | # IntelliJ 193 | 194 | # mpeltonen/sbt-idea plugin 195 | 196 | # JIRA plugin 197 | 198 | # Cursive Clojure plugin 199 | 200 | # Ruby plugin and RubyMine 201 | 202 | # Crashlytics plugin (for Android Studio and IntelliJ) 203 | 204 | ### Intellij+iml Patch ### 205 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 206 | 207 | 208 | ### Maven ### 209 | target/ 210 | pom.xml.tag 211 | pom.xml.releaseBackup 212 | pom.xml.versionsBackup 213 | pom.xml.next 214 | release.properties 215 | dependency-reduced-pom.xml 216 | buildNumber.properties 217 | .mvn/timing.properties 218 | 219 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 220 | !/.mvn/wrapper/maven-wrapper.jar 221 | 222 | ### Vertx ### 223 | # Vertx cache directory 224 | .vertx 225 | 226 | # End of https://www.gitignore.io/api/maven,vertx,eclipse,intellij,intellij+all,intellij+iml 227 | .vscode/ 228 | src/main/kotlin 229 | src/main/scala 230 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 The Vert.x Community 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Vert.x Cassandra client 2 | 3 | image:https://github.com/vert-x3/vertx-cassandra-client/actions/workflows/ci-5.x.yml/badge.svg["Build Status (5.x)",link="https://github.com/vert-x3/vertx-cassandra-client/actions/workflows/ci-5.x.yml"] 4 | image:https://github.com/vert-x3/vertx-cassandra-client/actions/workflows/ci-4.x.yml/badge.svg["Build Status (4.x)",link="https://github.com/vert-x3/vertx-cassandra-client/actions/workflows/ci-4.x.yml"] 5 | 6 | The Vert.x Cassandra client is an extension for interacting with http://cassandra.apache.org/[Apache Cassandra]. 7 | 8 | The client is built on top of https://github.com/datastax/java-driver[DataStax Java Driver]. 9 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.vertx 7 | vertx5-parent 8 | 12 9 | 10 | 11 | vertx-cassandra-client 12 | 5.1.0-SNAPSHOT 13 | 14 | 15 | scm:git:git@github.com:vert-x3/vertx-cassandra-client.git 16 | scm:git:git@github.com:vert-x3/vertx-cassandra-client.git 17 | git@github.com:vert-x3/vertx-cassandra-client.git 18 | 19 | 20 | 21 | 24 | 4.15.0 25 | 4.13.1 26 | 1.20.1 27 | 1.3.12 28 | 29 | false 30 | 31 | 32 | 33 | 34 | 35 | io.vertx 36 | vertx-dependencies 37 | ${project.version} 38 | pom 39 | import 40 | 41 | 42 | 43 | 44 | 45 | 46 | The Apache License, Version 2.0 47 | http://www.apache.org/licenses/LICENSE-2.0.txt 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | com.datastax.oss 56 | java-driver-core 57 | ${datastax-driver.version} 58 | 59 | 60 | org.hdrhistogram 61 | HdrHistogram 62 | 63 | 64 | com.typesafe 65 | config 66 | 67 | 68 | 69 | 70 | org.hdrhistogram 71 | HdrHistogram 72 | 2.1.12 73 | 74 | 75 | com.typesafe 76 | config 77 | 1.4.3 78 | 79 | 80 | com.datastax.oss 81 | java-driver-mapper-runtime 82 | ${datastax-driver.version} 83 | true 84 | 85 | 86 | com.datastax.oss 87 | java-driver-query-builder 88 | ${datastax-driver.version} 89 | true 90 | 91 | 92 | 93 | 94 | io.vertx 95 | vertx-core 96 | 97 | 98 | io.vertx 99 | vertx-codegen-api 100 | true 101 | 102 | 103 | io.vertx 104 | vertx-codegen-json 105 | true 106 | 107 | 108 | io.vertx 109 | vertx-docgen-api 110 | true 111 | 112 | 113 | 114 | 115 | org.slf4j 116 | slf4j-api 117 | 118 | 119 | 120 | 121 | org.testcontainers 122 | testcontainers 123 | ${testcontainers.version} 124 | test 125 | 126 | 127 | org.testcontainers 128 | cassandra 129 | ${testcontainers.version} 130 | test 131 | 132 | 133 | com.datastax.cassandra 134 | cassandra-driver-core 135 | 136 | 137 | 138 | 139 | io.vertx 140 | vertx-unit 141 | ${project.version} 142 | test 143 | 144 | 145 | junit 146 | junit 147 | ${junit.version} 148 | test 149 | 150 | 151 | ch.qos.logback 152 | logback-classic 153 | ${logback.version} 154 | test 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | maven-compiler-plugin 163 | 164 | 165 | default-compile 166 | 167 | 168 | 169 | io.vertx 170 | vertx-codegen 171 | processor 172 | 173 | 174 | io.vertx 175 | vertx-docgen-processor 176 | processor 177 | 178 | 179 | 180 | 181 | 182 | default-testCompile 183 | 184 | false 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | maven-assembly-plugin 194 | 195 | 196 | package-docs 197 | 198 | single 199 | 200 | 201 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /src/main/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Cassandra Client for Vert.x 2 | 3 | A Vert.x client allowing applications to interact with an http://cassandra.apache.org/[Apache Cassandra] service. 4 | 5 | == Getting started 6 | 7 | To use this module, add the following to the _dependencies_ section of your Maven POM file: 8 | 9 | [source,xml,subs="+attributes"] 10 | ---- 11 | 12 | io.vertx 13 | vertx-cassandra-client 14 | ${maven.version} 15 | 16 | ---- 17 | 18 | Or, if you use Gradle: 19 | 20 | [source,groovy,subs="+attributes"] 21 | ---- 22 | compile 'io.vertx:vertx-cassandra-client:${maven.version}' 23 | ---- 24 | 25 | == Creating a client 26 | 27 | === Client options 28 | 29 | Cassandra is a distributed system, and it can have many nodes. 30 | To connect to Cassandra you need to specify the addresses of some cluster nodes when creating a {@link io.vertx.cassandra.CassandraClientOptions} object: 31 | 32 | [source,$lang] 33 | ---- 34 | {@link examples.CassandraClientExamples#specifyingNodes} 35 | ---- 36 | 37 | By default, the Cassandra client for Vert.x connects to the local machine's port `9042` and is not tied to any specific keyspace. 38 | But you can set either or both of these options: 39 | 40 | [source,$lang] 41 | ---- 42 | {@link examples.CassandraClientExamples#portAndKeyspace} 43 | ---- 44 | 45 | TIP: For fine tuning purposes, {@link io.vertx.cassandra.CassandraClientOptions} exposes a `com.datastax.driver.core.Cluster.Builder` instance. 46 | 47 | === Shared clients 48 | 49 | If you deploy multiple instances of your verticle or have different verticles interacting with the same database, it is recommended to create a shared client: 50 | 51 | [source,$lang] 52 | ---- 53 | {@link examples.CassandraClientExamples#sharedClient} 54 | ---- 55 | 56 | Shared clients with the same name will use a single underlying `com.datastax.driver.core.Session`. 57 | 58 | === Client lifecycle 59 | 60 | After the client is created, it is not connected until the first query is executed. 61 | 62 | TIP: A shared client can be connected after creation if another client with the same name has already executed a query. 63 | 64 | Clients created inside a verticle are automatically stopped when the verticle is undeployed. 65 | In other words, you do not need to invoke {@link io.vertx.cassandra.CassandraClient#close} in the verticle `stop` method. 66 | 67 | In all other cases, you must manually close the client. 68 | 69 | NOTE: When a shared client is closed, the driver dession is not closed if other clients with the same name are still running. 70 | 71 | == Using the API 72 | 73 | The client API is represented by {@link io.vertx.cassandra.CassandraClient}. 74 | 75 | === Querying 76 | 77 | You can get query results using three different ways. 78 | 79 | ==== Streaming 80 | 81 | The streaming API is most appropriate when you need to consume results iteratively, e.g you want to process each item. 82 | This is very efficient specially for large amount of rows. 83 | 84 | In order to give you some inspiration and ideas on how you can use the API, we'd like to you to consider this example: 85 | 86 | [source,$lang] 87 | ---- 88 | {@link examples.CassandraClientExamples#streamingViaHttp} 89 | ---- 90 | 91 | In the example, we are executing a query, and stream results via HTTP. 92 | 93 | ==== Bulk fetching 94 | 95 | This API should be used when you need to process all the rows at the same time. 96 | 97 | [source,$lang] 98 | ---- 99 | {@link examples.CassandraClientExamples#fetchAll} 100 | ---- 101 | 102 | CAUTION: Use bulk fetching only if you can afford to load the full result set in memory. 103 | 104 | === Collector queries 105 | 106 | You can use Java collectors with the query API: 107 | 108 | [source,$lang] 109 | ---- 110 | {@link examples.CassandraClientExamples#executeAndCollect} 111 | ---- 112 | 113 | ==== Low level fetch 114 | 115 | This API provides greater control over loading at the expense of being a bit lower-level than the streaming and bulk fetching APIs. 116 | 117 | [source,$lang] 118 | ---- 119 | {@link examples.CassandraClientExamples#lowLevelQuerying} 120 | ---- 121 | 122 | === Prepared queries 123 | 124 | For security and efficiency reasons, it is a good idea to use prepared statements for all the queries you are using more than once. 125 | 126 | You can prepare a query: 127 | 128 | [source,$lang] 129 | ---- 130 | {@link examples.CassandraClientExamples#prepareQuery} 131 | ---- 132 | 133 | And then use the https://docs.datastax.com/en/drivers/java/${datastax.driver.minor.version}/com/datastax/driver/core/PreparedStatement.html[`PreparedStatement`] for all the next queries: 134 | 135 | [source,$lang] 136 | ---- 137 | {@link examples.CassandraClientExamples#usingPreparedStatementFuture} 138 | ---- 139 | 140 | === Batching 141 | 142 | In case you'd like to execute several queries at once, you can use https://docs.datastax.com/en/drivers/java/${datastax.driver.minor.version}/com/datastax/driver/core/BatchStatement.html[`BatchStatement`] for that: 143 | 144 | [source,$lang] 145 | ---- 146 | {@link examples.CassandraClientExamples#batching} 147 | ---- 148 | 149 | == Tracing queries 150 | 151 | The Cassandra Client can trace query execution when Vert.x has tracing enabled. 152 | 153 | The client reports the following _client_ spans: 154 | 155 | * `Query` operation name 156 | * tags 157 | ** `peer.address`: list of nodes known to the driver, in the form `[127_0_0_1:9042,localhost:9042,myhost_mydomain:9042]` 158 | ** `span.kind`: `client` 159 | ** `db.instance`: the keyspace 160 | ** `db.statement`: the CQL query 161 | ** `db.type`: `cassandra` 162 | 163 | The default tracing policy is {@link io.vertx.core.tracing.TracingPolicy#PROPAGATE}, the client will only create a span when involved in an active trace. 164 | 165 | You can change the client policy with {@link io.vertx.cassandra.CassandraClientOptions#setTracingPolicy}. 166 | For example, you can set {@link io.vertx.core.tracing.TracingPolicy#ALWAYS} to always report a span: 167 | 168 | [source,$lang] 169 | ---- 170 | {@link examples.CassandraClientExamples#tracing} 171 | ---- 172 | -------------------------------------------------------------------------------- /src/main/asciidoc/override/rxjava2.adoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vert-x3/vertx-cassandra-client/aa67874bd59a4f8e328ffaa1098b0ab4e85c28b4/src/main/asciidoc/override/rxjava2.adoc -------------------------------------------------------------------------------- /src/main/generated/io/vertx/cassandra/CassandraClientOptionsConverter.java: -------------------------------------------------------------------------------- 1 | package io.vertx.cassandra; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import io.vertx.core.json.JsonArray; 5 | import java.time.Instant; 6 | import java.time.format.DateTimeFormatter; 7 | 8 | /** 9 | * Converter and mapper for {@link io.vertx.cassandra.CassandraClientOptions}. 10 | * NOTE: This class has been automatically generated from the {@link io.vertx.cassandra.CassandraClientOptions} original class using Vert.x codegen. 11 | */ 12 | public class CassandraClientOptionsConverter { 13 | 14 | static void fromJson(Iterable> json, CassandraClientOptions obj) { 15 | for (java.util.Map.Entry member : json) { 16 | switch (member.getKey()) { 17 | case "contactPoints": 18 | if (member.getValue() instanceof JsonObject) { 19 | ((Iterable>)member.getValue()).forEach(entry -> { 20 | if (entry.getValue() instanceof Number) 21 | obj.addContactPoint(entry.getKey(), ((Number)entry.getValue()).intValue()); 22 | }); 23 | } 24 | break; 25 | case "keyspace": 26 | if (member.getValue() instanceof String) { 27 | obj.setKeyspace((String)member.getValue()); 28 | } 29 | break; 30 | case "username": 31 | if (member.getValue() instanceof String) { 32 | obj.setUsername((String)member.getValue()); 33 | } 34 | break; 35 | case "password": 36 | if (member.getValue() instanceof String) { 37 | obj.setPassword((String)member.getValue()); 38 | } 39 | break; 40 | case "tracingPolicy": 41 | if (member.getValue() instanceof String) { 42 | obj.setTracingPolicy(io.vertx.core.tracing.TracingPolicy.valueOf((String)member.getValue())); 43 | } 44 | break; 45 | } 46 | } 47 | } 48 | 49 | static void toJson(CassandraClientOptions obj, JsonObject json) { 50 | toJson(obj, json.getMap()); 51 | } 52 | 53 | static void toJson(CassandraClientOptions obj, java.util.Map json) { 54 | if (obj.getKeyspace() != null) { 55 | json.put("keyspace", obj.getKeyspace()); 56 | } 57 | if (obj.getUsername() != null) { 58 | json.put("username", obj.getUsername()); 59 | } 60 | if (obj.getPassword() != null) { 61 | json.put("password", obj.getPassword()); 62 | } 63 | if (obj.getTracingPolicy() != null) { 64 | json.put("tracingPolicy", obj.getTracingPolicy().name()); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/examples/CassandraClientExamples.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | package examples; 17 | 18 | import com.datastax.oss.driver.api.core.cql.*; 19 | import io.vertx.cassandra.CassandraClient; 20 | import io.vertx.cassandra.CassandraClientOptions; 21 | import io.vertx.cassandra.CassandraRowStream; 22 | import io.vertx.cassandra.ResultSet; 23 | import io.vertx.core.Vertx; 24 | import io.vertx.core.http.HttpServerResponse; 25 | import io.vertx.core.tracing.TracingPolicy; 26 | 27 | import java.util.List; 28 | import java.util.stream.Collector; 29 | 30 | public class CassandraClientExamples { 31 | 32 | public void specifyingNodes(Vertx vertx) { 33 | CassandraClientOptions options = new CassandraClientOptions() 34 | .addContactPoint("node1.address", 9142) 35 | .addContactPoint("node2.address", 9142) 36 | .addContactPoint("node3.address", 9142); 37 | CassandraClient client = CassandraClient.create(vertx, options); 38 | } 39 | 40 | public void portAndKeyspace(Vertx vertx) { 41 | CassandraClientOptions options = new CassandraClientOptions() 42 | .addContactPoint("localhost", 9142) 43 | .setKeyspace("my_keyspace"); 44 | CassandraClient client = CassandraClient.create(vertx, options); 45 | } 46 | 47 | public void sharedClient(Vertx vertx) { 48 | CassandraClientOptions options = new CassandraClientOptions() 49 | .addContactPoint("node1.address", 9142) 50 | .addContactPoint("node2.address", 9142) 51 | .addContactPoint("node3.address", 9142) 52 | .setKeyspace("my_keyspace"); 53 | CassandraClient client = CassandraClient.createShared(vertx, "sharedClientName", options); 54 | } 55 | 56 | public void lowLevelQuerying(CassandraClient cassandraClient) { 57 | cassandraClient.execute("SELECT * FROM my_keyspace.my_table where my_key = 'my_value'") 58 | .onComplete(execute -> { 59 | if (execute.succeeded()) { 60 | ResultSet resultSet = execute.result(); 61 | 62 | if (resultSet.remaining() != 0) { 63 | Row row = resultSet.one(); 64 | System.out.println("One row successfully fetched"); 65 | } else if (!resultSet.hasMorePages()) { 66 | System.out.println("No pages to fetch"); 67 | } else { 68 | resultSet.fetchNextPage().onComplete(fetchMoreResults -> { 69 | if (fetchMoreResults.succeeded()) { 70 | int availableWithoutFetching = resultSet.remaining(); 71 | System.out.println("Now we have " + availableWithoutFetching + " rows fetched, but not consumed!"); 72 | } else { 73 | System.out.println("Unable to fetch more results"); 74 | fetchMoreResults.cause().printStackTrace(); 75 | } 76 | }); 77 | } 78 | } else { 79 | System.out.println("Unable to execute the query"); 80 | execute.cause().printStackTrace(); 81 | } 82 | }); 83 | } 84 | 85 | public void executeAndCollect(CassandraClient cassandraClient, Collector listCollector) { 86 | // Run the query with the collector 87 | cassandraClient.execute("SELECT * FROM users", listCollector) 88 | .onComplete(ar -> { 89 | if (ar.succeeded()) { 90 | // Get the string created by the collector 91 | String list = ar.result(); 92 | System.out.println("Got " + list); 93 | } else { 94 | System.out.println("Failure: " + ar.cause().getMessage()); 95 | } 96 | }); 97 | } 98 | 99 | public void streamingViaHttp(Vertx vertx, CassandraClient cassandraClient, HttpServerResponse response) { 100 | cassandraClient.queryStream("SELECT my_string_col FROM my_keyspace.my_table where my_key = 'my_value'") 101 | .onComplete(queryStream -> { 102 | if (queryStream.succeeded()) { 103 | CassandraRowStream stream = queryStream.result(); 104 | 105 | // resume stream when queue is ready to accept buffers again 106 | response.drainHandler(v -> stream.resume()); 107 | 108 | stream.handler(row -> { 109 | String value = row.getString("my_string_col"); 110 | response.write(value); 111 | 112 | // pause row stream when we buffer queue is full 113 | if (response.writeQueueFull()) { 114 | stream.pause(); 115 | } 116 | }); 117 | 118 | // end request when we reached end of the stream 119 | stream.endHandler(end -> response.end()); 120 | 121 | } else { 122 | queryStream.cause().printStackTrace(); 123 | // response with internal server error if we are not able to execute given query 124 | response 125 | .setStatusCode(500) 126 | .end("Unable to execute the query"); 127 | } 128 | }); 129 | } 130 | 131 | public void fetchAll(CassandraClient cassandraClient) { 132 | cassandraClient.executeWithFullFetch("SELECT * FROM my_keyspace.my_table where my_key = 'my_value'") 133 | .onComplete(executeWithFullFetch -> { 134 | if (executeWithFullFetch.succeeded()) { 135 | List rows = executeWithFullFetch.result(); 136 | for (Row row : rows) { 137 | // handle each row here 138 | } 139 | } else { 140 | System.out.println("Unable to execute the query"); 141 | executeWithFullFetch.cause().printStackTrace(); 142 | } 143 | }); 144 | } 145 | 146 | public void prepareQuery(CassandraClient cassandraClient) { 147 | cassandraClient.prepare("SELECT * FROM my_keyspace.my_table where my_key = ? ") 148 | .onComplete(preparedStatementResult -> { 149 | if (preparedStatementResult.succeeded()) { 150 | System.out.println("The query has successfully been prepared"); 151 | PreparedStatement preparedStatement = preparedStatementResult.result(); 152 | // now you can use this PreparedStatement object for the next queries 153 | } else { 154 | System.out.println("Unable to prepare the query"); 155 | preparedStatementResult.cause().printStackTrace(); 156 | } 157 | }); 158 | } 159 | 160 | public void usingPreparedStatementFuture(CassandraClient cassandraClient, PreparedStatement preparedStatement) { 161 | // You can execute you prepared statement using any way to execute queries. 162 | 163 | // Low level fetch API 164 | cassandraClient.execute(preparedStatement.bind("my_value")) 165 | .onComplete(done -> { 166 | ResultSet results = done.result(); 167 | // handle results here 168 | }); 169 | 170 | // Bulk fetching API 171 | cassandraClient.executeWithFullFetch(preparedStatement.bind("my_value")) 172 | .onComplete(done -> { 173 | List results = done.result(); 174 | // handle results here 175 | }); 176 | 177 | // Streaming API 178 | cassandraClient.queryStream(preparedStatement.bind("my_value")) 179 | .onComplete(done -> { 180 | CassandraRowStream results = done.result(); 181 | // handle results here 182 | }); 183 | } 184 | 185 | public void batching(CassandraClient cassandraClient) { 186 | BatchStatement batchStatement = BatchStatement.newInstance(BatchType.LOGGED) 187 | .add(SimpleStatement.newInstance("INSERT INTO NAMES (name) VALUES ('Pavel')")) 188 | .add(SimpleStatement.newInstance("INSERT INTO NAMES (name) VALUES ('Thomas')")) 189 | .add(SimpleStatement.newInstance("INSERT INTO NAMES (name) VALUES ('Julien')")); 190 | 191 | cassandraClient 192 | .execute(batchStatement) 193 | .onComplete(result -> { 194 | if (result.succeeded()) { 195 | System.out.println("The given batch executed successfully"); 196 | } else { 197 | System.out.println("Unable to execute the batch"); 198 | result.cause().printStackTrace(); 199 | } 200 | }); 201 | } 202 | 203 | public void tracing() { 204 | CassandraClientOptions options = new CassandraClientOptions() 205 | .setTracingPolicy(TracingPolicy.ALWAYS); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/main/java/examples/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | @Source 17 | package examples; 18 | 19 | import io.vertx.docgen.Source; 20 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/CassandraClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | package io.vertx.cassandra; 17 | 18 | import com.datastax.oss.driver.api.core.cql.PreparedStatement; 19 | import com.datastax.oss.driver.api.core.cql.Row; 20 | import com.datastax.oss.driver.api.core.cql.SimpleStatement; 21 | import com.datastax.oss.driver.api.core.cql.Statement; 22 | import com.datastax.oss.driver.api.core.metadata.Metadata; 23 | import io.vertx.cassandra.impl.CassandraClientImpl; 24 | import io.vertx.codegen.annotations.GenIgnore; 25 | import io.vertx.codegen.annotations.VertxGen; 26 | import io.vertx.core.Future; 27 | import io.vertx.core.Vertx; 28 | 29 | import java.util.List; 30 | import java.util.UUID; 31 | import java.util.stream.Collector; 32 | 33 | /** 34 | * Eclipse Vert.x Cassandra client. 35 | * 36 | * @author Pavel Drankou 37 | * @author Thomas Segismont 38 | */ 39 | @VertxGen 40 | public interface CassandraClient { 41 | 42 | /** 43 | * The default shared client name. 44 | */ 45 | String DEFAULT_SHARED_CLIENT_NAME = "DEFAULT"; 46 | 47 | /** 48 | * Like {@link CassandraClient#create(Vertx, CassandraClientOptions)} with default options. 49 | */ 50 | static CassandraClient create(Vertx vertx) { 51 | return create(vertx, new CassandraClientOptions()); 52 | } 53 | 54 | /** 55 | * Create a Cassandra client which maintains its own driver session. 56 | *

57 | * It is not recommended to create several non shared clients in an application. 58 | * 59 | * @param vertx the Vert.x instance 60 | * @param options the options 61 | * @return the client 62 | */ 63 | static CassandraClient create(Vertx vertx, CassandraClientOptions options) { 64 | return new CassandraClientImpl(vertx, UUID.randomUUID().toString(), options); 65 | } 66 | 67 | /** 68 | * Like {@link CassandraClient#createShared(Vertx, String, CassandraClientOptions)} with default options and client name. 69 | */ 70 | static CassandraClient createShared(Vertx vertx) { 71 | return createShared(vertx, DEFAULT_SHARED_CLIENT_NAME); 72 | } 73 | 74 | /** 75 | * Like {@link CassandraClient#createShared(Vertx, String, CassandraClientOptions)} with default options. 76 | */ 77 | static CassandraClient createShared(Vertx vertx, String clientName) { 78 | return createShared(vertx, clientName, new CassandraClientOptions()); 79 | } 80 | 81 | /** 82 | * Like {@link CassandraClient#createShared(Vertx, String, CassandraClientOptions)} with default client name. 83 | */ 84 | static CassandraClient createShared(Vertx vertx, CassandraClientOptions options) { 85 | return createShared(vertx, DEFAULT_SHARED_CLIENT_NAME, options); 86 | } 87 | 88 | /** 89 | * Create a Cassandra client that shares its driver session with any other client having the same name. 90 | * 91 | * @param vertx the Vert.x instance 92 | * @param options the options 93 | * @param clientName the shared client name 94 | * @return the client 95 | */ 96 | static CassandraClient createShared(Vertx vertx, String clientName, CassandraClientOptions options) { 97 | return new CassandraClientImpl(vertx, clientName, options); 98 | } 99 | 100 | /** 101 | * @return whether this Cassandra client instance is connected 102 | */ 103 | boolean isConnected(); 104 | 105 | /** 106 | * Execute the query and provide a handler for consuming results. 107 | * 108 | * @param query the query to execute 109 | * @return a future of the result 110 | */ 111 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 112 | Future> executeWithFullFetch(String query); 113 | 114 | /** 115 | * Execute the query and provide a handler for consuming results. 116 | * 117 | * @param statement the statement to execute 118 | * @return a future of the result 119 | */ 120 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 121 | Future> executeWithFullFetch(Statement statement); 122 | 123 | /** 124 | * Execute the query and provide a handler for consuming results. 125 | * 126 | * @param query the query to execute 127 | * @return a future of the result 128 | */ 129 | Future execute(String query); 130 | 131 | /** 132 | * Execute a query and produce a result by applying a collector to result set rows. 133 | * 134 | * @param query the query to execute 135 | * @param collector the collector to use to produce a result 136 | * @param the result type 137 | * @return a future of the result 138 | */ 139 | @GenIgnore 140 | Future execute(String query, Collector collector); 141 | 142 | /** 143 | * Execute the statement and provide a handler for consuming results. 144 | * 145 | * @param statement the statement to execute 146 | * @return a future of the result 147 | */ 148 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 149 | Future execute(Statement statement); 150 | 151 | /** 152 | * Execute a statement and produce a result by applying a collector to result set rows. 153 | * 154 | * @param statement the statement to execute 155 | * @param collector the collector to use to produce a result 156 | * @param the result type 157 | * @return a future of the result 158 | */ 159 | @GenIgnore 160 | Future execute(Statement statement, Collector collector); 161 | 162 | /** 163 | * Prepares the provided query string. 164 | * 165 | * @param query the query to prepare 166 | * @return a future of the result 167 | */ 168 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 169 | Future prepare(String query); 170 | 171 | /** 172 | * Prepares the provided a {@link SimpleStatement}. 173 | * 174 | * @param statement the statement to prepare 175 | * @return a future of the result 176 | */ 177 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 178 | Future prepare(SimpleStatement statement); 179 | 180 | /** 181 | * Executes the given SQL SELECT statement which returns the results of the query as a read stream. 182 | * 183 | * @param sql the SQL to execute. For example SELECT * FROM table .... 184 | * @return a future of the result 185 | */ 186 | Future queryStream(String sql); 187 | 188 | /** 189 | * Executes the given SQL statement which returns the results of the query as a read stream. 190 | * 191 | * @param statement the statement to execute. 192 | * @return a future of the result 193 | */ 194 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 195 | Future queryStream(Statement statement); 196 | 197 | /** 198 | * Closes this client. 199 | * 200 | * @return a future of the result 201 | */ 202 | Future close(); 203 | 204 | /** 205 | * Get {@link Metadata} for the session. 206 | * 207 | * @return a future of the result 208 | */ 209 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 210 | Future metadata(); 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/CassandraClientOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | package io.vertx.cassandra; 17 | 18 | import com.datastax.oss.driver.api.core.CqlSession; 19 | import com.datastax.oss.driver.api.core.CqlSessionBuilder; 20 | import io.vertx.codegen.annotations.DataObject; 21 | import io.vertx.codegen.json.annotations.JsonGen; 22 | import io.vertx.core.json.JsonObject; 23 | import io.vertx.core.tracing.TracingPolicy; 24 | 25 | import java.net.InetSocketAddress; 26 | 27 | /** 28 | * Eclipse Vert.x Cassandra client options. 29 | * 30 | * @author Pavel Drankou 31 | * @author Thomas Segismont 32 | */ 33 | @DataObject 34 | @JsonGen(publicConverter = false) 35 | public class CassandraClientOptions { 36 | 37 | /** 38 | * Default port for connecting with Cassandra service. 39 | */ 40 | public static final int DEFAULT_PORT = 9042; 41 | 42 | /** 43 | * Default host for connecting with Cassandra service. 44 | */ 45 | public static final String DEFAULT_HOST = "localhost"; 46 | 47 | private final CqlSessionBuilder builder; 48 | 49 | private String keyspace; 50 | private String username; 51 | private String password; 52 | private TracingPolicy tracingPolicy; 53 | 54 | /** 55 | * Default constructor. 56 | */ 57 | public CassandraClientOptions() { 58 | this(CqlSession.builder()); 59 | } 60 | 61 | /** 62 | * Copy constructor. 63 | * @param other The other client to copy from. 64 | */ 65 | public CassandraClientOptions(CassandraClientOptions other) { 66 | this(other.dataStaxClusterBuilder()); 67 | this.setKeyspace(other.getKeyspace()); 68 | tracingPolicy = other.tracingPolicy; 69 | } 70 | 71 | /** 72 | * Constructor using an existing {@link CqlSessionBuilder} instance. 73 | */ 74 | public CassandraClientOptions(CqlSessionBuilder builder) { 75 | this.builder = builder; 76 | this.tracingPolicy = TracingPolicy.PROPAGATE; 77 | } 78 | 79 | /** 80 | * Constructor to create options from JSON. 81 | * 82 | * @param json the JSON 83 | */ 84 | public CassandraClientOptions(JsonObject json) { 85 | this(); 86 | CassandraClientOptionsConverter.fromJson(json, this); 87 | } 88 | 89 | /** 90 | * @return a JSON representation of these options 91 | */ 92 | public JsonObject toJson() { 93 | JsonObject json = new JsonObject(); 94 | CassandraClientOptionsConverter.toJson(this, json); 95 | return json; 96 | } 97 | 98 | /** 99 | * Adds a contact point to use for the initial connection to the cluster 100 | * 101 | * @param address the address 102 | * @return a reference to this, so the API can be used fluently 103 | */ 104 | public CassandraClientOptions addContactPoint(InetSocketAddress address) { 105 | builder.addContactPoint(address); 106 | return this; 107 | } 108 | 109 | /** 110 | * Adds a contact point to use for the initial connection to the cluster 111 | * 112 | * @param host the address 113 | * @param port the port 114 | * @return a reference to this, so the API can be used fluently 115 | */ 116 | public CassandraClientOptions addContactPoint(String host, int port) { 117 | builder.addContactPoint(InetSocketAddress.createUnresolved(host, port)); 118 | return this; 119 | } 120 | 121 | /** 122 | * @return a cluster builder, which will be used by the client 123 | */ 124 | public CqlSessionBuilder dataStaxClusterBuilder() { 125 | return builder; 126 | } 127 | 128 | /** 129 | * @return the keyspace to use when creating the Cassandra session 130 | */ 131 | public String getKeyspace() { 132 | return keyspace; 133 | } 134 | 135 | /** 136 | * Set the keyspace to use when creating the Cassandra session. Defaults to {@code null}. 137 | * 138 | * @param keyspace the keyspace to use when creating the Cassandra session 139 | * 140 | * @return a reference to this, so the API can be used fluently 141 | */ 142 | public CassandraClientOptions setKeyspace(String keyspace) { 143 | this.keyspace = keyspace; 144 | builder.withKeyspace(keyspace); 145 | return this; 146 | } 147 | 148 | /** 149 | * @return the username if plaintext authentication is used 150 | */ 151 | public String getUsername() { 152 | return username; 153 | } 154 | 155 | /** 156 | * Set the username for plaintext authentication. Defaults to {@code null}. 157 | * 158 | * @param username the username for plaintext authentication 159 | * @return a reference to this, so the API can be used fluently 160 | */ 161 | public CassandraClientOptions setUsername(String username) { 162 | this.username = username; 163 | setAuth(); 164 | return this; 165 | } 166 | 167 | private void setAuth() { 168 | if (username == null || password == null) { 169 | builder.withAuthProvider(null); 170 | } else { 171 | builder.withAuthCredentials(username, password); 172 | } 173 | } 174 | 175 | /** 176 | * @return the password if plaintext authentication is used 177 | */ 178 | public String getPassword() { 179 | return password; 180 | } 181 | 182 | /** 183 | * Set the password for plaintext authentication. Defaults to {@code null}. 184 | * 185 | * @param password the username for plaintext authentication 186 | * @return a reference to this, so the API can be used fluently 187 | */ 188 | public CassandraClientOptions setPassword(String password) { 189 | this.password = password; 190 | setAuth(); 191 | return this; 192 | } 193 | 194 | /** 195 | * @return the tracing policy 196 | */ 197 | public TracingPolicy getTracingPolicy() { 198 | return tracingPolicy; 199 | } 200 | 201 | /** 202 | * Set the tracing policy for the client behavior when Vert.x has tracing enabled. 203 | * 204 | * @param tracingPolicy the tracing policy 205 | * @return a reference to this, so the API can be used fluently 206 | */ 207 | public CassandraClientOptions setTracingPolicy(TracingPolicy tracingPolicy) { 208 | this.tracingPolicy = tracingPolicy; 209 | return this; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/CassandraRowStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | package io.vertx.cassandra; 17 | 18 | import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; 19 | import com.datastax.oss.driver.api.core.cql.ExecutionInfo; 20 | import com.datastax.oss.driver.api.core.cql.Row; 21 | import io.vertx.codegen.annotations.GenIgnore; 22 | import io.vertx.codegen.annotations.VertxGen; 23 | import io.vertx.core.AsyncResult; 24 | import io.vertx.core.Future; 25 | import io.vertx.core.Handler; 26 | import io.vertx.core.streams.ReadStream; 27 | import io.vertx.core.streams.WriteStream; 28 | 29 | /** 30 | * A {@link ReadStream} for {@link Row} consumption. 31 | * 32 | * @author Pavel Drankou 33 | * @author Thomas Segismont 34 | */ 35 | @VertxGen 36 | public interface CassandraRowStream extends ReadStream { 37 | 38 | @Override 39 | CassandraRowStream exceptionHandler(Handler handler); 40 | 41 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 42 | @Override 43 | CassandraRowStream handler(Handler handler); 44 | 45 | @Override 46 | CassandraRowStream pause(); 47 | 48 | @Override 49 | CassandraRowStream resume(); 50 | 51 | @Override 52 | CassandraRowStream endHandler(Handler handler); 53 | 54 | @Override 55 | CassandraRowStream fetch(long l); 56 | 57 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 58 | @Override 59 | default Future pipeTo(WriteStream dst) { 60 | return ReadStream.super.pipeTo(dst); 61 | } 62 | 63 | /** 64 | * Get the {@link ExecutionInfo} provided by the backing {@link ResultSet} for this stream. 65 | * 66 | * @returns the executionInfo 67 | */ 68 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 69 | ExecutionInfo executionInfo(); 70 | 71 | 72 | /** 73 | * Get the {@link ColumnDefinitions} provided by the backing {@link ResultSet} for this stream. 74 | * 75 | * @returns the columnDefinitions 76 | */ 77 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 78 | ColumnDefinitions columnDefinitions(); 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/ResultSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | package io.vertx.cassandra; 17 | 18 | import com.datastax.oss.driver.api.core.cql.AsyncResultSet; 19 | import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; 20 | import com.datastax.oss.driver.api.core.cql.ExecutionInfo; 21 | import com.datastax.oss.driver.api.core.cql.Row; 22 | import io.vertx.codegen.annotations.GenIgnore; 23 | import io.vertx.codegen.annotations.VertxGen; 24 | import io.vertx.core.Future; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * It is like {@link com.datastax.oss.driver.api.core.cql.AsyncResultSet}, but adapted for Vert.x. 30 | * 31 | * @author Pavel Drankou 32 | * @author Thomas Segismont 33 | */ 34 | @VertxGen 35 | public interface ResultSet { 36 | 37 | /** 38 | * The method should not be used concurrently with others like {@link #fetchNextPage()} or {@link #one()}. 39 | * This may lead to unexpected result. 40 | * 41 | * @return a future notified all the rows are fetched 42 | */ 43 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 44 | Future> all(); 45 | 46 | /** 47 | * @see AsyncResultSet#getColumnDefinitions() 48 | */ 49 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 50 | ColumnDefinitions getColumnDefinitions(); 51 | 52 | /** 53 | * @see AsyncResultSet#getExecutionInfo() 54 | */ 55 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 56 | ExecutionInfo getExecutionInfo(); 57 | 58 | /** 59 | * @see AsyncResultSet#remaining() 60 | */ 61 | int remaining(); 62 | 63 | /** 64 | * @see AsyncResultSet#currentPage() 65 | */ 66 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 67 | Iterable currentPage(); 68 | 69 | /** 70 | * @see AsyncResultSet#one() 71 | */ 72 | @GenIgnore(GenIgnore.PERMITTED_TYPE) 73 | Row one(); 74 | 75 | /** 76 | * @see AsyncResultSet#hasMorePages() 77 | */ 78 | boolean hasMorePages(); 79 | 80 | /** 81 | * @see AsyncResultSet#wasApplied() 82 | */ 83 | Future fetchNextPage() throws IllegalStateException; 84 | 85 | /** 86 | * @see AsyncResultSet#wasApplied() 87 | */ 88 | boolean wasApplied(); 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/impl/CassandraClientImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | package io.vertx.cassandra.impl; 17 | 18 | import com.datastax.oss.driver.api.core.CqlSession; 19 | import com.datastax.oss.driver.api.core.CqlSessionBuilder; 20 | import com.datastax.oss.driver.api.core.cql.*; 21 | import com.datastax.oss.driver.api.core.metadata.Metadata; 22 | import com.datastax.oss.driver.api.core.session.Session; 23 | import io.vertx.cassandra.CassandraClient; 24 | import io.vertx.cassandra.CassandraClientOptions; 25 | import io.vertx.cassandra.CassandraRowStream; 26 | import io.vertx.cassandra.ResultSet; 27 | import io.vertx.cassandra.impl.tracing.QueryRequest; 28 | import io.vertx.core.*; 29 | import io.vertx.core.internal.ContextInternal; 30 | import io.vertx.core.internal.VertxInternal; 31 | import io.vertx.core.spi.tracing.SpanKind; 32 | import io.vertx.core.spi.tracing.TagExtractor; 33 | import io.vertx.core.spi.tracing.VertxTracer; 34 | 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.Objects; 38 | import java.util.function.BiConsumer; 39 | import java.util.function.Function; 40 | import java.util.stream.Collector; 41 | 42 | import static io.vertx.cassandra.impl.tracing.RequestTags.REQUEST_TAG_EXTRACTOR; 43 | 44 | /** 45 | * @author Pavel Drankou 46 | * @author Thomas Segismont 47 | */ 48 | public class CassandraClientImpl implements CassandraClient { 49 | 50 | public static final String HOLDERS_LOCAL_MAP_NAME = "__vertx.cassandraClient.sessionHolders"; 51 | 52 | final VertxInternal vertx; 53 | private final VertxTracer tracer; 54 | private final String clientName; 55 | private final CassandraClientOptions options; 56 | private final Map holders; 57 | private final ContextInternal creatingContext; 58 | 59 | private boolean closed; 60 | 61 | public CassandraClientImpl(Vertx vertx, String clientName, CassandraClientOptions options) { 62 | Objects.requireNonNull(vertx, "vertx"); 63 | Objects.requireNonNull(clientName, "clientName"); 64 | Objects.requireNonNull(options, "options"); 65 | this.vertx = (VertxInternal) vertx; 66 | this.tracer = ((VertxInternal) vertx).tracer(); 67 | this.clientName = clientName; 68 | this.options = options; 69 | this.creatingContext = ((VertxInternal) vertx).getOrCreateContext(); 70 | holders = vertx.sharedData().getLocalMap(HOLDERS_LOCAL_MAP_NAME); 71 | SessionHolder current = holders.compute(clientName, (k, h) -> h == null ? new SessionHolder() : h.increment()); 72 | creatingContext.addCloseHook(completion -> CassandraClientImpl.this.close().onComplete(completion)); 73 | } 74 | 75 | @Override 76 | public synchronized boolean isConnected() { 77 | if (closed) { 78 | return false; 79 | } 80 | Session s = holders.get(clientName).session; 81 | return s != null && !s.isClosed(); 82 | } 83 | 84 | @Override 85 | public Future> executeWithFullFetch(String query) { 86 | return executeWithFullFetch(SimpleStatement.newInstance(query)); 87 | } 88 | 89 | @Override 90 | public Future> executeWithFullFetch(Statement statement) { 91 | return execute(statement) 92 | .flatMap(ResultSet::all); 93 | } 94 | 95 | @Override 96 | public Future execute(String query) { 97 | return execute(SimpleStatement.newInstance(query)); 98 | } 99 | 100 | @Override 101 | public Future execute(String query, Collector collector) { 102 | return execute(SimpleStatement.newInstance(query), collector); 103 | } 104 | 105 | @Override 106 | public Future execute(Statement statement) { 107 | return executeInternal(statement) 108 | .map(rs -> new ResultSetImpl(rs, vertx)); 109 | } 110 | 111 | private Future executeInternal(Statement statement) { 112 | return getSession(vertx.getOrCreateContext()) 113 | .flatMap(session -> { 114 | Object payload; 115 | if (tracer != null) { 116 | payload = sendRequest(session, statement); 117 | } else { 118 | payload = null; 119 | } 120 | Future future = Future.fromCompletionStage(session.executeAsync(statement), vertx.getContext()); 121 | if (tracer != null) { 122 | future = future.onComplete(ar -> receiveResponse(payload, ar)); 123 | } 124 | return future; 125 | }); 126 | } 127 | 128 | private Object sendRequest(CqlSession session, Statement statement) { 129 | QueryRequest request = new QueryRequest(session, statement); 130 | return tracer.sendRequest(vertx.getContext(), SpanKind.RPC, options.getTracingPolicy(), request, "Query", (k, v) -> { 131 | }, REQUEST_TAG_EXTRACTOR); 132 | } 133 | 134 | private void receiveResponse(Object payload, AsyncResult asyncResult) { 135 | tracer.receiveResponse(vertx.getContext(), null, payload, asyncResult.cause(), TagExtractor.empty()); 136 | } 137 | 138 | @Override 139 | public Future execute(Statement statement, Collector collector) { 140 | return executeAndCollect(statement, collector); 141 | } 142 | 143 | private Future executeAndCollect(Statement statement, Collector collector) { 144 | C container = collector.supplier().get(); 145 | BiConsumer accumulator = collector.accumulator(); 146 | Function finisher = collector.finisher(); 147 | return queryStream(statement) 148 | .flatMap(cassandraRowStream -> { 149 | Promise resultPromise = Promise.promise(); 150 | cassandraRowStream.endHandler(end -> { 151 | R result = finisher.apply(container); 152 | resultPromise.complete(result); 153 | }); 154 | cassandraRowStream.handler(row -> { 155 | accumulator.accept(container, row); 156 | }); 157 | cassandraRowStream.exceptionHandler(resultPromise::fail); 158 | return resultPromise.future(); 159 | }); 160 | } 161 | 162 | @Override 163 | public Future prepare(String query) { 164 | return getSession(vertx.getOrCreateContext()) 165 | .flatMap(session -> Future.fromCompletionStage(session.prepareAsync(query), vertx.getContext())); 166 | } 167 | 168 | @Override 169 | public Future prepare(SimpleStatement statement) { 170 | return getSession(vertx.getOrCreateContext()) 171 | .flatMap(session -> Future.fromCompletionStage(session.prepareAsync(statement), vertx.getContext())); 172 | } 173 | 174 | @Override 175 | public Future queryStream(String sql) { 176 | return queryStream(SimpleStatement.newInstance(sql)); 177 | } 178 | 179 | @Override 180 | public Future queryStream(Statement statement) { 181 | return executeInternal(statement) 182 | .map(rs -> { 183 | ResultSet resultSet = new ResultSetImpl(rs, vertx); 184 | CassandraRowStreamImpl stream = new CassandraRowStreamImpl(vertx.getContext()); 185 | stream.init(resultSet); 186 | return stream; 187 | }); 188 | } 189 | 190 | @Override 191 | public Future close() { 192 | ContextInternal context = vertx.getOrCreateContext(); 193 | if (raiseCloseFlag()) { 194 | do { 195 | SessionHolder current = holders.get(clientName); 196 | SessionHolder next = current.decrement(); 197 | if (next.refCount == 0) { 198 | if (holders.remove(clientName, current)) { 199 | if (current.session != null) { 200 | return Future.fromCompletionStage(current.session.closeAsync(), context); 201 | } 202 | break; 203 | } 204 | } else if (holders.replace(clientName, current, next)) { 205 | break; 206 | } 207 | } while (true); 208 | } 209 | return context.succeededFuture(); 210 | } 211 | 212 | @Override 213 | public Future metadata() { 214 | return getSession(vertx.getOrCreateContext()).map(Session::getMetadata); 215 | } 216 | 217 | private synchronized boolean raiseCloseFlag() { 218 | if (!closed) { 219 | closed = true; 220 | return true; 221 | } 222 | return false; 223 | } 224 | 225 | synchronized Future getSession(ContextInternal context) { 226 | if (closed) { 227 | return context.failedFuture("Client is closed"); 228 | } 229 | SessionHolder holder = holders.get(clientName); 230 | if (holder.session != null) { 231 | return context.succeededFuture(holder.session); 232 | } 233 | return context.executeBlocking(this::connect); 234 | } 235 | 236 | private CqlSession connect() { 237 | SessionHolder current = holders.get(clientName); 238 | if (current == null) { 239 | throw new VertxException("Client closed while connecting", true); 240 | } 241 | if (current.session != null) { 242 | return current.session; 243 | } 244 | CqlSessionBuilder builder = options.dataStaxClusterBuilder(); 245 | CqlSession session = builder.build(); 246 | current = holders.compute(clientName, (k, h) -> h == null ? null : h.connected(session)); 247 | if (current != null) { 248 | return current.session; 249 | } else { 250 | try { 251 | session.close(); 252 | } catch (Exception ignored) { 253 | } 254 | throw new VertxException("Client closed while connecting", true); 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/impl/CassandraRowStreamImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 The Vert.x Community. 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 | package io.vertx.cassandra.impl; 17 | 18 | import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; 19 | import com.datastax.oss.driver.api.core.cql.ExecutionInfo; 20 | import com.datastax.oss.driver.api.core.cql.Row; 21 | import io.vertx.cassandra.CassandraRowStream; 22 | import io.vertx.cassandra.ResultSet; 23 | import io.vertx.core.Context; 24 | import io.vertx.core.Future; 25 | import io.vertx.core.Handler; 26 | import io.vertx.core.internal.ContextInternal; 27 | import io.vertx.core.internal.EventExecutor; 28 | import io.vertx.core.internal.concurrent.InboundMessageQueue; 29 | 30 | import java.util.concurrent.locks.Lock; 31 | import java.util.concurrent.locks.ReentrantLock; 32 | 33 | /** 34 | * @author Pavel Drankou 35 | * @author Thomas Segismont 36 | */ 37 | public class CassandraRowStreamImpl implements CassandraRowStream { 38 | 39 | private static final Object DONE = new Object(); 40 | 41 | private final ContextInternal context; 42 | private final Queue internalQueue; 43 | 44 | private Handler rowHandler; 45 | private Handler exceptionHandler; 46 | private Handler endHandler; 47 | 48 | private ExecutionInfo executionInfo; 49 | private ColumnDefinitions columnDefinitions; 50 | 51 | public CassandraRowStreamImpl(Context context) { 52 | 53 | Queue queue = new Queue((ContextInternal) context); 54 | queue.pause(); 55 | 56 | this.context = (ContextInternal) context; 57 | this.internalQueue = queue; 58 | } 59 | 60 | void init(ResultSet resultSet) { 61 | executionInfo = resultSet.getExecutionInfo(); 62 | columnDefinitions = resultSet.getColumnDefinitions(); 63 | internalQueue.init(resultSet); 64 | } 65 | 66 | @Override 67 | public synchronized CassandraRowStream exceptionHandler(Handler handler) { 68 | exceptionHandler = handler; 69 | return this; 70 | } 71 | 72 | @Override 73 | public CassandraRowStream handler(Handler handler) { 74 | synchronized (this) { 75 | rowHandler = handler; 76 | } 77 | if (handler == null) { 78 | pause(); 79 | } else { 80 | resume(); 81 | } 82 | return this; 83 | } 84 | 85 | @Override 86 | public synchronized CassandraRowStream endHandler(Handler handler) { 87 | endHandler = handler; 88 | return this; 89 | } 90 | 91 | @Override 92 | public CassandraRowStream pause() { 93 | internalQueue.pause(); 94 | return this; 95 | } 96 | 97 | @Override 98 | public CassandraRowStream resume() { 99 | return fetch(Long.MAX_VALUE); 100 | } 101 | 102 | @Override 103 | public CassandraRowStream fetch(long l) { 104 | internalQueue.fetch(l); 105 | return this; 106 | } 107 | 108 | @Override 109 | public ExecutionInfo executionInfo() { 110 | return executionInfo; 111 | } 112 | 113 | @Override 114 | public ColumnDefinitions columnDefinitions() { 115 | return columnDefinitions; 116 | } 117 | 118 | private final Lock lock = new ReentrantLock(); 119 | private final EventExecutor executor = new EventExecutor() { 120 | @Override 121 | public boolean inThread() { 122 | return true; 123 | } 124 | @Override 125 | public void execute(Runnable command) { 126 | lock.lock(); 127 | try { 128 | command.run(); 129 | } finally { 130 | lock.unlock(); 131 | } 132 | } 133 | }; 134 | 135 | private class Queue extends InboundMessageQueue { 136 | 137 | private ResultSet resultSet; 138 | private boolean paused; 139 | 140 | public Queue(ContextInternal context) { 141 | super(executor, context.executor()); 142 | } 143 | 144 | void init(ResultSet rs) { 145 | transfer(rs); 146 | } 147 | 148 | private void transfer(ResultSet rs) { 149 | Iterable page = rs.currentPage(); 150 | lock.lock(); 151 | try { 152 | for (Row row : page) { 153 | write(new StreamItem(rs, row)); 154 | } 155 | } finally { 156 | lock.unlock(); 157 | } 158 | if (rs.hasMorePages()) { 159 | Future next = rs.fetchNextPage(); 160 | next.onComplete((res, err) -> { 161 | if (err == null) { 162 | resultSet = res; 163 | if (!paused) { 164 | transfer(res); 165 | } 166 | } else { 167 | write(err); 168 | } 169 | }); 170 | } else { 171 | write(DONE); 172 | } 173 | } 174 | 175 | @Override 176 | protected void handleResume() { 177 | paused = false; 178 | ResultSet rs = resultSet; 179 | resultSet = null; 180 | if (rs != null) { 181 | transfer(rs); 182 | } 183 | } 184 | 185 | @Override 186 | protected void handlePause() { 187 | paused = true; 188 | } 189 | 190 | @Override 191 | protected void handleMessage(Object msg) { 192 | if (msg == DONE) { 193 | Handler handler; 194 | synchronized (CassandraRowStreamImpl.this) { 195 | handler = endHandler; 196 | } 197 | if (handler != null) { 198 | context.emit(null, handler); 199 | } 200 | } else if (msg instanceof StreamItem) { 201 | StreamItem item = (StreamItem) msg; 202 | Handler handler; 203 | synchronized (CassandraRowStreamImpl.this) { 204 | handler = rowHandler; 205 | } 206 | executionInfo = item.executionInfo; 207 | columnDefinitions = item.columnDefinitions; 208 | if (handler != null) { 209 | context.emit(item.row, handler); 210 | } 211 | } else if (msg instanceof Throwable) { 212 | Throwable err = (Throwable) msg; 213 | Handler handler; 214 | synchronized (CassandraRowStreamImpl.this) { 215 | handler = exceptionHandler; 216 | } 217 | if (handler != null) { 218 | context.emit(err, handler); 219 | } 220 | } 221 | } 222 | } 223 | 224 | private static class StreamItem { 225 | public final ExecutionInfo executionInfo; 226 | public final ColumnDefinitions columnDefinitions; 227 | public final Row row; 228 | 229 | StreamItem(ResultSet resultSet, Row row) { 230 | this.executionInfo = resultSet.getExecutionInfo(); 231 | this.columnDefinitions = resultSet.getColumnDefinitions(); 232 | this.row = row; 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/impl/ResultSetImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | package io.vertx.cassandra.impl; 17 | 18 | import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; 19 | import com.datastax.oss.driver.api.core.cql.ExecutionInfo; 20 | import com.datastax.oss.driver.api.core.cql.Row; 21 | import io.vertx.cassandra.ResultSet; 22 | import io.vertx.core.*; 23 | import io.vertx.core.internal.ContextInternal; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Collections; 27 | import java.util.List; 28 | import java.util.concurrent.atomic.AtomicReference; 29 | 30 | /** 31 | * @author Pavel Drankou 32 | * @author Thomas Segismont 33 | */ 34 | public class ResultSetImpl implements ResultSet { 35 | 36 | private final Vertx vertx; 37 | private final AtomicReference resultSetRef; 38 | 39 | public ResultSetImpl(com.datastax.oss.driver.api.core.cql.AsyncResultSet resultSet, Vertx vertx) { 40 | this.resultSetRef = new AtomicReference<>(resultSet); 41 | this.vertx = vertx; 42 | } 43 | 44 | @Override 45 | public Future> all() { 46 | Promise> promise = Promise.promise(); 47 | loadMore(vertx.getOrCreateContext(), Collections.emptyList(), promise); 48 | return promise.future(); 49 | } 50 | 51 | @Override 52 | public ColumnDefinitions getColumnDefinitions() { 53 | return resultSetRef.get().getColumnDefinitions(); 54 | } 55 | 56 | @Override 57 | public ExecutionInfo getExecutionInfo() { 58 | return resultSetRef.get().getExecutionInfo(); 59 | } 60 | 61 | @Override 62 | public int remaining() { 63 | return resultSetRef.get().remaining(); 64 | } 65 | 66 | @Override 67 | public Iterable currentPage() { 68 | return resultSetRef.get().currentPage(); 69 | } 70 | 71 | @Override 72 | public Row one() { 73 | return resultSetRef.get().one(); 74 | } 75 | 76 | @Override 77 | public boolean hasMorePages() { 78 | return resultSetRef.get().hasMorePages(); 79 | } 80 | 81 | @Override 82 | public Future fetchNextPage() throws IllegalStateException { 83 | return Future.fromCompletionStage( 84 | resultSetRef.get().fetchNextPage(), 85 | vertx.getOrCreateContext()) 86 | .map(datastaxRS -> { 87 | resultSetRef.set(datastaxRS); 88 | return this; 89 | }); 90 | } 91 | 92 | @Override 93 | public boolean wasApplied() { 94 | return resultSetRef.get().wasApplied(); 95 | } 96 | 97 | private void loadMore(Context context, List loaded, Completable> handler) { 98 | int availableWithoutFetching = resultSetRef.get().remaining(); 99 | List rows = new ArrayList<>(loaded.size() + availableWithoutFetching); 100 | rows.addAll(loaded); 101 | for (int i = 0; i < availableWithoutFetching; i++) { 102 | rows.add(resultSetRef.get().one()); 103 | } 104 | 105 | if (resultSetRef.get().hasMorePages()) { 106 | Future.fromCompletionStage(resultSetRef.get().fetchNextPage(), context).onComplete(ar -> { 107 | if (ar.succeeded()) { 108 | resultSetRef.set(ar.result()); 109 | loadMore(context, rows, handler); 110 | } else { 111 | if (handler != null) { 112 | handler.fail(ar.cause()); 113 | } 114 | } 115 | }); 116 | } else { 117 | if (handler != null) { 118 | handler.succeed(rows); 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/impl/SessionHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.impl; 18 | 19 | import com.datastax.oss.driver.api.core.CqlSession; 20 | import io.vertx.core.shareddata.Shareable; 21 | 22 | import java.util.Objects; 23 | 24 | /** 25 | * @author Thomas Segismont 26 | */ 27 | public final class SessionHolder implements Shareable { 28 | 29 | final CqlSession session; 30 | final int refCount; 31 | 32 | SessionHolder() { 33 | session = null; 34 | refCount = 1; 35 | } 36 | 37 | public int refCount() { 38 | return refCount; 39 | } 40 | 41 | private SessionHolder(CqlSession session, int refCount) { 42 | this.session = session; 43 | this.refCount = refCount; 44 | } 45 | 46 | SessionHolder connected(CqlSession session) { 47 | Objects.requireNonNull(session); 48 | if (this.session != null) { 49 | throw new IllegalStateException(); 50 | } 51 | return new SessionHolder(session, refCount); 52 | } 53 | 54 | SessionHolder increment() { 55 | return new SessionHolder(session, refCount + 1); 56 | } 57 | 58 | SessionHolder decrement() { 59 | if (refCount < 1) { 60 | throw new IllegalArgumentException(); 61 | } 62 | return new SessionHolder(session, refCount - 1); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/impl/tracing/QueryRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.impl.tracing; 18 | 19 | import com.datastax.oss.driver.api.core.CqlIdentifier; 20 | import com.datastax.oss.driver.api.core.CqlSession; 21 | import com.datastax.oss.driver.api.core.cql.BatchStatement; 22 | import com.datastax.oss.driver.api.core.cql.BoundStatement; 23 | import com.datastax.oss.driver.api.core.cql.SimpleStatement; 24 | import com.datastax.oss.driver.api.core.cql.Statement; 25 | import com.datastax.oss.driver.api.core.metadata.EndPoint; 26 | import com.datastax.oss.driver.api.core.metadata.Metadata; 27 | import com.datastax.oss.driver.api.core.metadata.Node; 28 | 29 | import static java.util.stream.Collectors.joining; 30 | 31 | public class QueryRequest { 32 | 33 | public final String address; 34 | public final String keyspace; 35 | public final String cql; 36 | 37 | public QueryRequest(CqlSession session, Statement statement) { 38 | Metadata metadata = session.getMetadata(); 39 | address = metadata.getNodes().values().stream() 40 | .map(Node::getEndPoint) 41 | .map(EndPoint::asMetricPrefix) 42 | .collect(joining(",", "[", "]")); 43 | keyspace = session.getKeyspace().map(CqlIdentifier::toString).orElse(""); 44 | if (statement instanceof SimpleStatement) { 45 | SimpleStatement simpleStatement = (SimpleStatement) statement; 46 | cql = simpleStatement.getQuery(); 47 | } else if (statement instanceof BoundStatement) { 48 | BoundStatement boundStatement = (BoundStatement) statement; 49 | cql = boundStatement.getPreparedStatement().getQuery(); 50 | } else if (statement instanceof BatchStatement) { 51 | cql = "_batch_"; 52 | } else { 53 | cql = "unknown statement type"; 54 | } 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "QueryRequest{" + 60 | "address='" + address + '\'' + 61 | ", keyspace='" + keyspace + '\'' + 62 | ", cql='" + cql + '\'' + 63 | '}'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/impl/tracing/RequestTags.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.impl.tracing; 18 | 19 | import io.vertx.core.spi.tracing.TagExtractor; 20 | 21 | import java.util.function.Function; 22 | 23 | public enum RequestTags { 24 | 25 | // Generic 26 | PEER_ADDRESS("network.peer.address", q -> q.address), 27 | SPAN_KIND("span.kind", q -> "client"), 28 | 29 | // DB 30 | // See https://opentelemetry.io/docs/specs/semconv/database/cassandra/ 31 | 32 | DB_NAMESPACE("db.namespace", q -> q.keyspace), 33 | DB_QUERY_TEXT("db.query.text", q -> q.cql), 34 | DB_SYSTEM("db.system", q -> "cassandra"); 35 | 36 | final String name; 37 | final Function fn; 38 | 39 | RequestTags(String name, Function fn) { 40 | this.name = name; 41 | this.fn = fn; 42 | } 43 | 44 | public static final TagExtractor REQUEST_TAG_EXTRACTOR = new TagExtractor() { 45 | 46 | private final RequestTags[] TAGS = RequestTags.values(); 47 | 48 | @Override 49 | public int len(QueryRequest obj) { 50 | return TAGS.length; 51 | } 52 | 53 | @Override 54 | public String name(QueryRequest obj, int index) { 55 | return TAGS[index].name; 56 | } 57 | 58 | @Override 59 | public String value(QueryRequest obj, int index) { 60 | return TAGS[index].fn.apply(obj); 61 | } 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/cassandra/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Vert.x Community. 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 | @ModuleGen(name = "vertx-cassandra", groupPackage = "io.vertx") 17 | package io.vertx.cassandra; 18 | 19 | import io.vertx.codegen.annotations.ModuleGen; 20 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | module io.vertx.cassandra.client { 17 | 18 | requires static io.vertx.docgen; 19 | requires static io.vertx.codegen.api; 20 | requires static io.vertx.codegen.json; 21 | 22 | requires com.datastax.oss.driver.core; 23 | requires io.vertx.core; 24 | requires io.vertx.core.logging; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/AuthenticationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.tests; 18 | 19 | import io.vertx.cassandra.CassandraClient; 20 | import io.vertx.cassandra.CassandraClientOptions; 21 | import io.vertx.ext.unit.TestContext; 22 | import io.vertx.ext.unit.junit.RunTestOnContext; 23 | import io.vertx.ext.unit.junit.VertxUnitRunner; 24 | import org.junit.After; 25 | import org.junit.Assume; 26 | import org.junit.BeforeClass; 27 | import org.junit.Rule; 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | import org.testcontainers.containers.GenericContainer; 31 | import org.testcontainers.containers.wait.strategy.Wait; 32 | import org.testcontainers.images.builder.ImageFromDockerfile; 33 | 34 | import static org.hamcrest.CoreMatchers.startsWith; 35 | import static org.hamcrest.MatcherAssert.assertThat; 36 | 37 | @RunWith(VertxUnitRunner.class) 38 | public class AuthenticationTest { 39 | 40 | private static final int CQL_PORT = 9042; 41 | 42 | @Rule 43 | public RunTestOnContext vertx = new RunTestOnContext(); 44 | 45 | @Rule 46 | public GenericContainer authContainer = new GenericContainer<>( 47 | new ImageFromDockerfile() 48 | .withFileFromClasspath("Dockerfile", "auth-container/Dockerfile") 49 | ) 50 | .waitingFor(Wait.forLogMessage(".*Created default superuser role 'cassandra'.*\\n", 1)) 51 | .withExposedPorts(CQL_PORT); 52 | 53 | private CassandraClient client; 54 | 55 | @BeforeClass 56 | public static void notMacOs(){ 57 | Assume.assumeFalse("Test does not work on a Mac OS", io.netty.util.internal.PlatformDependent.isOsx()); 58 | } 59 | 60 | @Test 61 | public void testAuthProvider(TestContext context) { 62 | CassandraClientOptions options = new CassandraClientOptions() 63 | .addContactPoint("localhost", authContainer.getMappedPort(CQL_PORT)) 64 | .setUsername("cassandra") 65 | .setPassword("cassandra"); 66 | options.dataStaxClusterBuilder().withLocalDatacenter("datacenter1"); 67 | client = CassandraClient.createShared(vertx.vertx(), options); 68 | client.executeWithFullFetch("select release_version from system.local").onComplete(context.asyncAssertSuccess(result -> { 69 | context.verify(unused -> { 70 | assertThat(result.iterator().next().getString("release_version"), startsWith("3.11")); 71 | }); 72 | })); 73 | } 74 | 75 | @After 76 | public void tearDown(TestContext context) { 77 | if (client != null) { 78 | client.close().onComplete(context.asyncAssertSuccess()); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/CassandraClientTestBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.vertx.cassandra.tests; 17 | 18 | import com.datastax.oss.driver.api.core.CqlSession; 19 | import com.datastax.oss.driver.api.core.cql.Row; 20 | import io.vertx.cassandra.CassandraClient; 21 | import io.vertx.cassandra.CassandraClientOptions; 22 | import io.vertx.core.*; 23 | import io.vertx.core.internal.VertxInternal; 24 | import io.vertx.ext.unit.Async; 25 | import io.vertx.ext.unit.TestContext; 26 | import io.vertx.ext.unit.junit.VertxUnitRunner; 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.runner.RunWith; 30 | import org.testcontainers.containers.CassandraContainer; 31 | import org.testcontainers.utility.DockerImageName; 32 | 33 | import java.net.InetSocketAddress; 34 | import java.util.ArrayList; 35 | import java.util.List; 36 | import java.util.UUID; 37 | import java.util.concurrent.CompletableFuture; 38 | import java.util.concurrent.atomic.AtomicReference; 39 | 40 | import static io.vertx.cassandra.CassandraClientOptions.DEFAULT_HOST; 41 | 42 | /** 43 | * @author Pavel Drankou 44 | * @author Thomas Segismont 45 | */ 46 | @RunWith(VertxUnitRunner.class) 47 | public abstract class CassandraClientTestBase { 48 | 49 | private static final int CASSANDRA_PORT; 50 | private static final CassandraContainer CASSANDRA_CONTAINER; 51 | protected static final CqlSession CQL_SESSION; 52 | 53 | private final AtomicReference capturedContext = new AtomicReference<>(); 54 | 55 | protected VertxInternal vertx; 56 | protected CassandraClient client; 57 | 58 | static { 59 | CASSANDRA_CONTAINER = new CassandraContainer(DockerImageName.parse("cassandra:3.11").asCompatibleSubstituteFor("cassandra")); 60 | CASSANDRA_CONTAINER.start(); 61 | CASSANDRA_PORT = CASSANDRA_CONTAINER.getMappedPort(CassandraContainer.CQL_PORT); 62 | CQL_SESSION = CqlSession.builder() 63 | .addContactPoint(new InetSocketAddress(CASSANDRA_CONTAINER.getHost(), CASSANDRA_PORT)) 64 | .withLocalDatacenter("datacenter1") 65 | .build(); 66 | } 67 | 68 | @Before 69 | public void setUp() { 70 | vertx = (VertxInternal) createVertx(createVertxOptions()); 71 | client = CassandraClient.create(vertx, createClientOptions()); 72 | } 73 | 74 | protected VertxOptions createVertxOptions() { 75 | return new VertxOptions(); 76 | } 77 | 78 | protected Vertx createVertx(VertxOptions options) { 79 | return Vertx.vertx(options); 80 | } 81 | 82 | @After 83 | public void tearDown(TestContext testContext) { 84 | final Async async = testContext.async(); 85 | client.close().onComplete(testContext.asyncAssertSuccess(close -> async.countDown())); 86 | async.await(); 87 | vertx.close().onComplete(testContext.asyncAssertSuccess()); 88 | } 89 | 90 | protected CassandraClientOptions createClientOptions() { 91 | CassandraClientOptions cassandraClientOptions = new CassandraClientOptions(); 92 | cassandraClientOptions.dataStaxClusterBuilder().withLocalDatacenter("datacenter1"); 93 | return cassandraClientOptions.addContactPoint(InetSocketAddress.createUnresolved(DEFAULT_HOST, CASSANDRA_PORT)); 94 | } 95 | 96 | protected void initializeRandomStringKeyspace() { 97 | initializeKeyspace("random_strings"); 98 | CQL_SESSION.execute("create table random_strings.random_string_by_first_letter (first_letter text, random_string text, primary key (first_letter, random_string))"); 99 | } 100 | 101 | protected void initializeNamesKeyspace() { 102 | initializeKeyspace("names"); 103 | CQL_SESSION.execute("create table names.names_by_first_letter (first_letter text, name text, primary key (first_letter, name))"); 104 | } 105 | 106 | private void initializeKeyspace(String keyspace) { 107 | CQL_SESSION.execute("drop keyspace if exists " + keyspace); 108 | CQL_SESSION.execute("create keyspace if not exists " + keyspace + " WITH replication={'class' : 'SimpleStrategy', 'replication_factor':1} AND durable_writes = false"); 109 | } 110 | 111 | protected void insertRandomStrings(int rowsPerLetter) throws Exception { 112 | List> futures = new ArrayList<>(); 113 | for (char c = 'A'; c <= 'Z'; c++) { 114 | for (int i = 0; i < rowsPerLetter; i++) { 115 | String randomString = UUID.randomUUID().toString(); 116 | String statement = String.format("INSERT INTO random_strings.random_string_by_first_letter (first_letter, random_string) VALUES ('%s', '%s%s')", c, c, randomString); 117 | CompletableFuture future = CompletableFuture.runAsync(() -> { 118 | CQL_SESSION.execute(statement); 119 | }, vertx.workerPool().executor()); 120 | futures.add(future); 121 | } 122 | } 123 | CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(); 124 | } 125 | 126 | protected void checkContext(TestContext testContext) { 127 | Context context = vertx.getOrCreateContext(); 128 | if (capturedContext.compareAndSet(null, context)) { 129 | context.exceptionHandler(testContext::fail); 130 | } else { 131 | testContext.assertEquals(capturedContext.get(), context); 132 | } 133 | } 134 | 135 | protected static void getCassandraReleaseVersion(CassandraClient client, Handler> handler) { 136 | client.executeWithFullFetch("select release_version from system.local") 137 | .onComplete(ar -> { 138 | if (ar.succeeded()) { 139 | List result = ar.result(); 140 | handler.handle(Future.succeededFuture(result.iterator().next().getString("release_version"))); 141 | } else { 142 | handler.handle(Future.failedFuture(ar.cause())); 143 | } 144 | }); 145 | } 146 | 147 | protected static Future getCassandraReleaseVersion(CassandraClient client) { 148 | return client.executeWithFullFetch("select release_version from system.local") 149 | .map(result -> result.iterator().next().getString("release_version")); 150 | } 151 | 152 | protected static String randomClientName() { 153 | return CassandraClient.class.getSimpleName() + "-" + UUID.randomUUID(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/CloseHookTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.tests; 18 | 19 | import io.vertx.cassandra.CassandraClient; 20 | import io.vertx.ext.unit.TestContext; 21 | import io.vertx.ext.unit.junit.VertxUnitRunner; 22 | import org.junit.After; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | 26 | import static org.junit.Assert.assertFalse; 27 | 28 | @RunWith(VertxUnitRunner.class) 29 | public class CloseHookTest extends CassandraClientTestBase { 30 | 31 | private CassandraClient shared; 32 | 33 | @After 34 | public void closeSharedClient(TestContext testContext) { 35 | if (shared != null) { 36 | shared.close().onComplete(testContext.asyncAssertSuccess()); 37 | } 38 | } 39 | 40 | @Test 41 | public void testClientClosedAfterUndeploy(TestContext testContext) { 42 | String clientName = randomClientName(); 43 | VerticleWithCassandraClient verticle = new VerticleWithCassandraClient(createClientOptions(), clientName, true, false); 44 | vertx.deployVerticle(verticle).onComplete(testContext.asyncAssertSuccess(id -> { 45 | vertx.undeploy(id).onComplete(testContext.asyncAssertSuccess(v -> { 46 | CassandraClient client = CassandraClient.createShared(vertx, clientName, createClientOptions()); 47 | testContext.assertFalse(client.isConnected()); 48 | })); 49 | })); 50 | } 51 | 52 | @Test 53 | public void testExternalSharedClientNotClosedAfterUndeploy(TestContext testContext) { 54 | String clientName = randomClientName(); 55 | shared = CassandraClient.createShared(vertx, clientName, createClientOptions()); 56 | assertFalse(shared.isConnected()); 57 | VerticleWithCassandraClient verticle = new VerticleWithCassandraClient(createClientOptions(), clientName, true, true); 58 | vertx.deployVerticle(verticle).onComplete(testContext.asyncAssertSuccess(id -> { 59 | testContext.assertTrue(shared.isConnected()); 60 | vertx.undeploy(id).onComplete(testContext.asyncAssertSuccess(v -> { 61 | testContext.assertTrue(shared.isConnected()); 62 | })); 63 | })); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/ExecutionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.vertx.cassandra.tests; 17 | 18 | 19 | import com.datastax.oss.driver.api.core.cql.*; 20 | import io.vertx.ext.unit.TestContext; 21 | import io.vertx.ext.unit.junit.VertxUnitRunner; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | 25 | import java.util.stream.Collector; 26 | import java.util.stream.Collectors; 27 | import java.util.stream.Stream; 28 | 29 | /** 30 | * Test Casssandra client on how it executes queries. 31 | */ 32 | @RunWith(VertxUnitRunner.class) 33 | public class ExecutionTest extends CassandraClientTestBase { 34 | 35 | @Test 36 | public void tableHaveSomeRows(TestContext testContext) throws Exception { 37 | initializeRandomStringKeyspace(); 38 | insertRandomStrings(1); 39 | String query = "select count(*) as cnt from random_strings.random_string_by_first_letter"; 40 | client.execute(query).onComplete(testContext.asyncAssertSuccess(resultSet -> { 41 | Row one = resultSet.one(); 42 | long cnt = one.getLong("cnt"); 43 | testContext.assertTrue(cnt > 0); 44 | })); 45 | } 46 | 47 | @Test 48 | public void simpleExecuteWithBigAmountOfFetches(TestContext testContext) throws Exception { 49 | initializeRandomStringKeyspace(); 50 | insertRandomStrings(50); 51 | String query = "select random_string from random_strings.random_string_by_first_letter where first_letter = 'B'"; 52 | SimpleStatement statement = SimpleStatement.newInstance(query) 53 | // we would like to test that we are able to handle several fetches. 54 | // that is why we are setting a small fetch size 55 | .setPageSize(3); 56 | client.executeWithFullFetch(statement).onComplete(testContext.asyncAssertSuccess(rows -> { 57 | for (Row row : rows) { 58 | String selectedString = row.getString(0); 59 | testContext.assertNotNull(selectedString); 60 | } 61 | })); 62 | } 63 | 64 | @Test 65 | public void executeWithCollector(TestContext testContext) { 66 | initializeNamesKeyspace(); 67 | String prefix = "("; 68 | String suffix = ")"; 69 | String delimiter = ","; 70 | Collector collector = Collectors.mapping( 71 | row -> row.getString(0), 72 | Collectors.joining(delimiter, prefix, suffix) 73 | ); 74 | String insert = "INSERT INTO names.names_by_first_letter (first_letter, name) VALUES (?, ?)"; 75 | client.prepare(insert).onComplete(testContext.asyncAssertSuccess(prepared -> { 76 | BatchStatement batch = BatchStatement.newInstance(BatchType.LOGGED); 77 | for (String name : Stream.of("Paul", "Paulo", "Pavel").collect(Collectors.toSet())) { 78 | batch = batch.add(prepared.bind(name.substring(0, 1), name)); 79 | } 80 | client.execute(batch).onComplete(testContext.asyncAssertSuccess(exec -> { 81 | String query = "select name from names.names_by_first_letter where first_letter = 'P'"; 82 | client.execute(query, collector).onComplete(testContext.asyncAssertSuccess(result -> { 83 | testContext.assertEquals(result, "(Paul,Paulo,Pavel)"); 84 | })); 85 | })); 86 | })); 87 | } 88 | 89 | @Test 90 | public void preparedStatementsShouldWork(TestContext testContext) { 91 | initializeNamesKeyspace(); 92 | String insert = "INSERT INTO names.names_by_first_letter (first_letter, name) VALUES (?, ?)"; 93 | client.prepare(insert).onComplete(testContext.asyncAssertSuccess(prepared -> { 94 | Statement statement = prepared.bind("P", "Pavel"); 95 | client.execute(statement).onComplete(testContext.asyncAssertSuccess(exec -> { 96 | String select = "select NAME as n from names.names_by_first_letter where first_letter = 'P'"; 97 | client.executeWithFullFetch(select).onComplete(testContext.asyncAssertSuccess(rows -> { 98 | testContext.assertTrue(rows.get(0).getString("n").equals("Pavel")); 99 | })); 100 | })); 101 | })); 102 | } 103 | 104 | @Test 105 | public void preparedStatementsShouldWorkWithSimpleStatement(TestContext testContext) { 106 | initializeNamesKeyspace(); 107 | SimpleStatement insert = SimpleStatement.newInstance("INSERT INTO names.names_by_first_letter (first_letter, name) VALUES (?, ?)"); 108 | client.prepare(insert).onComplete(testContext.asyncAssertSuccess(prepared -> { 109 | BoundStatement statement = prepared.bind("P", "Pavel"); 110 | client.execute(statement).onComplete(testContext.asyncAssertSuccess(exec -> { 111 | String select = "select NAME as n from names.names_by_first_letter where first_letter = 'P'"; 112 | client.executeWithFullFetch(select).onComplete(testContext.asyncAssertSuccess(rows -> { 113 | testContext.assertTrue(rows.get(0).getString("n").equals("Pavel")); 114 | })); 115 | })); 116 | })); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/SharedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.tests; 18 | 19 | import io.vertx.cassandra.CassandraClient; 20 | import io.vertx.ext.unit.TestContext; 21 | import io.vertx.ext.unit.junit.VertxUnitRunner; 22 | import org.junit.After; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | 26 | import java.util.regex.Pattern; 27 | 28 | @RunWith(VertxUnitRunner.class) 29 | public class SharedTest extends CassandraClientTestBase { 30 | 31 | private CassandraClient shared; 32 | 33 | @After 34 | public void closeShared(TestContext testContext) { 35 | if (shared != null) { 36 | shared.close().onComplete(testContext.asyncAssertSuccess()); 37 | } 38 | } 39 | 40 | @Test 41 | public void testSharedClientNotClosed(TestContext testContext) { 42 | String clientName = randomClientName(); 43 | client = CassandraClient.createShared(vertx, clientName, createClientOptions()); 44 | client.executeWithFullFetch("select release_version from system.local").onComplete(testContext.asyncAssertSuccess(rows -> { 45 | String release_version = rows.iterator().next().getString("release_version"); 46 | testContext.assertTrue(Pattern.compile("[0-9\\.]+").matcher(release_version).find()); 47 | vertx.deployVerticle(new VerticleWithCassandraClient(createClientOptions(), clientName, false, true)) 48 | .onComplete(testContext.asyncAssertSuccess(id -> { 49 | vertx.undeploy(id).onComplete(testContext.asyncAssertSuccess(v2 -> { 50 | testContext.assertTrue(client.isConnected()); 51 | })); 52 | })); 53 | })); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/StreamingTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.vertx.cassandra.tests; 17 | 18 | import com.datastax.oss.driver.api.core.cql.PagingState; 19 | import com.datastax.oss.driver.api.core.cql.Row; 20 | import com.datastax.oss.driver.api.core.cql.SimpleStatement; 21 | import com.datastax.oss.driver.api.core.cql.SimpleStatementBuilder; 22 | import io.vertx.ext.unit.Async; 23 | import io.vertx.ext.unit.TestContext; 24 | import org.junit.Test; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.Collections; 29 | import java.util.List; 30 | import java.util.concurrent.atomic.AtomicInteger; 31 | 32 | import static java.util.concurrent.TimeUnit.NANOSECONDS; 33 | import static org.junit.Assert.*; 34 | 35 | public class StreamingTest extends CassandraClientTestBase { 36 | 37 | @Test 38 | public void testReadStream(TestContext testContext) throws Exception { 39 | initializeRandomStringKeyspace(); 40 | insertRandomStrings(63); 41 | 42 | String query = "select random_string from random_strings.random_string_by_first_letter where first_letter = 'A'"; 43 | SimpleStatement statement = SimpleStatement.newInstance(query) 44 | .setPageSize(5); // make sure data is not loaded at once from Cassandra 45 | 46 | Async async = testContext.async(); 47 | 48 | List items = Collections.synchronizedList(new ArrayList<>()); 49 | List pagingStates = Collections.synchronizedList(new ArrayList<>()); 50 | AtomicInteger idx = new AtomicInteger(); 51 | 52 | client.queryStream(statement).onComplete(testContext.asyncAssertSuccess(stream -> { 53 | long pause = 500; 54 | long start = System.nanoTime(); 55 | stream.endHandler(end -> testContext.verify(v -> { 56 | long duration = NANOSECONDS.toMillis(System.nanoTime() - start); 57 | // assertTrue(duration >= 5 * pause); 58 | for (int i = 1; i < pagingStates.size(); i++) { 59 | if (i >= 60) { 60 | assertNull(pagingStates.get(i)); 61 | } else if (i % 5 == 0) { 62 | assertFalse(Arrays.equals(pagingStates.get(i).toBytes(), pagingStates.get(i - 1).toBytes())); 63 | } else { 64 | assertArrayEquals(pagingStates.get(i).toBytes(), pagingStates.get(i - 1).toBytes()); 65 | } 66 | } 67 | async.countDown(); 68 | })).exceptionHandler(testContext::fail).handler(item -> { 69 | items.add(item); 70 | int j = idx.getAndIncrement(); 71 | pagingStates.add(stream.executionInfo().getSafePagingState()); 72 | if (j == 3 || j == 16 || j == 21 || j == 38 || j == 47) { 73 | stream.pause(); 74 | int emitted = items.size(); 75 | vertx.setTimer(pause, tid -> { 76 | testContext.assertTrue(emitted == items.size()); 77 | stream.resume(); 78 | }); 79 | } 80 | }); 81 | })); 82 | } 83 | 84 | @Test 85 | public void streamFetchesDoesNotOverflowDefault512KbJVMStack(TestContext testContext) throws Exception { 86 | int fetchSize = 100_000; 87 | initializeRandomStringKeyspace(); 88 | insertRandomStrings(5_000); 89 | final SimpleStatement query = new SimpleStatementBuilder( 90 | String.format( 91 | "select random_string from random_strings.random_string_by_first_letter limit %d", 92 | fetchSize 93 | ) 94 | ).setPageSize(fetchSize).build(); 95 | Async async = testContext.async(); 96 | client.queryStream(query).onComplete(testContext.asyncAssertSuccess(stream -> { 97 | stream.endHandler(end -> async.countDown()) 98 | .exceptionHandler(testContext::fail) 99 | .handler(item -> {}); 100 | })); 101 | } 102 | 103 | @Test 104 | public void emptyStream(TestContext testContext) throws Exception { 105 | initializeRandomStringKeyspace(); 106 | insertRandomStrings(1); 107 | String query = "select random_string from random_strings.random_string_by_first_letter where first_letter = '$'"; 108 | Async async = testContext.async(); 109 | client.queryStream(query).onComplete(testContext.asyncAssertSuccess(stream -> { 110 | testContext.assertNotNull(stream.columnDefinitions()); 111 | stream.endHandler(end -> async.countDown()) 112 | .exceptionHandler(testContext::fail) 113 | .handler(item -> testContext.fail()); 114 | })); 115 | } 116 | 117 | @Test 118 | public void emptyStreamWithHandlerSetFirst(TestContext testContext) throws Exception { 119 | initializeRandomStringKeyspace(); 120 | insertRandomStrings(1); 121 | String query = "select random_string from random_strings.random_string_by_first_letter where first_letter = '$'"; 122 | Async async = testContext.async(); 123 | client.queryStream(query).onComplete(testContext.asyncAssertSuccess(stream -> { 124 | stream.handler(item -> testContext.fail()) 125 | .endHandler(end -> async.countDown()) 126 | .exceptionHandler(testContext::fail); 127 | }) 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/ThreadingCheckTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.tests; 18 | 19 | import com.datastax.oss.driver.api.core.cql.Statement; 20 | import io.vertx.ext.unit.Async; 21 | import io.vertx.ext.unit.TestContext; 22 | import io.vertx.ext.unit.junit.VertxUnitRunner; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | 26 | @RunWith(VertxUnitRunner.class) 27 | public class ThreadingCheckTest extends CassandraClientTestBase { 28 | 29 | @Test 30 | public void checkStreamHandlers(TestContext testContext) throws Exception { 31 | initializeRandomStringKeyspace(); 32 | insertRandomStrings(1); 33 | String query = "select random_string from random_strings.random_string_by_first_letter where first_letter = 'A'"; 34 | Async async = testContext.async(1); 35 | client.queryStream(query).onComplete(testContext.asyncAssertSuccess(stream -> { 36 | checkContext(testContext); 37 | stream.endHandler(end -> { 38 | checkContext(testContext); 39 | async.countDown(); 40 | }).exceptionHandler(throwable -> checkContext(testContext)).handler(item -> checkContext(testContext)); 41 | })); 42 | } 43 | 44 | @Test 45 | public void checkPrepareAndQueryHandlers(TestContext testContext) { 46 | initializeNamesKeyspace(); 47 | String insert = "INSERT INTO names.names_by_first_letter (first_letter, name) VALUES (?, ?)"; 48 | client.prepare(insert).onComplete(testContext.asyncAssertSuccess(prep -> { 49 | checkContext(testContext); 50 | Statement statement = prep.bind("P", "Pavel"); 51 | client.execute(statement).onComplete(testContext.asyncAssertSuccess(exec -> { 52 | checkContext(testContext); 53 | String select = "select NAME as n from names.names_by_first_letter where first_letter = 'P'"; 54 | client.executeWithFullFetch(select).onComplete(testContext.asyncAssertSuccess(rows -> { 55 | checkContext(testContext); 56 | testContext.assertTrue(rows.iterator().next().getString("n").equals("Pavel")); 57 | })); 58 | })); 59 | })); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/TracingTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.tests; 18 | 19 | import io.vertx.cassandra.CassandraClientOptions; 20 | import io.vertx.cassandra.impl.tracing.QueryRequest; 21 | import io.vertx.core.Context; 22 | import io.vertx.core.Vertx; 23 | import io.vertx.core.VertxOptions; 24 | import io.vertx.core.spi.tracing.SpanKind; 25 | import io.vertx.core.spi.tracing.TagExtractor; 26 | import io.vertx.core.spi.tracing.VertxTracer; 27 | import io.vertx.core.tracing.TracingPolicy; 28 | import io.vertx.ext.unit.Async; 29 | import io.vertx.ext.unit.TestContext; 30 | import org.junit.Test; 31 | 32 | import java.util.function.BiConsumer; 33 | 34 | import static org.junit.Assert.*; 35 | 36 | public class TracingTest extends CassandraClientTestBase { 37 | 38 | VertxTracer tracer; 39 | 40 | @Override 41 | protected Vertx createVertx(VertxOptions options) { 42 | return Vertx.builder() 43 | .with(options) 44 | .withTracer(o -> new VertxTracer() { 45 | @Override 46 | public Object sendRequest(Context context, SpanKind kind, TracingPolicy policy, Object request, String operation, BiConsumer headers, TagExtractor tagExtractor) { 47 | return tracer.sendRequest(context, kind, policy, request, operation, headers, tagExtractor); 48 | } 49 | 50 | @Override 51 | public void receiveResponse(Context context, Object response, Object payload, Throwable failure, TagExtractor tagExtractor) { 52 | tracer.receiveResponse(context, response, payload, failure, tagExtractor); 53 | } 54 | }) 55 | .build(); 56 | } 57 | 58 | @Override 59 | protected CassandraClientOptions createClientOptions() { 60 | return super.createClientOptions() 61 | .setTracingPolicy(TracingPolicy.ALWAYS); 62 | } 63 | 64 | @Test 65 | public void testSimpleStatement(TestContext ctx) { 66 | String cql = "select release_version from system.local"; 67 | 68 | Context expectedContext = vertx.getOrCreateContext(); 69 | 70 | Async async = ctx.async(3); 71 | tracer = new VertxTracer() { 72 | @Override 73 | public Object sendRequest(Context context, SpanKind kind, TracingPolicy policy, Object request, String operation, BiConsumer headers, TagExtractor tagExtractor) { 74 | ctx.verify(unused -> { 75 | assertSame(expectedContext, context); 76 | assertEquals(TracingPolicy.ALWAYS, policy); 77 | QueryRequest queryRequest = (QueryRequest) request; 78 | assertEquals(cql, queryRequest.cql); 79 | async.countDown(); 80 | }); 81 | return null; 82 | } 83 | 84 | @Override 85 | public void receiveResponse(Context context, Object response, Object payload, Throwable failure, TagExtractor tagExtractor) { 86 | ctx.verify(unused -> { 87 | assertSame(expectedContext, context); 88 | assertNull(failure); 89 | async.countDown(); 90 | }); 91 | } 92 | }; 93 | 94 | expectedContext.runOnContext(v -> { 95 | client.executeWithFullFetch(cql).onComplete(ctx.asyncAssertSuccess(rows -> { 96 | async.countDown(); 97 | })); 98 | }); 99 | } 100 | 101 | @Test 102 | public void testSimpleStatementFailure(TestContext ctx) { 103 | String cql = "select pone from fonky.family"; 104 | 105 | Context expectedContext = vertx.getOrCreateContext(); 106 | 107 | Async async = ctx.async(3); 108 | tracer = new VertxTracer() { 109 | @Override 110 | public Object sendRequest(Context context, SpanKind kind, TracingPolicy policy, Object request, String operation, BiConsumer headers, TagExtractor tagExtractor) { 111 | ctx.verify(unused -> { 112 | assertSame(expectedContext, context); 113 | assertEquals(TracingPolicy.ALWAYS, policy); 114 | QueryRequest queryRequest = (QueryRequest) request; 115 | assertEquals(cql, queryRequest.cql); 116 | async.countDown(); 117 | }); 118 | return null; 119 | } 120 | 121 | @Override 122 | public void receiveResponse(Context context, Object response, Object payload, Throwable failure, TagExtractor tagExtractor) { 123 | ctx.verify(unused -> { 124 | assertSame(expectedContext, context); 125 | assertNotNull(failure); 126 | async.countDown(); 127 | }); 128 | } 129 | }; 130 | 131 | expectedContext.runOnContext(v -> { 132 | client.executeWithFullFetch(cql).onComplete(ctx.asyncAssertFailure(t -> async.countDown())); 133 | }); 134 | } 135 | 136 | @Test 137 | public void testBoundStatement(TestContext ctx) throws Exception { 138 | initializeRandomStringKeyspace(); 139 | insertRandomStrings(5); 140 | String cql = "select random_string from random_strings.random_string_by_first_letter where first_letter = ?"; 141 | 142 | Context expectedContext = vertx.getOrCreateContext(); 143 | 144 | Async async = ctx.async(3); 145 | tracer = new VertxTracer() { 146 | @Override 147 | public Object sendRequest(Context context, SpanKind kind, TracingPolicy policy, Object request, String operation, BiConsumer headers, TagExtractor tagExtractor) { 148 | ctx.verify(unused -> { 149 | assertSame(expectedContext, context); 150 | assertEquals(TracingPolicy.ALWAYS, policy); 151 | QueryRequest queryRequest = (QueryRequest) request; 152 | assertEquals(cql, queryRequest.cql); 153 | async.countDown(); 154 | }); 155 | return null; 156 | } 157 | 158 | @Override 159 | public void receiveResponse(Context context, Object response, Object payload, Throwable failure, TagExtractor tagExtractor) { 160 | ctx.verify(unused -> { 161 | assertSame(expectedContext, context); 162 | assertNull(failure); 163 | async.countDown(); 164 | }); 165 | } 166 | }; 167 | 168 | expectedContext.runOnContext(v -> { 169 | client.prepare(cql).onComplete(ctx.asyncAssertSuccess(ps -> { 170 | client.execute(ps.bind("B")).onComplete(ctx.asyncAssertSuccess(rs -> { 171 | async.countDown(); 172 | })); 173 | })); 174 | }); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/VerticleWithCassandraClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.tests; 18 | 19 | import io.vertx.cassandra.CassandraClient; 20 | import io.vertx.cassandra.CassandraClientOptions; 21 | import io.vertx.core.Future; 22 | import io.vertx.core.VerticleBase; 23 | 24 | public class VerticleWithCassandraClient extends VerticleBase { 25 | 26 | private final CassandraClientOptions options; 27 | private final String clientName; 28 | private final boolean executeRequestOnStart; 29 | private final boolean closeOnStop; 30 | 31 | private CassandraClient client; 32 | 33 | public VerticleWithCassandraClient(CassandraClientOptions options, String clientName, boolean executeRequestOnStart, boolean closeOnStop) { 34 | this.options = options; 35 | this.clientName = clientName; 36 | this.executeRequestOnStart = executeRequestOnStart; 37 | this.closeOnStop = closeOnStop; 38 | } 39 | 40 | @Override 41 | public Future start() throws Exception { 42 | client = CassandraClient.createShared(vertx, clientName, options); 43 | if (executeRequestOnStart) { 44 | return CassandraClientTestBase.getCassandraReleaseVersion(client); 45 | } else { 46 | return super.start(); 47 | } 48 | } 49 | 50 | @Override 51 | public Future stop() throws Exception { 52 | if (closeOnStop && client != null) { 53 | return client.close(); 54 | } else { 55 | return super.stop(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/cassandra/tests/impl/SessionHolderMapCleaningTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.cassandra.tests.impl; 18 | 19 | import io.vertx.cassandra.CassandraClient; 20 | import io.vertx.cassandra.impl.CassandraClientImpl; 21 | import io.vertx.cassandra.impl.SessionHolder; 22 | import io.vertx.core.*; 23 | import io.vertx.core.shareddata.LocalMap; 24 | import io.vertx.ext.unit.junit.VertxUnitRunner; 25 | import org.junit.After; 26 | import org.junit.Before; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | 30 | import java.util.concurrent.TimeUnit; 31 | 32 | import static org.junit.Assert.assertEquals; 33 | 34 | @RunWith(VertxUnitRunner.class) 35 | public class SessionHolderMapCleaningTest { 36 | 37 | private static final String CLIENT_NAME = "test"; 38 | 39 | private Vertx vertx; 40 | 41 | @Before 42 | public void before() { 43 | vertx = Vertx.vertx(); 44 | } 45 | 46 | @After 47 | public void after() throws Exception { 48 | vertx.close().await(20, TimeUnit.SECONDS); 49 | } 50 | 51 | @Test 52 | public void testMapCleaned() throws Exception { 53 | LocalMap holders = vertx.sharedData().getLocalMap(CassandraClientImpl.HOLDERS_LOCAL_MAP_NAME); 54 | int instances = 5; 55 | String id = vertx.deployVerticle(SampleVerticle::new, new DeploymentOptions().setInstances(instances)) 56 | .await(20, TimeUnit.SECONDS); 57 | assertEquals(instances, holders.get(CLIENT_NAME).refCount()); 58 | vertx.undeploy(id).await(20, TimeUnit.SECONDS); 59 | assertEquals(0, holders.size()); 60 | } 61 | 62 | private static class SampleVerticle extends VerticleBase { 63 | 64 | CassandraClient shared; 65 | 66 | @Override 67 | public Future start() throws Exception { 68 | shared = CassandraClient.createShared(vertx, CLIENT_NAME); 69 | return super.start(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/resources/auth-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cassandra:3.11 2 | 3 | RUN echo "authenticator: PasswordAuthenticator" >> /etc/cassandra/cassandra.yaml 4 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | target/test.log 21 | 22 | %date %level [%thread] %logger{10} [%file:%line] %msg%n 23 | 24 | 25 | 26 | 27 | 28 | %date %level [%thread] %logger{30} [%file:%line] %msg%n 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | --------------------------------------------------------------------------------