[
  {
    "path": ".github/workflows/ci.yml",
    "content": "# This workflow will build a Java project with Maven\n# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven\n\nname: Java CI with Maven\n\non:\n  push:\n    branches: [ main, 1.0.x, 0.9.x, 0.8.x ]\n\njobs:\n  build:\n    if: github.repository == 'r2dbc/r2dbc-mssql'\n    runs-on: ${{ matrix.os }}\n    strategy:\n        matrix:\n            include:\n                - os: ubuntu-latest\n                  mvn: ./mvnw\n                - os: windows-latest\n                  mvn: mvn\n                - os: macos-latest\n                  mvn: ./mvnw\n    steps:\n      - uses: actions/checkout@v5\n      - name: Set up Java\n        uses: actions/setup-java@v5\n        with:\n          java-version: 24\n          distribution: temurin\n          cache: maven\n      - name: Build with Maven\n        env:\n          CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}\n          CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}\n        run: ${{ matrix.mvn }} -B deploy -D skipITs -P snapshot -s settings.xml\n"
  },
  {
    "path": ".github/workflows/pullrequests.yml",
    "content": "# This workflow will build a Java project with Maven\n# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven\n\nname: Build Pull request with Maven\n\non: [pull_request]\n\njobs:\n  pr-build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n        matrix:\n            include:\n                - os: ubuntu-latest\n                  mvn: ./mvnw\n                - os: windows-latest\n                  mvn: mvn\n                - os: macos-latest\n                  mvn: ./mvnw\n    steps:\n      - uses: actions/checkout@v5\n      - name: Set up Java\n        uses: actions/setup-java@v5\n        with:\n          java-version: 24\n          distribution: temurin\n          cache: maven\n      - name: Build with Maven\n        run: ${{ matrix.mvn }} -B verify -D skipITs\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# This workflow will build a Java project with Maven\n# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven\n\nname: Stage release to Maven Central\n\non:\n  push:\n    branches: [ release ]\n\njobs:\n  release:\n    if: github.repository == 'r2dbc/r2dbc-mssql'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n      - name: Set up Java\n        uses: actions/setup-java@v5\n        with:\n          java-version: 24\n          distribution: temurin\n      - name: Initialize Maven Version\n        run: ./mvnw -q org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version\n      - name: GPG Check\n        run: gpg -k\n      - name: Release with Maven\n        env:\n          CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}\n          CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}\n          GPG_KEY_BASE64: ${{ secrets.GPG_KEY_BASE64 }}\n          GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}\n        run: ci/build-and-deploy-to-maven-central.sh\n"
  },
  {
    "path": ".gitignore",
    "content": "### Maven template\ntarget/\npom.xml.tag\npom.xml.releaseBackup\npom.xml.versionsBackup\npom.xml.next\nrelease.properties\ndependency-reduced-pom.xml\nbuildNumber.properties\n.mvn/timing.properties\n\n# IntelliJ\nout/\nbuild/\n.idea/\n*.iml\n\n### Java template\n# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# Package Files #\n*.jar\n*.war\n*.nar\n*.ear\n*.zip\n*.tar.gz\n*.rar\n\n# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n.flattened-pom.xml\n"
  },
  {
    "path": ".mvn/wrapper/MavenWrapperDownloader.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport java.net.*;\nimport java.io.*;\nimport java.nio.channels.*;\nimport java.util.Properties;\n\npublic class MavenWrapperDownloader {\n\n    /**\n     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.\n     */\n    private static final String DEFAULT_DOWNLOAD_URL =\n            \"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar\";\n\n    /**\n     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to\n     * use instead of the default one.\n     */\n    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =\n            \".mvn/wrapper/maven-wrapper.properties\";\n\n    /**\n     * Path where the maven-wrapper.jar will be saved to.\n     */\n    private static final String MAVEN_WRAPPER_JAR_PATH =\n            \".mvn/wrapper/maven-wrapper.jar\";\n\n    /**\n     * Name of the property which should be used to override the default download url for the wrapper.\n     */\n    private static final String PROPERTY_NAME_WRAPPER_URL = \"wrapperUrl\";\n\n    public static void main(String args[]) {\n        System.out.println(\"- Downloader started\");\n        File baseDirectory = new File(args[0]);\n        System.out.println(\"- Using base directory: \" + baseDirectory.getAbsolutePath());\n\n        // If the maven-wrapper.properties exists, read it and check if it contains a custom\n        // wrapperUrl parameter.\n        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);\n        String url = DEFAULT_DOWNLOAD_URL;\n        if(mavenWrapperPropertyFile.exists()) {\n            FileInputStream mavenWrapperPropertyFileInputStream = null;\n            try {\n                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);\n                Properties mavenWrapperProperties = new Properties();\n                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);\n                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);\n            } catch (IOException e) {\n                System.out.println(\"- ERROR loading '\" + MAVEN_WRAPPER_PROPERTIES_PATH + \"'\");\n            } finally {\n                try {\n                    if(mavenWrapperPropertyFileInputStream != null) {\n                        mavenWrapperPropertyFileInputStream.close();\n                    }\n                } catch (IOException e) {\n                    // Ignore ...\n                }\n            }\n        }\n        System.out.println(\"- Downloading from: : \" + url);\n\n        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);\n        if(!outputFile.getParentFile().exists()) {\n            if(!outputFile.getParentFile().mkdirs()) {\n                System.out.println(\n                        \"- ERROR creating output direcrory '\" + outputFile.getParentFile().getAbsolutePath() + \"'\");\n            }\n        }\n        System.out.println(\"- Downloading to: \" + outputFile.getAbsolutePath());\n        try {\n            downloadFileFromURL(url, outputFile);\n            System.out.println(\"Done\");\n            System.exit(0);\n        } catch (Throwable e) {\n            System.out.println(\"- Error downloading\");\n            e.printStackTrace();\n            System.exit(1);\n        }\n    }\n\n    private static void downloadFileFromURL(String urlString, File destination) throws Exception {\n        URL website = new URL(urlString);\n        ReadableByteChannel rbc;\n        rbc = Channels.newChannel(website.openStream());\n        FileOutputStream fos = new FileOutputStream(destination);\n        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);\n        fos.close();\n        rbc.close();\n    }\n\n}\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\njdk:\n- openjdk8\ncache:\n  directories:\n  - $HOME/.m2\nnotifications:\n  slack:\n    secure: JMisDlbY76AZeIQn/IUw0TyCR7A6x0FoazJ5BR5Fz0S+AU0peSgU9J3IaqYWzp7rv7eXOOCExLVwzXFNz9cXCREhbbg5yoaKIfV0nQvu03Rt1cwsFqAHP/3M7zwSS2cdw++hoX3ErNcJVt1rhZCQkbcc33LDwx8tJnfDOgZ5wAg5Z3iempp4Zig+IgtqoccI2hmefgoNY68hApfRDhHTpJHdQeCLeYtEA8UpNpLhKGBzTONVhjwyeJxCBVuY/9GIW8p6TceWbIPpPWE1hRtQgLLfTtz3LUAU1aMbTYFEEYnNfeAMdSqnyPqgehpUaoX4M/ep/cvb5NGtDz4ksQrIBdc578YAv/YXP9Q1cTk8A2DT7e7yYQWrgH2M+KyEPi/HYRE8fT2BjzecA9wbJhkeNGS/Zt02mzUb8WmitgstnFLac+ibyUtXwK27RN3zdbaIKjr2FtDHU2qbQ3m8TRJXi7UgeAFc5HAnJ0TyqGYw/FbCRDgEuW3gz3a8MZSJmxy4/JtrAxdLdsI3UyZtj0wXCqxuaJOyOOI11UCqlD16fzB0S0A12lqq6sAIkNbGPB7shJ5iMK5rtip6S1ssuRJ8XgTATT/XiNIBXYaRMXAzv0QCYTRrR2dQnIDT4pzsEsTS4T150Jl5SgD88QfXUyree2Qg7bRTvQwe9ruPsBWDUBs=\n"
  },
  {
    "path": "CHANGELOG",
    "content": "R2DBC MSSQL Changelog\n=============================\n\n1.0.0.RELEASE\n------------------\n* Upgrade to Project Reactor 2022.0.0 #260\n* Upgrade to R2DBC Pool 1.0.0.RELEASE #261\n\n1.0.0.RC1\n------------------\n* Expand CI testing to other platforms. #250\n* MssqlConnectionConfigurationUnitTests.configureKeyStore fails Windows test environment. #251\n* Upgrade to R2DBC 1.0 #253\n* Upgrade to Reactor 2022.0.0-M4 #255\n\n0.9.0.RELEASE\n------------------\n* `SSL` configuration option enables SSL handshaking regardless of the configuration value #240\n* Extend license years in copyright header to 2022 #242\n\n0.9.0.RC1\n------------------\n* Upgrade to R2DBC 0.9.0.RELEASE #237\n* Upgrade to R2DBC SPI 0.9 RC1 #223\n* Adopt renamed TCK column name #236\n* Move off deprecated Reactor Netty API #231\n* Let `Statement.add()` always create a new binding set #230\n* `BigDecimal` with negative scale incorrectly encoded #228\n* Use sequential processing in `Result.flatMap(…)` #225\n* Propagate offending SQL into R2DBC exceptions #224\n* Upgrade to R2DBC SPI 0.9 RC1 #223\n* Align `Statement.bind(…)` and `Readable.get(…)` exceptions with specification #222\n* Upgrade dependencies #221\n* Replace `EmitterProcessor` with `Sinks` API #219\n* The FOR XML clause is not allowed in a CURSOR statement #209\n\n0.9.0.M2\n------------------\n* Statements hang up on reading `nvarchar(max)` columns #216\n* Add support for Attention token (cancelling running queries) #215\n* Add support for lock wait timeout #214\n* Add support for statement timeout #213\n* Upgrade to R2DBC SPI 0.9 M2 #212\n* The FOR XML clause is not allowed in a CURSOR statement #209\n* `OffsetDateTimeCodec` does not properly decode negative timezone offsets #208\n* Upgrade to Testcontainers 1.15.3 #203\n* ClassCastException when calling RowMetadata.getColumnNames().toArray(T[]) #200\n* Add support to consume return values from stored procedures #199\n* Exclude transitive SLF4J pulled from HikariCP #198\n* Can't combine bind variables with T-SQL local variables #197\n* Statement batch doesn't produce the correct number of update counts #196\n* Eager buffer allocation in `TdsEncoder.writeChunkedMessage(…)` can lead to memory leaks #195\n* Add support for trustServerCertificate flag #184\n* Exception is not thrown when do SQL insert through ReactiveCrudRepository's save method. #180\n\n0.9.0.M1\n------------------\n* Upgrade to Reactor 2020.0.4 #193\n* Upgrade to R2DBC Pool 0.9.0.M1 #192\n* Upgrade to R2DBC SPI 0.9.0.M1 #190\n* Strip ROWSTAT column from MssqlRowMetadata #188\n* Add support for extended transaction definitions #183\n* Add support for SPI Parameters #182\n* Enable Developer Certificate of Origin #181\n* Restrict CI and release task to r2dbc/r2dbc-mssql repo #179\n* Upgrade dependencies #178\n* Add config options for TCP KeepAlive and NoDelay #177\n* Add support for SSL tunnels #176\n* Use GitHub actions to deploy to OSS Sonatype/Maven Central #173\n* Clob decoding is prone to in-character decoding splits #172\n* StringCodec fix for characters split between PLP chunks #171\n* Upgrade to R2DBC SPI 0.8.3 #170\n* Ensure no snapshots get referenced in release builds #165\n* Add integration tests for null decode #163\n* Failed statement with returnGeneratedValues enabled causes onErrorDropped #162\n* Rename master branch to main #159\n* Upgrade to Reactor Dysprosium SR9 #158\n* Upgrade to Reactor Dysprosium SR8 #157\n* Upgrade dependencies #155\n* Add sslContextBuilderCustomizer(Function<SslContextBuilder, SslContextBuilder>) #152\n* Issue inserting byte objects when size is greater than 8000 and and less than 65535 bytes #151\n* Add support for configuring a custom trust store #150\n* Add BlockHound to integration tests #149\n* Allow custom trust store for server certificate verification. #148\n* Upgrade build and test dependencies #146\n* Upgrade to Reactor Dysprosium-SR6 #145\n* Stage releases directly on maven central #143\n* Multiple TDS chunks in a single buffer cause connection reset on Azure SQL #142\n* Protocol errors get swallowed in RPC message flow for direct queries #141\n* Query String bigger than 4000 characters result in java.lang.UnsupportedOperationException  #140\n* Provide additional configuration options for hostNameInCertificiate #138\n* Update dependencies #134\n* Upgrade to Testcontainers 1.12.5 #132\n* Fix infinite loop when clearing bindings #131\n* Support casting of BIGINT to BigDecimal #130\n* Enable Travis for pull requests #125\n* Migrate to Jenkins CI #124\n* ENVCHANGE Token is not decoded properly for Routing type #116\n\n0.8.5.RELEASE\n------------------\n* Upgrade dependencies #178\n* Add config options for TCP KeepAlive and NoDelay #177\n* Add support for SSL tunnels #176\n* Use GitHub actions to deploy to OSS Sonatype/Maven Central #173\n* Clob decoding is prone to in-character decoding splits #172\n* StringCodec fix for characters split between PLP chunks #171\n* Upgrade to R2DBC SPI 0.8.3 #170\n\n0.8.4.RELEASE\n------------------\n* Ensure no snapshots get referenced in release builds #165\n* Add integration tests for null decode #163\n* Failed statement with returnGeneratedValues enabled causes onErrorDropped #162\n* Rename master branch to main #159\n* Upgrade to Reactor Dysprosium SR9 #158\n* Upgrade to Reactor Dysprosium SR8 #157\n\n0.8.3.RELEASE\n------------------\n* Upgrade dependencies #155\n* Add sslContextBuilderCustomizer(Function<SslContextBuilder, SslContextBuilder>) #152\n* Issue inserting byte objects when size is greater than 8000 and and less than 65535 bytes #151\n* Add support for configuring a custom trust store #150\n* Add BlockHound to integration tests #149\n* Allow custom trust store for server certificate verification. #148\n* Multiple TDS chunks in a single buffer cause connection reset on Azure SQL #142\n\n0.8.2.RELEASE\n------------------\n* Upgrade build and test dependencies #146\n* Upgrade to Reactor Dysprosium-SR6 #145\n* Stage releases directly on maven central #144\n* Protocol errors get swallowed in RPC message flow for direct queries #141\n* Query String bigger than 4000 characters result in java.lang.UnsupportedOperationException  #140\n* Provide additional configuration options for hostNameInCertificiate #138\n* ENVCHANGE Token is not decoded properly for Routing type #116\n\n0.8.1.RELEASE\n------------------\n* Update dependencies #134\n* Fix infinite loop when clearing bindings #131\n* Support casting of BIGINT to BigDecimal #130\n* Backport Travis support to 0.8.x #128\n* Backport Jenkins to 0.8.x #127\n\n0.8.0.RELEASE\n------------------\n* Upgrade to Reactor Dysprosium SR2 #123\n* Upgrade to R2DBC SPI 0.8.0.RELEASE #121\n* Remove SLF4J in favor of Reactor Core Loggers #120\n* Clob codec should support UNICODE #117\n* Default to scalar values for LOB column retrieval according to spec changes  #115\n* Upgrade to Testcontainers 1.12.3 #114\n* SELECT (NEXT VALUE FOR TestSeq) with RPC Flow and fast-forward scroll option skips sequence items #113\n* Add support for sendStringParametersAsUnicode property #112\n* Add hints to ByteBufs #110\n* Statement execution gets stuck when connection gets disconnected #109\n\n0.8.0.RC2\n------------------\n* Revert reactor netty exclusions #107\n\n0.8.0.RC1\n------------------\n* Fix malformed Javadoc #104\n* Add automatic module name #103\n* Upgrade to Reactor Dysprosium GA #102\n* Upgrade dependencies #100\n* BinaryCodec uses varbinary which limits the byteArray to 65kb #99\n* Use ByteBuffer as default type for binary payloads #98\n* Remove repositories declaration from published pom #97\n* Move jitpack repository declaration to JMH profile #96\n* Adapt to Statement.bind and Row.get by name #95\n* Report ConnectionMetadata from SERVERPROPERTY and @@VERSION #94\n* Upgrade to Reactor Dysprosium RC1 #93\n* Rename MssqlExample to MssqlTestKit #92\n* IllegalArgumentException Invalid TDS type is 0 on SQL Server 2014 #90\n* Connection reset by peer #89\n* Replace RuleBasedCollector with simple string matcher in MssqlRowMetadata #87\n* Add implementation for Connection.validate(…) #86\n* Expose ConnectionMetadata #85\n* QueryMessageFlow terminates without final DONE token #84\n* Improve debugging experience #83\n* Allow control of AutoCommit and retrieval of the IsolationLevel #82\n* Optimize operator allocation #81\n* Introduce literals for NULL values #80\n* Buffer refCnt = 0 reported when encoding large lob #79\n* Add support for expected hostname configuration #78\n* NotSslRecordException thrown when connecting to Azure SQL Server #77\n* Exclude not-required netty dependencies #75\n* Add benchmark suites #68\n* Consider large chunks in StreamDecoder #63\n* Add FluxDiscardOnCancel operator #6\n\n0.8.0.M8\n------------------\n* Upgrade to AssertJ 3.12.0 #72\n* Upgrade to Reactor Dysprosium M1 #71\n* Adapt to IsolationLevel changes (switch from enum to constant class) #70\n* Implement RowMetadata.getColumnNames() #64\n* Readme mentions mysql as driver identifier #62\n* Example Tests #60\n* Completion in GeneratedValues.reduceToSingleCountDoneToken(…) leaves non-consumed protocol messages on the wire #59\n* Add support for BLOB/CLOB types #58\n* Use R2DBC Exception hierarchy for driver exceptions #57\n* Reduce dependencies #56\n* Add configurable fetch size to MssqlStatement #55\n* Executing a parametrized Statement twice fails #54\n* Introduce cache for parsed SQL statements #53\n* Fix memory leak in cursored RPC flow #52\n* Reduce object allocations #51\n* SimpleMssqlStatement creates eagerly QueryMessageFlow #50\n* Defer error signal emission in MssqlResult until done token is processed #49\n* Introduce direct/cursored preference Predicate to prefer direct/cursored execution #48\n* Add support for SP_EXECUTESQL for simple parametrized statements #47\n* Query-Subscribers of Client.exchange(…) remain subscribed #46\n* Getting java.lang.IllegalStateException: Collation not available when querying the database. #37\n* Add ConnectionFactoryProvider.getDriver() implementation #31\n* Git ignore enhancement #30\n* Add support for varchar(max) and nvarchar(max) #28\n* Support SQL Server-specific transaction isolation levels by adding setTransactionIsolationLevel(MssqlIsolationLevel) to MssqlConnection #19\n* Document supported data types #18\n* Add support for binary types #3\n\n1.0.0.M7\n------------------\n* Update changelog for M7 #25\n* Fix ConnectionFactories usage example in readme #24\n* Tabular decode function retains previous column metadata #23\n* Introduce caching for RowMetadata instead creating an instance per row #22\n* Enhanced ColumnMetadata #21\n* Upgrade to TestContainers 1.10.6 #20\n* Add Statement.returnGeneratedValues(String...) #17\n* Remove Recursive Generics #16\n* Add configuration support connect timeout #15\n* Implement ConnectionFactory Discovery #14\n* Null values should return IllegalArgumentException #10\n* Parametrized INSERT … SELECT select SCOPE_IDENTITY() returns wrong affected rows count #7\n* Add support for transport-level encryption to allow Azure usage #5\n* Add support for OffsetDateTime #4\n\n1.0.0.M6\n------------------\n* Inception\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        https://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       https://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "NOTICE",
    "content": "Reactive Relational Database Connectivity\n\nCopyright 2017-2022 the original author or authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   https://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Reactive Relational Database Connectivity Microsoft SQL Server Implementation [![Java CI with Maven](https://github.com/r2dbc/r2dbc-mssql/actions/workflows/ci.yml/badge.svg)](https://github.com/r2dbc/r2dbc-mssql/actions/workflows/ci.yml) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.r2dbc/r2dbc-mssql/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.r2dbc/r2dbc-mssql)\n \n\nThis project contains the [Microsoft SQL Server][m] implementation of the [R2DBC SPI][r]. This implementation is not intended to be used directly, but rather to be used as the backing implementation for a humane client library to delegate to\n\n[m]: https://microsoft.com/sqlserver\n[r]: https://github.com/r2dbc/r2dbc-spi\n\nThis driver provides the following features:\n\n* Complies with R2DBC 1.0\n* Login with username/password with temporary SSL encryption\n* Full SSL encryption support (for e.g. Azure usage).\n* Transaction Control\n* Simple execution of SQL batches (direct and cursored execution)\n* Execution of parametrized statements (direct and cursored execution)\n* Extensive type support (including `TEXT`, `VARCHAR(MAX)`, `IMAGE`, `VARBINARY(MAX)` and national variants, see below for exceptions)\n* Execution of stored procedures\n\nNext steps:\n\n* Add support for TVP and UDTs\n\n## Code of Conduct\n\nThis project is governed by the [R2DBC Code of Conduct](https://github.com/r2dbc/.github/blob/main/CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please\nreport unacceptable behavior to [info@r2dbc.io](mailto:info@r2dbc.io).\n\n## Getting Started\n\nHere is a quick teaser of how to use R2DBC MSSQL in Java:\n\n**URL Connection Factory Discovery**\n\n```java\nConnectionFactory connectionFactory = ConnectionFactories.get(\"r2dbc:mssql://<host>:1433/<database>\");\n\nPublisher<? extends Connection> connectionPublisher = connectionFactory.create();\n```\n\n**Programmatic Connection Factory Discovery**\n\n```java\nConnectionFactoryOptions options = builder()\n    .option(DRIVER, \"sqlserver\")\n    .option(HOST, \"…\")\n    .option(PORT, …)  // optional, defaults to 1433\n    .option(USER, \"…\")\n    .option(PASSWORD, \"…\")\n    .option(DATABASE, \"…\") // optional\n    .option(SSL, true) // optional, defaults to false\n    .option(Option.valueOf(\"applicationName\"), \"…\") // optional\n    .option(Option.valueOf(\"preferCursoredExecution\"), true/false) // optional\n    .option(Option.valueOf(\"connectionId\"), new UUID(…)) // optional\n    .build();\n\nConnectionFactory connectionFactory = ConnectionFactories.get(options);\n\nPublisher<? extends Connection> connectionPublisher = connectionFactory.create();\n\n// Alternative: Creating a Mono using Project Reactor\nMono<Connection> connectionMono = Mono.from(connectionFactory.create());\n```\n\n**Supported ConnectionFactory Discovery Options**\n\n| Option                          | Description                                                                                                                                                                                                                                                               \n|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n| `ssl`                           | Whether to use transport-level encryption for the entire SQL server traffic.                                                                                                                                                                                              \n| `driver`                        | Must be `sqlserver`.                                                                                                                                                                                                                                                      \n| `host`                          | Server hostname to connect to.                                                                                                                                                                                                                                            \n| `port`                          | Server port to connect to. Defaults to `1433`. _(Optional)_                                                                                                                                                                                                               \n| `username`                      | Login username.                                                                                                                                                                                                                                                           \n| `password`                      | Login password.                                                                                                                                                                                                                                                           \n| `database`                      | Initial database to select. Defaults to SQL Server user profile settings. _(Optional)_                                                                                                                                                                                    \n| `applicationName`               | Name of the application. Defaults to driver name and version. _(Optional)_                                                                                                                                                                                                \n| `connectionId`                  | Connection Id for tracing purposes. Defaults to a random Id. _(Optional)_                                                                                                                                                                                                 \n| `connectionProvider`            | Set the `reactor.netty.resources.ConnectionProvider` to be used when creating the connection. Defaults to `ConnectionProvider.newConnection()`. _(Optional)_                                                                                                              \n| `connectTimeout`                | Connection Id for tracing purposes. Defaults to 30 seconds. _(Optional)_                                                                                                                                                                                                  \n| `hostNameInCertificate`         | Expected hostname in SSL certificate. Supports wildcards (e.g. `*.database.windows.net`). _(Optional)_                                                                                                                                                                    \n| `lockWaitTimeout`               | Lock wait timeout using `SET LOCK_TIMEOUT …`. _(Optional)_                                                                                                                                                                                                                \n| `preferCursoredExecution`       | Whether to prefer cursors  or direct execution for queries. Uses by default direct. Cursors require more round-trips but are more backpressure-friendly. Defaults to direct execution. Can be `boolean` or a `Predicate<String>` accepting the SQL query. _(Optional)_    \n| `sendStringParametersAsUnicode` | Configure whether to send character data as unicode (NVARCHAR, NCHAR, NTEXT) or whether to use the database encoding, defaults to `true`. If disabled, `CharSequence` data is sent using the database-specific collation such as ASCII/MBCS instead of Unicode.           \n| `sslTunnel`                     | Enables SSL tunnel usage when using a SSL tunnel or SSL terminator in front of SQL Server. Accepts `Function<SslContextBuilder, SslContextBuilder>` to customize the SSL tunnel settings. SSL tunneling is not related to SQL Server's built-in SSL support. _(Optional)_ \n| `sslContextBuilderCustomizer`   | SSL Context customizer to configure SQL Server's built-in SSL support (`Function<SslContextBuilder, SslContextBuilder>`) _(Optional)_                                                                                                                                     \n| `tcpKeepAlive`                  | Enable/disable TCP KeepAlive. Disabled by default. _(Optional)_                                                                                                                                                                                                           \n| `tcpNoDelay`                    | Enable/disable TCP NoDelay. Enabled by default. _(Optional)_                                                                                                                                                                                                              \n| `trustServerCertificate`        | Fully trust the server certificate bypassing X.509 certificate validation. Disabled by default. _(Optional)_                                                                                                                                                              \n| `trustStoreType`                | Type of the TrustStore. Defaults to `KeyStore.getDefaultType()`. _(Optional)_                                                                                                                                                                                             \n| `trustStore`                    | Path to the certificate TrustStore file. _(Optional)_                                                                                                                                                                                                                     \n| `trustStorePassword`            | Password used to check the integrity of the TrustStore data. _(Optional)_                                                                                                                                                                                                 \n\n\n**Programmatic Configuration**\n\n```java\nMssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n    .host(\"…\")\n    .username(\"…\")\n    .password(\"…\")\n    .database(\"…\")\n    .preferCursoredExecution(…)\n    .build();\n\nMssqlConnectionFactory factory = new MssqlConnectionFactory(configuration);\n\nMono<MssqlConnection> connectionMono = factory.create();\n```\n\nMicrosoft SQL Server uses named parameters that are prefixed with `@`. The following SQL statement makes use of parameters:\n\n```sql\nINSERT INTO person (id, first_name, last_name) VALUES(@id, @firstname, @lastname)\n```\n\nParameters are referenced without the `@` prefix when binding these:\n\n```java\nconnection.createStatement(\"INSERT INTO person (id, first_name, last_name) VALUES(@id, @firstname, @lastname)\")\n            .bind(\"id\", 1)\n            .bind(\"firstname\", \"Walter\")\n            .bind(\"lastname\", \"White\")\n            .execute()\n``` \n\nBinding also allows positional index (zero-based) references. The parameter index is derived from the parameter discovery order when parsing the query.\n\n### Maven configuration\n\nArtifacts can be found on [Maven Central](https://central.sonatype.com/search?q=r2dbc-mssql).\n\n```xml\n<dependency>\n  <groupId>io.r2dbc</groupId>\n  <artifactId>r2dbc-mssql</artifactId>\n  <version>${version}</version>\n</dependency>\n```\n\nIf you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository and declare the appropriate dependency version.\n\n```xml\n<dependency>\n  <groupId>io.r2dbc</groupId>\n  <artifactId>r2dbc-mssql</artifactId>\n  <version>${version}.BUILD-SNAPSHOT</version>\n</dependency>\n\n<repository>\n  <id>central-portal-snapshots</id>\n  <name>Central Portal Snapshots</name>\n  <url>https://central.sonatype.com/repository/maven-snapshots/</url>\n</repository>\n``` \n\n## Transaction Definitions\n\nSQL Server supports additional options when starting a transaction. In particular, the following options can be specified:\n\n* Isolation Level (`isolationLevel`) (reset after the transaction to previous value)\n* Transaction Name (`name`)\n* Transaction Log Mark (`mark`)\n* Lock Wait Timeout (`lockWaitTimeout`) (reset after the transaction to `-1`)\n\nThese options can be specified upon transaction begin to start the transaction and apply options in a single command roundtrip:\n\n```java\nMssqlConnection connection= …;\n\n        connection.beginTransaction(MssqlTransactionDefinition.from(IsolationLevel.READ_UNCOMMITTED)\n        .name(\"my-transaction\").mark(\"tx-log-mark\")\n        .lockTimeout(Duration.ofMinutes(1)));\n```\n\nSee also: https://docs.microsoft.com/en-us/sql/t-sql/language-elements/begin-transaction-transact-sql\n\n### Data Type Mapping\n\nThis reference table shows the type mapping between [Microsoft SQL Server][m] and Java data types:\n\n| Microsoft SQL Server Type                 | Java Data Type                                                                                                                           | \n|:------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------|\n| [`bit`][sql-bit-ref]                      | [**`Boolean`**][java-boolean-ref], [`Byte`][java-byte-ref], [`Short`][java-short-ref], [`Integer`][java-integer-ref], [`Long`][java-long-ref], [`BigDecimal`][java-bigdecimal-ref], [`BigInteger`][java-biginteger-ref] |\n| [`tinyint`][sql-all-int-ref]              | [**`Byte`**][java-byte-ref], [`Boolean`][java-boolean-ref], [`Short`][java-short-ref], [`Integer`][java-integer-ref], [`Long`][java-long-ref], [`BigDecimal`][java-bigdecimal-ref], [`BigInteger`][java-biginteger-ref] |\n| [`smallint`][sql-all-int-ref]             | [**`Short`**][java-short-ref], [`Boolean`][java-boolean-ref], [`Byte`][java-byte-ref], [`Integer`][java-integer-ref], [`Long`][java-long-ref], [`BigDecimal`][java-bigdecimal-ref], [`BigInteger`][java-biginteger-ref] |\n| [`int`][sql-all-int-ref]                  | [**`Integer`**][java-integer-ref], [`Boolean`][java-boolean-ref], [`Byte`][java-byte-ref], [`Short`][java-short-ref], [`Long`][java-long-ref], [`BigDecimal`][java-bigdecimal-ref], [`BigInteger`][java-biginteger-ref] |\n| [`bigint`][sql-all-int-ref]               | [**`Long`**][java-long-ref], [`Boolean`][java-boolean-ref], [`Byte`][java-byte-ref], [`Short`][java-short-ref], [`Integer`][java-integer-ref], [`BigDecimal`][java-bigdecimal-ref], [`BigInteger`][java-biginteger-ref] |\n| [`real`][sql-float-real-ref]              | [**`Float`**][java-float-ref], [`Double`][java-double-ref]   \n| [`float`][sql-float-real-ref]             | [**`Double`**][java-double-ref], [`Float`][java-float-ref] \n| [`decimal`][sql-decimal-ref]              | [**`BigDecimal`**][java-bigdecimal-ref], [`BigInteger`][java-biginteger-ref] \n| [`numeric`][sql-decimal-ref]              | [**`BigDecimal`**][java-bigdecimal-ref], [`BigInteger`][java-biginteger-ref]\n| [`uniqueidentifier`][sql-uid-ref]         | [**`UUID`**][java-uuid-ref], [`String`][java-string-ref]   \n| [`smalldatetime`][sql-smalldatetime-ref]  | [`LocalDateTime`][java-ldt-ref] \n| [`datetime`][sql-datetime-ref]            | [`LocalDateTime`][java-ldt-ref] \n| [`datetime2`][sql-datetime2-ref]          | [`LocalDateTime`][java-ldt-ref] \n| [`date`][sql-date-ref]                    | [`LocalDate`][java-ld-ref] \n| [`time`][sql-time-ref]                    | [`LocalTime`][java-lt-ref] \n| [`datetimeoffset`][sql-dtof-ref]          | [**`OffsetDateTime`**][java-odt-ref], [`ZonedDateTime`][java-zdt-ref]  \n| [`timestamp`][sql-timestamp-ref]          | [`byte[]`][java-byte-ref]\n| [`smallmoney`][sql-money-ref]             | [`BigDecimal`][java-bigdecimal-ref]\n| [`money`][sql-money-ref]                  | [`BigDecimal`][java-bigdecimal-ref]\n| [`char`][sql-(var)char-ref]               | [`String`][java-string-ref], [`Clob`][r2dbc-clob-ref]\n| [`varchar`][sql-(var)char-ref]            | [`String`][java-string-ref], [`Clob`][r2dbc-clob-ref]\n| [`varcharmax`][sql-(var)char-ref]         | [`String`][java-string-ref], [`Clob`][r2dbc-clob-ref]\n| [`nchar`][sql-n(var)char-ref]             | [`String`][java-string-ref], [`Clob`][r2dbc-clob-ref]\n| [`nvarchar`][sql-n(var)char-ref]          | [`String`][java-string-ref], [`Clob`][r2dbc-clob-ref]\n| [`nvarcharmax`][sql-n(var)char-ref]       | [`String`][java-string-ref], [`Clob`][r2dbc-clob-ref]\n| [`text`][sql-(n)text-ref]                 | [`String`][java-string-ref], [`Clob`][r2dbc-clob-ref]\n| [`ntext`][sql-(n)text-ref]                | [`String`][java-string-ref], [`Clob`][r2dbc-clob-ref]\n| [`image`][sql-(n)text-ref]                | [**`ByteBuffer`**][java-ByteBuffer-ref], [`byte[]`][java-byte-ref], [`Blob`][r2dbc-blob-ref]\n| [`binary`][sql-binary-ref]                | [**`ByteBuffer`**][java-ByteBuffer-ref], [`byte[]`][java-byte-ref], [`Blob`][r2dbc-blob-ref]\n| [`varbinary`][sql-binary-ref]             | [**`ByteBuffer`**][java-ByteBuffer-ref], [`byte[]`][java-byte-ref], [`Blob`][r2dbc-blob-ref]\n| [`varbinarymax`][sql-binary-ref]          | [**`ByteBuffer`**][java-ByteBuffer-ref], [`byte[]`][java-byte-ref], [`Blob`][r2dbc-blob-ref]\n| [`sql_variant`][sql-sql-variant-ref]      | Not yet supported.\n| [`xml`][sql-xml-ref]                      | Not yet supported.\n| [`udt`][sql-udt-ref]                      | Not yet supported.\n| [`geometry`][sql-geometry-ref]            | Not yet supported.\n| [`geography`][sql-geography-ref]          | Not yet supported.\n\nTypes in **bold** indicate the native (default) Java type.\n\n**Note:** BLOB (`image`, `binary`, `varbinary` and `varbinary(max)`) and CLOB (`text`, `ntext`, `varchar(max)` and `nvarchar(max)`)\nvalues are fully materialized in the client before decoding. Make sure to account for proper memory sizing.\n\n\n[sql-bit-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/bit-transact-sql?view=sql-server-2017\n[sql-all-int-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/int-bigint-smallint-and-tinyint-transact-sql?view=sql-server-2017\n[sql-float-real-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/float-and-real-transact-sql?view=sql-server-2017\n[sql-decimal-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/decimal-and-numeric-transact-sql?view=sql-server-2017\n[sql-smalldatetime-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/smalldatetime-transact-sql?view=sql-server-2017\n[sql-datetime-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetime-transact-sql?view=sql-server-2017\n[sql-datetime2-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetime2-transact-sql?view=sql-server-2017\n[sql-date-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/date-transact-sql?view=sql-server-2017\n[sql-time-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql?view=sql-server-2017\n[sql-timestamp-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/rowversion-transact-sql?view=sql-server-2017\n[sql-uid-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/uniqueidentifier-transact-sql?view=sql-server-2017\n[sql-dtof-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetimeoffset-transact-sql?view=sql-server-2017\n[sql-money-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/money-and-smallmoney-transact-sql?view=sql-server-2017\n[sql-(var)char-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/char-and-varchar-transact-sql?view=sql-server-2017\n\n[sql-n(var)char-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/nchar-and-nvarchar-transact-sql?view=sql-server-2017\n\n[sql-(n)text-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/ntext-text-and-image-transact-sql?view=sql-server-2017\n\n[sql-binary-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/binary-and-varbinary-transact-sql?view=sql-server-2017\n\n[sql-sql-variant-ref]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/sql-variant-transact-sql?view=sql-server-2017\n\n[sql-xml-ref]: https://docs.microsoft.com/en-us/sql/t-sql/xml/xml-transact-sql?view=sql-server-2017\n\n[sql-udt-ref]: https://docs.microsoft.com/en-us/sql/relational-databases/clr-integration-database-objects-user-defined-types/clr-user-defined-types?view=sql-server-2017\n\n[sql-geometry-ref]: https://docs.microsoft.com/en-us/sql/t-sql/spatial-geometry/spatial-types-geometry-transact-sql?view=sql-server-2017\n\n[sql-geography-ref]: https://docs.microsoft.com/en-us/sql/t-sql/spatial-geography/spatial-types-geography?view=sql-server-2017\n\n[r2dbc-blob-ref]: https://r2dbc.io/spec/1.0.0.RELEASE/api/io/r2dbc/spi/Blob.html\n\n[r2dbc-clob-ref]: https://r2dbc.io/spec/1.0.0.RELEASE/api/io/r2dbc/spi/Clob.html\n\n[java-bigdecimal-ref]: https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html\n\n[java-biginteger-ref]: https://docs.oracle.com/javase/8/docs/api/java/math/BigInteger.html\n\n[java-boolean-ref]: https://docs.oracle.com/javase/8/docs/api/java/lang/Boolean.html\n\n[java-byte-ref]: https://docs.oracle.com/javase/8/docs/api/java/lang/Byte.html\n\n[java-ByteBuffer-ref]: https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html\n\n[java-double-ref]: https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html\n\n[java-float-ref]: https://docs.oracle.com/javase/8/docs/api/java/lang/Float.html\n\n[java-integer-ref]: https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html\n\n[java-long-ref]: https://docs.oracle.com/javase/8/docs/api/java/lang/Long.html\n[java-ldt-ref]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html\n[java-ld-ref]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html\n[java-lt-ref]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html\n[java-odt-ref]: https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html\n[java-short-ref]: https://docs.oracle.com/javase/8/docs/api/java/lang/Short.html\n[java-string-ref]: https://docs.oracle.com/javase/8/docs/api/java/lang/String.html\n[java-uuid-ref]: https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html\n[java-zdt-ref]: https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html\n\n## Logging\n\nIf SL4J is on the classpath, it will be used. Otherwise, there are two possible fallbacks: Console or `java.util.logging.Logger`). By default, the Console fallback is used. To use the JDK loggers, set the `reactor.logging.fallback` System property to `JDK`.\n\nLogging facilities:\n\n* Driver Logging (`io.r2dbc.mssql`)\n* Query Logging (`io.r2dbc.mssql.QUERY` on `DEBUG` level)\n* Transport Logging (`io.r2dbc.mssql.client`)\n    * `DEBUG` enables `Message` exchange logging\n    * `TRACE` enables traffic logging\n\n## Getting Help\n\nHaving trouble with R2DBC? We'd love to help!\n\n* Check the [spec documentation](https://r2dbc.io/spec/1.0.0.RELEASE/spec/html/), and [Javadoc](https://r2dbc.io/spec/1.0.0.RELEASE/api/).\n* If you are upgrading, check out the [changelog](https://r2dbc.io/spec/1.0.0.RELEASE/CHANGELOG.txt) for \"new and noteworthy\" features.\n* Ask a question - we monitor [stackoverflow.com](https://stackoverflow.com) for questions tagged with [`r2dbc`](https://stackoverflow.com/tags/r2dbc). You can also chat with the community\n  on [Gitter](https://gitter.im/r2dbc/r2dbc).\n* Report bugs with R2DBC MSSQL at [github.com/r2dbc/r2dbc-mssql/issues](https://github.com/r2dbc/r2dbc-mssql/issues).\n\n## Reporting Issues\n\nR2DBC uses GitHub as issue tracking system to record bugs and feature requests. \nIf you want to raise an issue, please follow the recommendations below:\n\n* Before you log a bug, please search the [issue tracker](https://github.com/r2dbc/r2dbc-mssql/issues) to see if someone has already reported the problem.\n* If the issue doesn't already exist, [create a new issue](https://github.com/r2dbc/r2dbc-mssql/issues/new).\n* Please provide as much information as possible with the issue report, we like to know the version of R2DBC MSSQL that you are using and JVM version.\n* If you need to paste code, or include a stack trace use Markdown ``` escapes before and after your text.\n* If possible try to create a test-case or project that replicates the issue. \nAttach a link to your code or a compressed file containing your code.\n\n## Building from Source\n\nYou don't need to build from source to use R2DBC MSSQL (binaries in Maven Central), but if you want to try out the latest and greatest, R2DBC MSSQL can be easily built with the\n[maven wrapper](https://github.com/takari/maven-wrapper). You also need JDK 1.8 and Docker to run integration tests.\n\n```bash\n $ ./mvnw clean install\n```\n\nIf you want to build with the regular `mvn` command, you will need [Maven v3.6.0 or above](https://maven.apache.org/run-maven/index.html).\n\n_Also see [CONTRIBUTING.adoc](https://github.com/r2dbc/.github/blob/main/CONTRIBUTING.adoc) if you wish to submit pull requests. Commits require `Signed-off-by` (`git commit -s`) to ensure [Developer Certificate of Origin](https://developercertificate.org/)._\n\n### Running JMH Benchmarks\n\nRunning the JMH benchmarks builds and runs the benchmarks without running tests.\n\n```bash\n $ ./mvnw clean install -Pjmh\n```\n\n## Staging to Maven Central\n\nTo stage a release to Maven Central, you need to create a release tag (release version) that contains the desired state\nand version numbers (\n`mvn versions:set versions:commit -q -o -DgenerateBackupPoms=false -DnewVersion=x.y.z.(RELEASE|Mnnn|RCnnn`) and\nforce-push it to the `release` branch. This push will trigger a Maven staging build.\n\n## License\n\nR2DBC MSSQL is Open Source software released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html).\n"
  },
  {
    "path": "ci/build-and-deploy-to-maven-central.sh",
    "content": "#!/bin/bash\n\nset -euo pipefail\n\nVERSION=$(./mvnw org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version -o | grep -v INFO)\n\nif [[ $VERSION =~ [^.*-SNAPSHOT$] ]] ; then\n\n  echo \"Cannot deploy a snapshot: $VERSION\"\n  exit 1\nfi\n\nif [[ $VERSION =~ [^(\\d+\\.)+(RC(\\d+)|M(\\d+)|RELEASE)$] ]] ; then\n\n  #\n  # Prepare GPG Key is expected to be in base64\n  # Exported with gpg -a --export-secret-keys \"your@email\" | base64 > gpg.base64\n  #\n  printf \"$GPG_KEY_BASE64\" | base64 --decode > gpg.asc\n  echo ${GPG_PASSPHRASE} | gpg --batch --yes --passphrase-fd 0 --import gpg.asc\n  gpg -k\n\n  #\n  # Stage on Maven Central\n  #\n  echo \"Staging $VERSION to Central Portal\"\n\n  ./mvnw \\\n      -s settings.xml \\\n      -Pcentral \\\n      -Dmaven.test.skip=true \\\n      -Dgpg.passphrase=${GPG_PASSPHRASE} \\\n      clean deploy -B -D skipITs\nelse\n\n  echo \"Not a release: $VERSION\"\n  exit 1\nfi\n\n"
  },
  {
    "path": "intellij-style.xml",
    "content": "<code_scheme name=\"Project\" version=\"173\">\n  <option name=\"RIGHT_MARGIN\" value=\"200\" />\n  <option name=\"FORMATTER_TAGS_ENABLED\" value=\"true\" />\n  <option name=\"WRAP_COMMENTS\" value=\"true\" />\n  <option name=\"SOFT_MARGINS\" value=\"200\" />\n  <JavaCodeStyleSettings>\n    <option name=\"CLASS_NAMES_IN_JAVADOC\" value=\"3\" />\n    <option name=\"CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\" />\n    <option name=\"NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\" />\n  </JavaCodeStyleSettings>\n  <XML>\n    <option name=\"XML_LEGACY_SETTINGS_IMPORTED\" value=\"true\" />\n  </XML>\n  <codeStyleSettings language=\"JAVA\">\n    <option name=\"BLANK_LINES_BEFORE_PACKAGE\" value=\"1\" />\n    <option name=\"BLANK_LINES_AROUND_FIELD\" value=\"1\" />\n    <option name=\"BLANK_LINES_AROUND_FIELD_IN_INTERFACE\" value=\"1\" />\n    <option name=\"BLANK_LINES_AFTER_CLASS_HEADER\" value=\"1\" />\n    <option name=\"BLANK_LINES_AFTER_ANONYMOUS_CLASS_HEADER\" value=\"1\" />\n    <option name=\"IF_BRACE_FORCE\" value=\"3\" />\n    <option name=\"WRAP_LONG_LINES\" value=\"true\" />\n    <indentOptions>\n      <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n    </indentOptions>\n    <arrangement>\n      <rules>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <NAME>LOGGER</NAME>\n                <PRIVATE>true</PRIVATE>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <PUBLIC>true</PUBLIC>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <PROTECTED>true</PROTECTED>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <PRIVATE>true</PRIVATE>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <PUBLIC>true</PUBLIC>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <PROTECTED>true</PROTECTED>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <PRIVATE>true</PRIVATE>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <NAME>logger</NAME>\n                <PRIVATE>true</PRIVATE>\n              </AND>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <PUBLIC>true</PUBLIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <PROTECTED>true</PROTECTED>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <FINAL>true</FINAL>\n                <PRIVATE>true</PRIVATE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <PUBLIC>true</PUBLIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <PROTECTED>true</PROTECTED>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <FIELD>true</FIELD>\n                <PRIVATE>true</PRIVATE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <FIELD>true</FIELD>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>\n            </match>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <CONSTRUCTOR>true</CONSTRUCTOR>\n                <PUBLIC>true</PUBLIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <CONSTRUCTOR>true</CONSTRUCTOR>\n                <PROTECTED>true</PROTECTED>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <CONSTRUCTOR>true</CONSTRUCTOR>\n                <PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <CONSTRUCTOR>true</CONSTRUCTOR>\n                <PRIVATE>true</PRIVATE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <METHOD>true</METHOD>\n                <PUBLIC>true</PUBLIC>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <METHOD>true</METHOD>\n                <PUBLIC>true</PUBLIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <METHOD>true</METHOD>\n                <PROTECTED>true</PROTECTED>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <METHOD>true</METHOD>\n                <PROTECTED>true</PROTECTED>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <METHOD>true</METHOD>\n                <PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <METHOD>true</METHOD>\n                <PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <METHOD>true</METHOD>\n                <PRIVATE>true</PRIVATE>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <METHOD>true</METHOD>\n                <PRIVATE>true</PRIVATE>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <ENUM>true</ENUM>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <INTERFACE>true</INTERFACE>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <AND>\n                <CLASS>true</CLASS>\n                <STATIC>true</STATIC>\n              </AND>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n        <section>\n          <rule>\n            <match>\n              <CLASS>true</CLASS>\n            </match>\n            <order>BY_NAME</order>\n          </rule>\n        </section>\n      </rules>\n    </arrangement>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"JavaScript\">\n    <option name=\"WRAP_COMMENTS\" value=\"true\" />\n  </codeStyleSettings>\n  <codeStyleSettings language=\"TypeScript\">\n    <option name=\"WRAP_COMMENTS\" value=\"true\" />\n  </codeStyleSettings>\n</code_scheme>"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven2 Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\n  # TODO classpath?\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    jarUrl=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar\"\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) jarUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $jarUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n\n    if command -v wget > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n        fi\n        wget \"$jarUrl\" -O \"$wrapperJarPath\"\n    elif command -v curl > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n        fi\n        curl -o \"$wrapperJarPath\" \"$jarUrl\"\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        if [ -e \"$javaClass\" ]; then\n            if [ ! -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaClass\")\n            fi\n            if [ -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    https://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Maven2 Start Up Batch script\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM M2_HOME - location of maven2's installed home dir\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM set title of command window\ntitle %0\n@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nset DOWNLOAD_URL=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar\"\nFOR /F \"tokens=1,2 delims==\" %%A IN (%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties) DO (\n\tIF \"%%A\"==\"wrapperUrl\" SET DOWNLOAD_URL=%%B \n)\n\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\nif exist %WRAPPER_JAR% (\n    echo Found %WRAPPER_JAR%\n) else (\n    echo Couldn't find %WRAPPER_JAR%, downloading it ...\n\techo Downloading from: %DOWNLOAD_URL%\n    powershell -Command \"(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')\"\n    echo Finished downloading %WRAPPER_JAR%\n)\n@REM End of extension\n\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\n\nexit /B %ERROR_CODE%\n"
  },
  {
    "path": "pom.xml",
    "content": "<!--\n  ~ Copyright 2017-2022 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      https://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<project\n        xmlns=\"http://maven.apache.org/POM/4.0.0\"\n        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:schemaLocation=\"\n                http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>io.r2dbc</groupId>\n    <artifactId>r2dbc-mssql</artifactId>\n    <version>1.1.0.BUILD-SNAPSHOT</version>\n    <packaging>jar</packaging>\n\n    <name>Reactive Relational Database Connectivity - Microsoft SQL Server</name>\n    <description>Reactive Relational Database Connectivity Driver Implementation for Microsoft SQL Server</description>\n    <url>https://github.com/r2dbc/r2dbc-mssql</url>\n\n    <properties>\n        <assertj.version>3.23.1</assertj.version>\n        <hikari-cp.version>4.0.3</hikari-cp.version>\n        <java.version>1.8</java.version>\n        <jsr305.version>3.0.2</jsr305.version>\n        <junit.version>5.13.4</junit.version>\n        <jmh.version>1.37</jmh.version>\n        <mbr.version>0.6.0.RELEASE</mbr.version>\n        <logback.version>1.5.24</logback.version>\n        <mockito.version>4.11.0</mockito.version>\n        <mssql-jdbc.version>13.2.1.jre8</mssql-jdbc.version>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <r2dbc-spi.version>1.0.0.RELEASE</r2dbc-spi.version>\n        <r2dbc-pool.version>1.0.2.RELEASE</r2dbc-pool.version>\n        <reactor.version>2022.0.22</reactor.version>\n        <spring-framework.version>5.3.39</spring-framework.version>\n        <testcontainers.version>1.21.4</testcontainers.version>\n    </properties>\n\n    <licenses>\n        <license>\n            <name>Apache License 2.0</name>\n            <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>\n            <distribution>repo</distribution>\n        </license>\n    </licenses>\n\n    <scm>\n        <connection>scm:git:https://github.com/r2dbc/r2dbc-mssql</connection>\n        <url>https://github.com/r2dbc/r2dbc-mssql</url>\n    </scm>\n\n    <developers>\n        <developer>\n            <name>Ben Hale</name>\n            <email>bhale@pivotal.io</email>\n        </developer>\n        <developer>\n            <name>Mark Paluch</name>\n            <email>mpaluch@pivotal.io</email>\n        </developer>\n    </developers>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>io.projectreactor</groupId>\n                <artifactId>reactor-bom</artifactId>\n                <version>${reactor.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <dependency>\n                <groupId>org.junit</groupId>\n                <artifactId>junit-bom</artifactId>\n                <version>${junit.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n            <dependency>\n                <groupId>org.testcontainers</groupId>\n                <artifactId>testcontainers-bom</artifactId>\n                <version>${testcontainers.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <dependencies>\n        <dependency>\n            <groupId>io.r2dbc</groupId>\n            <artifactId>r2dbc-spi</artifactId>\n            <version>${r2dbc-spi.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.projectreactor</groupId>\n            <artifactId>reactor-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>io.projectreactor.netty</groupId>\n            <artifactId>reactor-netty-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.google.code.findbugs</groupId>\n            <artifactId>jsr305</artifactId>\n            <version>${jsr305.version}</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <!-- Test Dependencies -->\n        <dependency>\n            <groupId>io.r2dbc</groupId>\n            <artifactId>r2dbc-pool</artifactId>\n            <version>${r2dbc-pool.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.projectreactor</groupId>\n            <artifactId>reactor-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.r2dbc</groupId>\n            <artifactId>r2dbc-spi-test</artifactId>\n            <version>${r2dbc-spi.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.assertj</groupId>\n            <artifactId>assertj-core</artifactId>\n            <version>${assertj.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-api</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-engine</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter-params</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-core</artifactId>\n            <version>${mockito.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.testcontainers</groupId>\n            <artifactId>mssqlserver</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.microsoft.sqlserver</groupId>\n            <artifactId>mssql-jdbc</artifactId>\n            <version>${mssql-jdbc.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.zaxxer</groupId>\n            <artifactId>HikariCP</artifactId>\n            <version>${hikari-cp.version}</version>\n            <scope>test</scope>\n            <exclusions>\n                <exclusion>\n                    <groupId>org.slf4j</groupId>\n                    <artifactId>slf4j-api</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-jdbc</artifactId>\n            <version>${spring-framework.version}</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>ch.qos.logback</groupId>\n            <artifactId>logback-classic</artifactId>\n            <version>${logback.version}</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.11.0</version>\n                <configuration>\n                    <showWarnings>true</showWarnings>\n                    <release>8</release>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <version>3.3.0</version>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>\n                        </manifest>\n                        <manifestEntries>\n                            <R2DBC-Specification-Version>${r2dbc-spi.version}</R2DBC-Specification-Version>\n                            <Automatic-Module-Name>r2dbc.mssql</Automatic-Module-Name>\n                        </manifestEntries>\n                    </archive>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-deploy-plugin</artifactId>\n                <version>3.1.1</version>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-enforcer-plugin</artifactId>\n                <version>3.3.0</version>\n                <executions>\n                    <execution>\n                        <id>enforce-no-snapshots</id>\n                        <goals>\n                            <goal>enforce</goal>\n                        </goals>\n                        <configuration>\n                            <rules>\n                                <requireReleaseDeps>\n                                    <onlyWhenRelease>true</onlyWhenRelease>\n                                    <message>No Snapshots in releases allowed!</message>\n                                </requireReleaseDeps>\n                            </rules>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <version>3.5.0</version>\n                <configuration>\n                    <excludePackageNames>\n                        io.r2dbc.mssql.authentication,io.r2dbc.mssql.client,io.r2dbc.mssql.codec,io.r2dbc.mssql.message,io.r2dbc.mssql.util\n                    </excludePackageNames>\n                    <links>\n                        <link>https://r2dbc.io/spec/${r2dbc-spi.version}/api/</link>\n                        <link>https://projectreactor.io/docs/core/release/api/</link>\n                        <link>https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/</link>\n                    </links>\n                    <doclint>-missing</doclint>\n                    <javadocVersion>1.8</javadocVersion>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <version>3.3.0</version>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.1.2</version>\n                <configuration>\n                    <runOrder>random</runOrder>\n                    <excludes>\n                        <exclude>**/**IntegrationTests.java</exclude>\n                    </excludes>\n                    <includes>\n                        <include>**/*Tests.java</include>\n                    </includes>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-failsafe-plugin</artifactId>\n                <version>3.1.2</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>integration-test</goal>\n                            <goal>verify</goal>\n                        </goals>\n                    </execution>\n                </executions>\n                <configuration>\n                    <runOrder>random</runOrder>\n                    <includes>\n                        <include>**/*TestKit.java</include>\n                        <include>**/**IntegrationTests.java</include>\n                    </includes>\n                    <redirectTestOutputToFile>true</redirectTestOutputToFile>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>flatten-maven-plugin</artifactId>\n                <version>1.5.0</version>\n                <executions>\n                    <execution>\n                        <id>flatten</id>\n                        <phase>process-resources</phase>\n                        <goals>\n                            <goal>flatten</goal>\n                        </goals>\n                        <configuration>\n                            <updatePomFile>true</updatePomFile>\n                            <flattenMode>oss</flattenMode>\n                            <pomElements>\n                                <properties>remove</properties>\n                                <parent>expand</parent>\n                                <repositories>remove</repositories>\n                                <profiles>remove</profiles>\n                            </pomElements>\n                        </configuration>\n                    </execution>\n                    <execution>\n                        <id>flatten-clean</id>\n                        <phase>clean</phase>\n                        <goals>\n                            <goal>clean</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <plugin>\n                <groupId>org.sonatype.central</groupId>\n                <artifactId>central-publishing-maven-plugin</artifactId>\n                <version>0.10.0</version>\n                <extensions>true</extensions>\n                <configuration>\n                    <autoPublish>true</autoPublish>\n                    <deploymentName>R2DBC MSSQL ${project.version}</deploymentName>\n                    <publishingServerId>central</publishingServerId>\n                </configuration>\n            </plugin>\n        </plugins>\n\n        <resources>\n            <resource>\n                <directory>${project.basedir}</directory>\n                <includes>\n                    <include>LICENSE</include>\n                    <include>NOTICE</include>\n                    <include>CHANGELOG</include>\n                </includes>\n                <targetPath>META-INF</targetPath>\n            </resource>\n            <resource>\n                <directory>src/main/resources</directory>\n            </resource>\n        </resources>\n    </build>\n\n    <profiles>\n\n        <profile>\n            <id>jmh</id>\n\n            <dependencies>\n                <dependency>\n                    <groupId>com.github.mp911de.microbenchmark-runner</groupId>\n                    <artifactId>microbenchmark-runner-junit5</artifactId>\n                    <version>${mbr.version}</version>\n                    <scope>test</scope>\n                </dependency>\n                <dependency>\n                    <groupId>org.openjdk.jmh</groupId>\n                    <artifactId>jmh-core</artifactId>\n                    <version>${jmh.version}</version>\n                    <scope>test</scope>\n                </dependency>\n                <dependency>\n                    <groupId>org.openjdk.jmh</groupId>\n                    <artifactId>jmh-generator-annprocess</artifactId>\n                    <version>${jmh.version}</version>\n                    <scope>test</scope>\n                </dependency>\n            </dependencies>\n\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.codehaus.mojo</groupId>\n                        <artifactId>build-helper-maven-plugin</artifactId>\n                        <version>3.3.0</version>\n                        <executions>\n                            <execution>\n                                <id>add-source</id>\n                                <phase>generate-sources</phase>\n                                <goals>\n                                    <goal>add-test-source</goal>\n                                </goals>\n                                <configuration>\n                                    <sources>\n                                        <source>src/jmh/java</source>\n                                    </sources>\n                                </configuration>\n                            </execution>\n                        </executions>\n                    </plugin>\n\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-surefire-plugin</artifactId>\n                        <configuration>\n                            <skip>true</skip>\n                        </configuration>\n                    </plugin>\n\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-failsafe-plugin</artifactId>\n                        <configuration>\n                            <skip>true</skip>\n                        </configuration>\n                    </plugin>\n\n                    <plugin>\n                        <groupId>org.codehaus.mojo</groupId>\n                        <artifactId>exec-maven-plugin</artifactId>\n                        <version>3.1.0</version>\n                        <executions>\n                            <execution>\n                                <id>run-benchmarks</id>\n                                <phase>pre-integration-test</phase>\n                                <goals>\n                                    <goal>exec</goal>\n                                </goals>\n                                <configuration>\n                                    <classpathScope>test</classpathScope>\n                                    <executable>java</executable>\n                                    <arguments>\n                                        <argument>-classpath</argument>\n                                        <classpath/>\n                                        <argument>org.openjdk.jmh.Main</argument>\n                                        <argument>.*</argument>\n                                    </arguments>\n                                </configuration>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n\n            <repositories>\n                <repository>\n                    <id>jitpack.io</id>\n                    <url>https://jitpack.io</url>\n                </repository>\n            </repositories>\n        </profile>\n\n        <profile>\n            <id>snapshot</id>\n\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.sonatype.central</groupId>\n                        <artifactId>central-publishing-maven-plugin</artifactId>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n\n        <profile>\n            <id>central</id>\n\n            <build>\n                <pluginManagement>\n                    <plugins>\n\n                        <!-- Sign JARs -->\n\n                        <plugin>\n                            <groupId>org.apache.maven.plugins</groupId>\n                            <artifactId>maven-gpg-plugin</artifactId>\n                            <version>3.1.0</version>\n                            <executions>\n                                <execution>\n                                    <id>sign-artifacts</id>\n                                    <phase>verify</phase>\n                                    <goals>\n                                        <goal>sign</goal>\n                                    </goals>\n                                    <configuration>\n                                        <gpgArguments>\n                                            <arg>--pinentry-mode</arg>\n                                            <arg>loopback</arg>\n                                        </gpgArguments>\n                                    </configuration>\n                                </execution>\n                            </executions>\n                        </plugin>\n                    </plugins>\n                </pluginManagement>\n\n                <plugins>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-gpg-plugin</artifactId>\n                    </plugin>\n\n                    <plugin>\n                        <groupId>org.sonatype.central</groupId>\n                        <artifactId>central-publishing-maven-plugin</artifactId>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n\n</project>\n"
  },
  {
    "path": "settings.xml",
    "content": "<settings xmlns=\"http://maven.apache.org/SETTINGS/1.0.0\"\n\t\t  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t\t  xsi:schemaLocation=\"http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd\">\n\n\t<servers>\n\t\t<server>\n\t\t\t<id>central</id>\n\t\t\t<username>${env.CENTRAL_TOKEN_USERNAME}</username>\n\t\t\t<password>${env.CENTRAL_TOKEN_PASSWORD}</password>\n\t\t</server>\n\t</servers>\n</settings>\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/BenchmarkSettings.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport org.openjdk.jmh.annotations.BenchmarkMode;\nimport org.openjdk.jmh.annotations.Fork;\nimport org.openjdk.jmh.annotations.Measurement;\nimport org.openjdk.jmh.annotations.Mode;\nimport org.openjdk.jmh.annotations.OutputTimeUnit;\nimport org.openjdk.jmh.annotations.Warmup;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Global benchmark settings.\n *\n * @author Mark Paluch\n */\n@Warmup(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS)\n@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)\n@Fork(value = 1, warmups = 0)\n@BenchmarkMode(Mode.AverageTime)\n@OutputTimeUnit(TimeUnit.NANOSECONDS)\npublic abstract class BenchmarkSettings {\n\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/ParametrizedMssqlStatementBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.mockito.Mockito;\nimport org.openjdk.jmh.annotations.Benchmark;\n\nimport java.util.function.Function;\n\n/**\n * Benchmarks for {@link ParametrizedMssqlStatement}, specifically query parsing.\n *\n * @author Mark Paluch\n */\n@Testable\npublic class ParametrizedMssqlStatementBenchmarks extends BenchmarkSettings {\n\n    private static final ConnectionOptions cached = new ConnectionOptions(s -> false, new DefaultCodecs(), new IndefinitePreparedStatementCache(), true);\n\n    private static final ConnectionOptions uncached = new ConnectionOptions(s -> false, new DefaultCodecs(), new PreparedStatementCache() {\n\n        @Override\n        public int getHandle(String sql, Binding binding) {\n            return 0;\n        }\n\n        @Override\n        public void putHandle(int handle, String sql, Binding binding) {\n        }\n\n        @Override\n        public <T> T getParsedSql(String sql, Function<String, T> parseFunction) {\n            return parseFunction.apply(sql);\n        }\n\n        @Override\n        public int size() {\n            return 0;\n        }\n    }, true);\n\n    private static final Client client = Mockito.mock(Client.class);\n\n    @Benchmark\n    public Object parseSqlCached1Param() {\n        return new ParametrizedMssqlStatement(client, cached, \"SELECT * from FOO where firstname = @firstname\");\n    }\n\n    @Benchmark\n    public Object parseSqlCached5Param() {\n        return new ParametrizedMssqlStatement(client, cached, \"SELECT * from FOO where firstname = @firstname and firstname = @firstname and p2 = @p2 and p3 = @p3 and p4 = @p4 and p5 = @p5\");\n    }\n\n    @Benchmark\n    public Object parseSqlNonCached1Param() {\n        return new ParametrizedMssqlStatement(client, uncached, \"SELECT * from FOO where firstname = @firstname\");\n    }\n\n    @Benchmark\n    public Object parseSqlNonCached5Param() {\n        return new ParametrizedMssqlStatement(client, uncached, \"SELECT * from FOO where firstname = @firstname and firstname = @firstname and p2 = @p2 and p3 = @p3 and p4 = @p4 and p5 = @p5\");\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/PooledBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport com.zaxxer.hikari.HikariDataSource;\nimport io.r2dbc.mssql.util.MsSqlServerExtension;\nimport io.r2dbc.pool.ConnectionPool;\nimport io.r2dbc.pool.ConnectionPoolConfiguration;\nimport io.r2dbc.spi.ConnectionFactory;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.BenchmarkMode;\nimport org.openjdk.jmh.annotations.Mode;\nimport org.openjdk.jmh.annotations.OutputTimeUnit;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\nimport org.openjdk.jmh.infra.Blackhole;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Benchmarks for direct Statement execution mode using connection pooling.\n *\n * @author Mark Paluch\n */\n@BenchmarkMode(Mode.Throughput)\n@OutputTimeUnit(TimeUnit.SECONDS)\npublic class PooledBenchmarks extends BenchmarkSettings {\n\n    @State(Scope.Benchmark)\n    public static class ConnectionHolder {\n\n        final HikariDataSource jdbc;\n\n        final ConnectionFactory r2dbc;\n\n        public ConnectionHolder() {\n\n            MsSqlServerExtension extension = new MsSqlServerExtension();\n            extension.initialize();\n\n            this.jdbc = extension.getDataSource();\n\n            MssqlConnectionConfiguration configuration = extension.configBuilder().build();\n\n            MssqlConnectionFactory mssqlConnectionFactory = new MssqlConnectionFactory(configuration);\n            ConnectionPoolConfiguration poolConfiguration = ConnectionPoolConfiguration.builder(mssqlConnectionFactory).maxSize(4).build();\n\n            this.r2dbc = new ConnectionPool(poolConfiguration);\n        }\n    }\n\n    @Benchmark\n    @Testable\n    public void simpleDirectJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        Connection connection = connectionHolder.jdbc.getConnection();\n        Statement statement = connection.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"SELECT * FROM MSreplication_options\");\n\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"optname\"));\n        }\n\n        resultSet.close();\n        statement.close();\n\n        connection.close();\n    }\n\n    @Benchmark\n    @Testable\n    public void simpleDirectR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) {\n\n        String optname = Mono.usingWhen(connectionHolder.r2dbc.create(), connection -> {\n\n                MssqlStatement statement = (MssqlStatement) connection.createStatement(\"SELECT * FROM MSreplication_options\");\n                statement.fetchSize(0);\n                return Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"optname\", String.class))).last();\n\n            }, io.r2dbc.spi.Connection::close, (conn, err) -> conn.close(), io.r2dbc.spi.Connection::close\n        ).block();\n\n        voodoo.consume(optname);\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/StagedResultSizeBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.MsSqlServerExtension;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.BenchmarkMode;\nimport org.openjdk.jmh.annotations.Mode;\nimport org.openjdk.jmh.annotations.OutputTimeUnit;\nimport org.openjdk.jmh.annotations.Param;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.Setup;\nimport org.openjdk.jmh.annotations.State;\nimport org.openjdk.jmh.infra.Blackhole;\nimport reactor.core.publisher.Flux;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Benchmarks for Statement execution modes across various result sizes. Contains the following execution methods:\n *\n * <ul>\n * <li>SQLBATCH (Direct, Statements without parameters)</li>\n * <li>SP_EXECUTESQL (Direct, Statements with parameters)</li>\n * <li>SP_CURSOROPEN (Cursors, Statements without parameters)</li>\n * <li>SP_CURSORPREPEXEC (Cursors, Prepared statements)</li>\n * </ul>\n *\n * @author Mark Paluch\n */\n@BenchmarkMode(Mode.Throughput)\n@OutputTimeUnit(TimeUnit.SECONDS)\npublic class StagedResultSizeBenchmarks extends BenchmarkSettings {\n\n    @State(Scope.Benchmark)\n    public static class ConnectionHolder {\n\n        final Connection jdbc;\n\n        final io.r2dbc.spi.Connection r2dbc;\n\n        @Param({\"1\", \"10\", \"100\", \"200\"})\n        int resultSize;\n\n        public ConnectionHolder() {\n\n            try {\n                MsSqlServerExtension extension = new MsSqlServerExtension();\n                extension.initialize();\n                this.jdbc = extension.getDataSource().getConnection();\n\n\n                MssqlConnectionConfiguration configuration =\n                    extension.configBuilder().preferCursoredExecution(sql -> sql.contains(\" /* cursored */\")).build();\n                this.r2dbc = new MssqlConnectionFactory(configuration).create().block();\n            } catch (SQLException e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        @Setup\n        public void setup() {\n            try {\n                Statement statement = this.jdbc.createStatement();\n\n                try {\n                    statement.execute(\"DROP TABLE result_sizes\");\n                } catch (SQLException e) {\n                }\n\n                statement.execute(\"CREATE TABLE result_sizes (id int, name VARCHAR(255))\");\n\n                for (int i = 0; i < this.resultSize; i++) {\n                    statement.execute(String.format(\"INSERT INTO result_sizes VALUES(%d, '%s')\", i, UUID.randomUUID()));\n                }\n            } catch (SQLException e) {\n                throw new RuntimeException(e);\n            }\n        }\n    }\n\n    @Benchmark\n    @Testable\n    public void simpleDirectJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        Statement statement = connectionHolder.jdbc.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"SELECT * FROM result_sizes\");\n\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"name\"));\n        }\n\n        resultSet.close();\n        statement.close();\n    }\n\n    @Benchmark\n    @Testable\n    public void simpleDirectR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) {\n\n        io.r2dbc.spi.Statement statement = connectionHolder.r2dbc.createStatement(\"SELECT * FROM result_sizes\");\n        ((MssqlStatement) statement).fetchSize(0);\n\n        String name = Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"name\", String.class))).blockLast();\n\n        voodoo.consume(name);\n    }\n\n    @Benchmark\n\n    public void simpleCursoredJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        Statement statement = connectionHolder.jdbc.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);\n        statement.setFetchSize(128);\n        ResultSet resultSet = statement.executeQuery(\"SELECT * FROM result_sizes  /* cursored */\");\n\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"name\"));\n        }\n\n        resultSet.close();\n        statement.close();\n    }\n\n    @Benchmark\n    public void simpleCursoredR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) {\n\n        io.r2dbc.spi.Statement statement = connectionHolder.r2dbc.createStatement(\"SELECT * FROM result_sizes /* cursored */\");\n\n        String name = Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"name\", String.class))).blockLast();\n\n        voodoo.consume(name);\n    }\n\n    @Benchmark\n    public void parametrizedDirectJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        PreparedStatement statement = connectionHolder.jdbc.prepareStatement(\"SELECT * FROM result_sizes WHERE name != ?\");\n        statement.setString(1, \"foo\");\n\n        ResultSet resultSet = statement.executeQuery();\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"name\"));\n        }\n\n        resultSet.close();\n        statement.close();\n    }\n\n    @Benchmark\n    public void parametrizedDirectR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        io.r2dbc.spi.Statement statement = connectionHolder.r2dbc.createStatement(\"SELECT * FROM result_sizes WHERE name != @P0\").bind(\"P0\", \"foo\");\n        ((MssqlStatement) statement).fetchSize(0);\n\n        String name = Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"name\", String.class))).blockLast();\n\n        voodoo.consume(name);\n    }\n\n    @Benchmark\n    public void preparedCursoredJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        PreparedStatement statement = connectionHolder.jdbc.prepareStatement(\"SELECT * FROM result_sizes WHERE name != ?\", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);\n        statement.setFetchSize(128);\n        statement.setString(1, \"foo\");\n\n        ResultSet resultSet = statement.executeQuery();\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"name\"));\n        }\n\n        resultSet.close();\n        statement.close();\n    }\n\n    @Benchmark\n    public void preparedCursoredR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) {\n\n        io.r2dbc.spi.Statement statement = connectionHolder.r2dbc.createStatement(\"SELECT * FROM result_sizes WHERE name != @P0 /* cursored */\").bind(\"P0\", \"foo\");\n\n        String name = Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"name\", String.class))).blockLast();\n\n        voodoo.consume(name);\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/StatementBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.MsSqlServerExtension;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.BenchmarkMode;\nimport org.openjdk.jmh.annotations.Mode;\nimport org.openjdk.jmh.annotations.OutputTimeUnit;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\nimport org.openjdk.jmh.infra.Blackhole;\nimport reactor.core.publisher.Flux;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Benchmarks for Statement execution modes. Contains the following execution methods:\n *\n * <ul>\n * <li>SQLBATCH (Direct, Statements without parameters)</li>\n * <li>SP_EXECUTESQL (Direct, Statements with parameters)</li>\n * <li>SP_CURSOROPEN (Cursors, Statements without parameters)</li>\n * <li>SP_CURSORPREPEXEC (Cursors, Prepared statements)</li>\n * </ul>\n *\n * @author Mark Paluch\n */\n@BenchmarkMode(Mode.Throughput)\n@OutputTimeUnit(TimeUnit.SECONDS)\npublic class StatementBenchmarks extends BenchmarkSettings {\n\n    @State(Scope.Benchmark)\n    public static class ConnectionHolder {\n\n        final Connection jdbc;\n\n        final MssqlConnection r2dbc;\n\n        public ConnectionHolder() {\n\n            try {\n\n                MsSqlServerExtension extension = new MsSqlServerExtension();\n                extension.initialize();\n\n                this.jdbc = extension.getDataSource().getConnection();\n\n                Statement statement = this.jdbc.createStatement();\n\n                try {\n                    statement.execute(\"DROP TABLE simple_test\");\n                } catch (SQLException e) {\n                }\n\n                statement.execute(\"CREATE TABLE simple_test (name VARCHAR(255))\");\n                statement.execute(\"INSERT INTO simple_test VALUES('foo')\");\n                statement.execute(\"INSERT INTO simple_test VALUES('bar')\");\n                statement.execute(\"INSERT INTO simple_test VALUES('baz')\");\n\n\n                MssqlConnectionConfiguration configuration =\n                    extension.configBuilder().preferCursoredExecution(sql -> sql.contains(\" /* cursored */\")).build();\n                this.r2dbc = new MssqlConnectionFactory(configuration).create().block();\n            } catch (SQLException e) {\n                throw new RuntimeException(e);\n            }\n        }\n    }\n\n    @Benchmark\n    @Testable\n    public void simpleDirectJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        Statement statement = connectionHolder.jdbc.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"SELECT * FROM simple_test\");\n\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"name\"));\n        }\n\n        resultSet.close();\n        statement.close();\n    }\n\n    @Benchmark\n    @Testable\n    public void simpleDirectR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) {\n\n        MssqlStatement statement = connectionHolder.r2dbc.createStatement(\"SELECT * FROM simple_test\");\n        statement.fetchSize(0);\n\n        String name = Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"name\", String.class))).blockLast();\n\n        voodoo.consume(name);\n    }\n\n    @Benchmark\n    @Testable\n    public void simpleCursoredJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        Statement statement = connectionHolder.jdbc.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);\n        ResultSet resultSet = statement.executeQuery(\"SELECT * FROM simple_test  /* cursored */\");\n\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"name\"));\n        }\n\n        resultSet.close();\n        statement.close();\n    }\n\n    @Benchmark\n    public void simpleCursoredR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) {\n\n        io.r2dbc.spi.Statement statement = connectionHolder.r2dbc.createStatement(\"SELECT * FROM simple_test /* cursored */\");\n\n        String name = Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"name\", String.class))).blockLast();\n\n        voodoo.consume(name);\n    }\n\n    @Benchmark\n    public void parametrizedDirectJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        PreparedStatement statement = connectionHolder.jdbc.prepareStatement(\"SELECT * FROM simple_test WHERE name = ?\");\n        statement.setString(1, \"foo\");\n\n        ResultSet resultSet = statement.executeQuery();\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"name\"));\n        }\n\n        resultSet.close();\n        statement.close();\n    }\n\n    @Benchmark\n    @Testable\n    public void parametrizedDirectR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) {\n\n        io.r2dbc.spi.Statement statement = connectionHolder.r2dbc.createStatement(\"SELECT * FROM simple_test WHERE name = @P0\").bind(\"P0\", \"foo\");\n\n        String name = Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"name\", String.class))).blockLast();\n\n        voodoo.consume(name);\n    }\n\n    @Benchmark\n    public void preparedCursoredJdbc(ConnectionHolder connectionHolder, Blackhole voodoo) throws SQLException {\n\n        PreparedStatement statement = connectionHolder.jdbc.prepareStatement(\"SELECT * FROM simple_test WHERE name = ?\", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);\n        statement.setString(1, \"foo\");\n\n        ResultSet resultSet = statement.executeQuery();\n        while (resultSet.next()) {\n            voodoo.consume(resultSet.getString(\"name\"));\n        }\n\n        resultSet.close();\n        statement.close();\n    }\n\n    @Benchmark\n    public void preparedCursoredR2dbc(ConnectionHolder connectionHolder, Blackhole voodoo) {\n\n        io.r2dbc.spi.Statement statement = connectionHolder.r2dbc.createStatement(\"SELECT * FROM simple_test WHERE name = @P0 /* cursored */\").bind(\"P0\", \"foo\");\n\n        String name = Flux.from(statement.execute()).flatMap(it -> it.map((row, rowMetadata) -> row.get(\"name\", String.class))).blockLast();\n\n        voodoo.consume(name);\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/BinaryCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\nimport java.nio.ByteBuffer;\n\n/**\n * Benchmarks for {@code BinaryCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class BinaryCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column binary = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.USHORTLENTYPE).withServerType(SqlServerType.VARBINARY).build());\n\n    private final ByteBuf binaryBuffer = HexUtils.decodeToByteBuf(\"03 00 62 61 72\");\n\n    private final byte[] toEncode = \"foo\".getBytes();\n\n    private final ByteBuffer bufferToEncode = ByteBuffer.wrap(this.toEncode);\n\n    @Benchmark\n    public Object decodeToByteArray() {\n        this.binaryBuffer.readerIndex(0);\n        return codecs.decode(this.binaryBuffer, binary, byte[].class);\n    }\n\n    @Benchmark\n    public Object decodeToByteBuffer() {\n        this.binaryBuffer.readerIndex(0);\n        return codecs.decode(this.binaryBuffer, binary, ByteBuffer.class);\n    }\n\n    @Benchmark\n    public Encoded encodeByteArray() {\n        return doEncode(this.toEncode);\n    }\n\n    @Benchmark\n    public Encoded encodeByteBuffer() {\n        this.bufferToEncode.rewind();\n        return doEncode(this.bufferToEncode);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, byte[].class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/BooleanCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\n/**\n * Benchmarks for {@code BooleanCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class BooleanCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.TINYINT).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"01 01\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, Boolean.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(true);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, Boolean.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/ByteCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\n/**\n * Benchmarks for {@code ByteCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class ByteCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.TINYINT).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"01 01\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, Byte.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(Byte.MIN_VALUE);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, Byte.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/CodecBenchmarkSupport.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.BenchmarkSettings;\n\n/**\n * Settings for codec benchmarks.\n *\n * @author Mark Paluch\n */\npublic abstract class CodecBenchmarkSupport extends BenchmarkSettings {\n\n    static final ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/DecimalCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\nimport java.math.BigDecimal;\n\n/**\n * Benchmarks for {@code DecimalCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class DecimalCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.NUMERIC).withScale(2).withPrecision(5).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"08 08 01 00 00 00 00 00 00 01\");\n\n    private final BigDecimal toEncode = new BigDecimal(\"36.89\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, BigDecimal.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(this.toEncode);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, BigDecimal.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/DoubleCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\n/**\n * Benchmarks for {@code DoubleCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class DoubleCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.FLOAT).withMaxLength(8).build());\n\n    private final ByteBuf doubleBuffer = HexUtils.decodeToByteBuf(\"08 FE D4 78 E9 46 28 C6 40\");\n\n    private final ByteBuf floatBuffer = HexUtils.decodeToByteBuf(\"04 04 37 42 31 46\");\n\n    @Benchmark\n    public Object decodeDouble() {\n        this.doubleBuffer.readerIndex(0);\n        return codecs.decode(this.doubleBuffer, column, Double.class);\n    }\n\n    @Benchmark\n    public Object decodeFloat() {\n        this.floatBuffer.readerIndex(0);\n        return codecs.decode(this.floatBuffer, column, Double.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(Double.MIN_VALUE);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, Double.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/IntegerCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\n/**\n * Benchmarks for {@code IntegerCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class IntegerCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.INTEGER).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"04 04 01 00 00 01\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, Integer.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(Integer.MIN_VALUE);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, Integer.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/LocalDateCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\nimport java.time.LocalDate;\n\n/**\n * Benchmarks for {@code LocalDateCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class LocalDateCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.DATE).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"03 DD 3E 0B\");\n\n    private final LocalDate toEncode = LocalDate.parse(\"2018-10-23\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, LocalDate.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(this.toEncode);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, LocalDate.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/LocalDateTimeCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\nimport java.time.LocalDateTime;\n\n/**\n * Benchmarks for {@code LocalDateTimeCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class LocalDateTimeCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column smallDateTimeColumn = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.SMALLDATETIME).build());\n\n    private final ByteBuf smallDateTimeBuffer = HexUtils.decodeToByteBuf(\"04 F5 A8 3E 00\");\n\n    private static final Column dateTimeColumn = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.DATETIME).build());\n\n    private final ByteBuf dateTimeBuffer = HexUtils.decodeToByteBuf(\"08 86 A9 00 00 AA 70 02 01\");\n\n    private static final Column dateTime2Column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.DATETIME2).withScale(7).build());\n\n    private final ByteBuf dateTime2Buffer = HexUtils.decodeToByteBuf(\"082006E17483E13E0B\");\n\n    private final LocalDateTime toEncode = LocalDateTime.parse(\"2018-06-04T01:02\");\n\n    @Benchmark\n    public Object decodeSmallDateTime() {\n        this.smallDateTimeBuffer.readerIndex(0);\n        return codecs.decode(this.smallDateTimeBuffer, smallDateTimeColumn, LocalDateTime.class);\n    }\n\n    @Benchmark\n    public Object decodeDateTime() {\n        this.dateTimeBuffer.readerIndex(0);\n        return codecs.decode(this.dateTimeBuffer, dateTimeColumn, LocalDateTime.class);\n    }\n\n\n    @Benchmark\n    public Object decodeDateTime2() {\n        this.dateTime2Buffer.readerIndex(0);\n        return codecs.decode(this.dateTime2Buffer, dateTime2Column, LocalDateTime.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(this.toEncode);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, LocalDateTime.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/LocalTimeCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\nimport java.time.LocalTime;\n\n/**\n * Benchmarks for {@code LocalTimeCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class LocalTimeCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.TIME).withScale(7).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"05 C0 C9 B1 61 5D\");\n\n    private final LocalTime toEncode = LocalTime.parse(\"18:13:14\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, LocalTime.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(this.toEncode);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, LocalTime.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/LongCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\n/**\n * Benchmarks for {@code LongCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class LongCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.BIGINT).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"08 08 01 00 00 00 00 00 00 01\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, Long.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(Long.MIN_VALUE);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, Long.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/MoneyCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\nimport java.math.BigDecimal;\n\n/**\n * Benchmarks for {@code MoneyCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class MoneyCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.MONEY).withMaxLength(8).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"08 08 11 00 00 00 20 a1 07 00\");\n\n    private final BigDecimal toEncode = new BigDecimal(\"7301494.4032\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, BigDecimal.class);\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/ShortCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\n/**\n * Benchmarks for {@code ShortCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class ShortCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.SMALLINT).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"02 01 01\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, Short.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(Short.MIN_VALUE);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, Short.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/StringCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.codec.RpcParameterContext.ValueContext;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\n/**\n * Benchmarks for {@code StringCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class StringCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Collation cp1252 = Collation.from(13632521, 52);\n\n    private static final Column varchar = new Column(0, \"\",\n        TypeInformation.builder().withCharset(ServerCharset.CP1252.charset()).withLengthStrategy(LengthStrategy.USHORTLENTYPE).withServerType(SqlServerType.VARCHAR).build());\n\n    private final ByteBuf varcharBuffer;\n\n    private static final Collation unicode = Collation.from(0x0445, 0);\n\n    private static final Column nvarchar = new Column(0, \"\",\n        TypeInformation.builder().withCharset(ServerCharset.UNICODE.charset()).withLengthStrategy(LengthStrategy.USHORTLENTYPE).withServerType(SqlServerType.NVARCHAR).build());\n\n    private final ByteBuf nvarcharBuffer;\n\n    private static final Column text = new Column(0, \"\",\n        TypeInformation.builder().withMaxLength(2147483647).withLengthStrategy(LengthStrategy.LONGLENTYPE).withServerType(SqlServerType.TEXT).withCharset(ServerCharset.CP1252.charset()).build());\n\n    private final ByteBuf textBuffer;\n\n    private static final Column uuid = new Column(0, \"\",\n        TypeInformation.builder().withMaxLength(16).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withServerType(SqlServerType.GUID).build());\n\n    private final ByteBuf uuidBuffer;\n\n    public StringCodecBenchmarks() {\n\n        this.varcharBuffer = alloc.buffer();\n        Encode.uShort(this.varcharBuffer, 6);\n        this.varcharBuffer.writeCharSequence(\"foobar\", ServerCharset.CP1252.charset());\n\n        this.nvarcharBuffer = alloc.buffer();\n        Encode.uShort(this.nvarcharBuffer, 104);\n        this.nvarcharBuffer.writeCharSequence(\"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο\", ServerCharset.UNICODE.charset());\n\n        this.textBuffer = HexUtils.decodeToByteBuf(\"10 64\" +\n            \"75 6D 6D 79 20 74 65 78 74 70 74 72 00 00 00 64\" +\n            \"75 6D 6D 79 54 53 00 0B 00 00 00 6D 79 74 65 78\" +\n            \"74 76 61 6C 75 65\");\n\n        this.uuidBuffer = HexUtils.decodeToByteBuf(\"F17B0DC7C7E5C54098C7A12F7E686724FD\");\n    }\n\n    @Benchmark\n    public String decodeVarchar() {\n        this.varcharBuffer.readerIndex(0);\n        return codecs.decode(this.varcharBuffer, varchar, String.class);\n    }\n\n    @Benchmark\n    public Encoded encodeVarchar() {\n        return doEncode(cp1252, \"foobar\");\n    }\n\n    @Benchmark\n    public String decodeNVarchar() {\n        this.nvarcharBuffer.readerIndex(0);\n        return codecs.decode(this.nvarcharBuffer, nvarchar, String.class);\n    }\n\n    @Benchmark\n    public Encoded encodeNvarchar() {\n        return doEncode(unicode, \"Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο\");\n    }\n\n    @Benchmark\n    public String decodeText() {\n        this.textBuffer.readerIndex(0);\n        return codecs.decode(this.textBuffer, text, String.class);\n    }\n\n    @Benchmark\n    public String decodeUuid() {\n        this.uuidBuffer.readerIndex(0);\n        return codecs.decode(this.uuidBuffer, uuid, String.class);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(TestByteBufAllocator.TEST, String.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Collation collation, Object value) {\n        Encoded encoded = codecs.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(ValueContext.character(collation, true)), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/jmh/java/io/r2dbc/mssql/codec/UuidCodecBenchmarks.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.platform.commons.annotation.Testable;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.State;\n\nimport java.util.UUID;\n\n/**\n * Benchmarks for {@code UuidCodec}.\n *\n * @author Mark Paluch\n */\n@State(Scope.Thread)\n@Testable\npublic class UuidCodecBenchmarks extends CodecBenchmarkSupport {\n\n    private static final DefaultCodecs codecs = new DefaultCodecs();\n\n    private static final Column column = new Column(0, \"\",\n        TypeInformation.builder().withMaxLength(16).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(16).withServerType(SqlServerType.GUID).build());\n\n    private final ByteBuf buffer = HexUtils.decodeToByteBuf(\"F17B0DC7C7E5C54098C7A12F7E686724\");\n\n    private final UUID toEncode = UUID.fromString(\"C70D7BF1-E5C7-40C5-98C7-A12F7E686724\");\n\n    @Benchmark\n    public Object decode() {\n        this.buffer.readerIndex(0);\n        return codecs.decode(this.buffer, column, UUID.class);\n    }\n\n    @Benchmark\n    public Encoded encode() {\n        return doEncode(this.toEncode);\n    }\n\n    @Benchmark\n    public Encoded encodeNull() {\n        Encoded encoded = codecs.encodeNull(alloc, UUID.class);\n        encoded.dispose();\n        return encoded;\n    }\n\n    private Encoded doEncode(Object value) {\n        Encoded encoded = codecs.encode(alloc, RpcParameterContext.in(), value);\n        encoded.dispose();\n        return encoded;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/AbstractMssqlException.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.spi.R2dbcException;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Microsoft SQL Server-specific exception class.\n *\n * @author Mark Paluch\n */\npublic class AbstractMssqlException extends R2dbcException {\n\n    public static final int DRIVER_ERROR_NONE = 0;\n\n    public static final int DRIVER_ERROR_FROM_DATABASE = 2;\n\n    public static final int DRIVER_ERROR_IO_FAILED = 3;\n\n    public static final int DRIVER_ERROR_INVALID_TDS = 4;\n\n    public static final int DRIVER_ERROR_SSL_FAILED = 5;\n\n    public static final int DRIVER_ERROR_UNSUPPORTED_CONFIG = 6;\n\n    public static final int DRIVER_ERROR_INTERMITTENT_TLS_FAILED = 7;\n\n    public static final int ERROR_SOCKET_TIMEOUT = 8;\n\n    public static final int ERROR_QUERY_TIMEOUT = 9;\n\n    /**\n     * Creates a new exception.\n     */\n    public AbstractMssqlException() {\n        this((String) null);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason the reason for the error.  Set as the exception's message and retrieved with {@link #getMessage()}.\n     */\n    public AbstractMssqlException(@reactor.util.annotation.Nullable String reason) {\n        this(reason, (String) null);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason   the reason for the error.  Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @param sqlState the \"SQLstate\" string, which follows either the XOPEN SQLstate conventions or the SQL:2003 conventions\n     */\n    public AbstractMssqlException(@Nullable String reason, @Nullable String sqlState) {\n        this(reason, sqlState, 0);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason    the reason for the error.  Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @param sqlState  the \"SQLstate\" string, which follows either the XOPEN SQLstate conventions or the SQL:2003 conventions\n     * @param errorCode a vendor-specific error code representing this failure\n     */\n    public AbstractMssqlException(@Nullable String reason, @Nullable String sqlState, int errorCode) {\n        this(reason, sqlState, errorCode, null);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason    the reason for the error.  Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @param sqlState  the \"SQLstate\" string, which follows either the XOPEN SQLstate conventions or the SQL:2003 conventions\n     * @param errorCode a vendor-specific error code representing this failure\n     * @param cause     the cause\n     */\n    public AbstractMssqlException(@Nullable String reason, @Nullable String sqlState, int errorCode, @Nullable Throwable cause) {\n        super(reason, sqlState, errorCode, cause);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason   the reason for the error.  Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @param sqlState the \"SQLstate\" string, which follows either the XOPEN SQLstate conventions or the SQL:2003 conventions\n     * @param cause    the cause\n     */\n    public AbstractMssqlException(@Nullable String reason, @Nullable String sqlState, @Nullable Throwable cause) {\n        this(reason, sqlState, 0, cause);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason the reason for the error.  Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @param cause  the cause\n     */\n    public AbstractMssqlException(@Nullable String reason, @Nullable Throwable cause) {\n        this(reason, null, cause);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param cause the cause\n     */\n    public AbstractMssqlException(@Nullable Throwable cause) {\n        this(null, cause);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/Binding.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.codec.Encoded;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.BiConsumer;\n\n/**\n * A collection of {@link Encoded encoded parameters} for a single bind invocation of a prepared statement.\n * Bindings for Microsoft SQL Server are name-based and names are handled without the prefixing at-sign. The at sign is added during encoding.\n *\n * @author Mark Paluch\n */\nclass Binding {\n\n    private final Map<String, RpcParameter> parameters = new LinkedHashMap<>();\n\n    private boolean hasOutParameters = false;\n\n    @Nullable\n    private volatile String formalRepresentation;\n\n    /**\n     * Add a {@link Encoded encoded parameter} to the binding.\n     *\n     * @param name      the name of the {@link Encoded encoded parameter}\n     * @param direction the direction of the encoded parameter\n     * @param parameter the {@link Encoded encoded parameter}\n     * @return this {@link Binding}\n     */\n    public Binding add(String name, RpcDirection direction, Encoded parameter) {\n\n        Assert.requireNonNull(name, \"Name must not be null\");\n        Assert.requireNonNull(direction, \"RpcDirection must not be null\");\n        Assert.requireNonNull(parameter, \"Parameter must not be null\");\n\n        this.formalRepresentation = null;\n        this.parameters.put(name, new RpcParameter(direction, parameter));\n        if (direction == RpcDirection.OUT) {\n            this.hasOutParameters = true;\n        }\n        return this;\n    }\n\n    /**\n     * Returns parameter names of the return values.\n     *\n     * @return\n     */\n    boolean hasOutParameters() {\n        return this.hasOutParameters;\n    }\n\n    /**\n     * Clear/release binding values.\n     */\n    void clear() {\n\n        this.parameters.forEach((s, parameter) -> {\n            if (!parameter.encoded.isDisposed()) {\n                parameter.encoded.dispose();\n            }\n        });\n\n        this.parameters.clear();\n    }\n\n    /**\n     * Returns a formal representation of the bound parameters such as {@literal @P0 VARCHAR(8000), @P1 DECIMAL(12,6)}\n     *\n     * @return a formal representation of the bound parameters.\n     */\n    public String getFormalParameters() {\n\n        String formalRepresentation = this.formalRepresentation;\n        if (formalRepresentation != null) {\n            return formalRepresentation;\n        }\n\n        StringBuilder builder = new StringBuilder(this.parameters.size() * 16);\n        Set<Map.Entry<String, RpcParameter>> entries = this.parameters.entrySet();\n\n        for (Map.Entry<String, RpcParameter> entry : entries) {\n\n            if (builder.length() != 0) {\n                builder.append(',');\n            }\n\n            builder.append('@').append(entry.getKey()).append(' ').append(entry.getValue().encoded.getFormalType());\n\n            if (entry.getValue().rpcDirection == RpcDirection.OUT) {\n                builder.append(\" OUTPUT\");\n            }\n        }\n\n        formalRepresentation = builder.toString();\n        this.formalRepresentation = formalRepresentation;\n\n        return formalRepresentation;\n    }\n\n    /**\n     * Performs the given action for each entry in this binding until all bound parameters\n     * have been processed or the action throws an exception.   Unless\n     * otherwise specified by the implementing class, actions are performed in\n     * the order of entry set iteration (if an iteration order is specified.)\n     *\n     * @param action The action to be performed for each bound parameter.\n     */\n    public void forEach(BiConsumer<String, RpcParameter> action) {\n\n        Assert.requireNonNull(action, \"Action must not be null\");\n\n        this.parameters.forEach(action);\n    }\n\n    Map<String, RpcParameter> getParameters() {\n        return this.parameters;\n    }\n\n    /**\n     * Returns whether this {@link Binding} is empty (i.e. no parameters bound).\n     *\n     * @return {@code true} if no parameters were bound.\n     */\n    public boolean isEmpty() {\n        return this.parameters.isEmpty();\n    }\n\n    /**\n     * Returns the number of bound parameters.\n     *\n     * @return the number of bound parameters.\n     */\n    int size() {\n        return this.parameters.size();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Binding that = (Binding) o;\n        return Objects.equals(this.parameters, that.parameters);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.parameters);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [parameters=\").append(this.parameters);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    public static class RpcParameter {\n\n        final RpcDirection rpcDirection;\n\n        final Encoded encoded;\n\n        public RpcParameter(RpcDirection rpcDirection, Encoded encoded) {\n            this.rpcDirection = rpcDirection;\n            this.encoded = encoded;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/ConnectionOptions.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.codec.Codecs;\nimport reactor.util.annotation.Nullable;\n\nimport java.time.Duration;\nimport java.util.function.Predicate;\n\n/**\n * @author Mark Paluch\n */\nclass ConnectionOptions {\n\n    private final Predicate<String> preferCursoredExecution;\n\n    private final Codecs codecs;\n\n    private final PreparedStatementCache preparedStatementCache;\n\n    private final boolean sendStringParametersAsUnicode;\n\n    private volatile Duration statementTimeout = Duration.ZERO;\n\n    ConnectionOptions(Predicate<String> preferCursoredExecution, Codecs codecs, PreparedStatementCache preparedStatementCache, boolean sendStringParametersAsUnicode) {\n        this.preferCursoredExecution = preferCursoredExecution;\n        this.codecs = codecs;\n        this.preparedStatementCache = preparedStatementCache;\n        this.sendStringParametersAsUnicode = sendStringParametersAsUnicode;\n    }\n\n    public Codecs getCodecs() {\n        return this.codecs;\n    }\n\n    public PreparedStatementCache getPreparedStatementCache() {\n        return this.preparedStatementCache;\n    }\n\n    public boolean prefersCursors(String sql) {\n        return this.preferCursoredExecution.test(sql);\n    }\n\n    public boolean isSendStringParametersAsUnicode() {\n        return this.sendStringParametersAsUnicode;\n    }\n\n    public Duration getStatementTimeout() {\n        return this.statementTimeout;\n    }\n\n    public void setStatementTimeout(@Nullable Duration statementTimeout) {\n        this.statementTimeout = statementTimeout;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [preferCursoredExecution=\").append(this.preferCursoredExecution);\n        sb.append(\", codecs=\").append(this.codecs);\n        sb.append(\", preparedStatementCache=\").append(this.preparedStatementCache);\n        sb.append(\", sendStringParametersAsUnicode=\").append(this.sendStringParametersAsUnicode);\n        sb.append(\", statementTimeout=\").append(this.statementTimeout);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/DefaultMssqlResult.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.util.ReferenceCountUtil;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.message.token.AbstractDoneToken;\nimport io.r2dbc.mssql.message.token.ColumnMetadataToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.NbcRowToken;\nimport io.r2dbc.mssql.message.token.ReturnValue;\nimport io.r2dbc.mssql.message.token.RowToken;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Readable;\nimport io.r2dbc.spi.Result;\nimport io.r2dbc.spi.Row;\nimport io.r2dbc.spi.RowMetadata;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * {@link Result} of query results.\n *\n * @author Mark Paluch\n */\nfinal class DefaultMssqlResult implements MssqlResult {\n\n    private static final Logger LOGGER = Loggers.getLogger(DefaultMssqlResult.class);\n\n    public static final boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();\n\n    private final String sql;\n\n    private final ConnectionContext context;\n\n    private final Codecs codecs;\n\n    private final Flux<io.r2dbc.mssql.message.Message> messages;\n\n    private final boolean expectReturnValues;\n\n    private volatile MssqlRowMetadata rowMetadata;\n\n    private DefaultMssqlResult(String sql, ConnectionContext context, Codecs codecs, Flux<io.r2dbc.mssql.message.Message> messages, boolean expectReturnValues) {\n\n        this.sql = sql;\n        this.context = context;\n        this.codecs = codecs;\n        this.messages = messages;\n        this.expectReturnValues = expectReturnValues;\n    }\n\n    /**\n     * Create a {@link DefaultMssqlResult}.\n     *\n     * @param sql                the underlying SQL statement.\n     * @param codecs             the codecs to use.\n     * @param messages           message stream.\n     * @param expectReturnValues {@code true} if the result is expected to have result values.\n     * @return {@link Result} object.\n     */\n    static MssqlResult toResult(String sql, ConnectionContext context, Codecs codecs, Flux<io.r2dbc.mssql.message.Message> messages, boolean expectReturnValues) {\n\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n        Assert.requireNonNull(codecs, \"Codecs must not be null\");\n        Assert.requireNonNull(context, \"ConnectionContext must not be null\");\n        Assert.requireNonNull(messages, \"Messages must not be null\");\n\n        LOGGER.debug(context.getMessage(\"Creating new result\"));\n\n        return new DefaultMssqlResult(sql, context, codecs, messages, expectReturnValues);\n    }\n\n    @Override\n    public Mono<Long> getRowsUpdated() {\n\n        return this.messages\n            .<Long>handle((message, sink) -> {\n\n                if (message instanceof AbstractDoneToken) {\n\n                    AbstractDoneToken doneToken = (AbstractDoneToken) message;\n                    if (doneToken.hasCount()) {\n\n                        if (DEBUG_ENABLED) {\n                            LOGGER.debug(this.context.getMessage(\"Incoming row count: {}\"), doneToken);\n                        }\n\n                        sink.next(doneToken.getRowCount());\n                    }\n\n                    if (doneToken.isAttentionAck()) {\n                        sink.error(new ExceptionFactory.MssqlStatementCancelled(this.sql));\n                        return;\n                    }\n                }\n\n                if (message instanceof ErrorToken) {\n\n                    sink.error(ExceptionFactory.createException((ErrorToken) message, this.sql));\n                    return;\n                }\n\n                ReferenceCountUtil.release(message);\n            }).reduce(Long::sum);\n    }\n\n    @Override\n    public <T> Flux<T> map(BiFunction<Row, RowMetadata, ? extends T> mappingFunction) {\n        Assert.requireNonNull(mappingFunction, \"Mapping function must not be null\");\n        return doMap(true, false, readable -> {\n            Row row = (Row) readable;\n            return mappingFunction.apply(row, row.getMetadata());\n        });\n    }\n\n    @Override\n    public <T> Publisher<T> map(Function<? super Readable, ? extends T> mappingFunction) {\n        Assert.requireNonNull(mappingFunction, \"Mapping function must not be null\");\n        return doMap(true, true, mappingFunction);\n    }\n\n    private <T> Flux<T> doMap(boolean rows, boolean outparameters, Function<? super Readable, ? extends T> mappingFunction) {\n\n        Flux<T> mappedReturnValues = Flux.empty();\n        Flux<io.r2dbc.mssql.message.Message> messages = this.messages;\n\n        if (this.expectReturnValues && outparameters) {\n\n            List<ReturnValue> returnValues = new ArrayList<>();\n\n            messages = messages.doOnNext(message -> {\n\n                if (message instanceof ReturnValue) {\n                    returnValues.add((ReturnValue) message);\n                }\n            }).filter(it -> !(it instanceof ReturnValue));\n\n            mappedReturnValues = Flux.defer(() -> {\n\n                if (returnValues.size() != 0) {\n\n                    MssqlReturnValues mssqlReturnValues = MssqlReturnValues.toReturnValues(this.codecs, returnValues);\n\n                    try {\n                        return Flux.just(mappingFunction.apply(mssqlReturnValues));\n                    } finally {\n                        mssqlReturnValues.release();\n                    }\n                }\n\n                return Flux.empty();\n            });\n        }\n\n        Flux<T> mapped = messages\n            .handle((message, sink) -> {\n\n                if (message instanceof AbstractDoneToken) {\n\n                    AbstractDoneToken doneToken = (AbstractDoneToken) message;\n                    if (doneToken.isAttentionAck()) {\n                        sink.error(new ExceptionFactory.MssqlStatementCancelled(this.sql));\n                        return;\n                    }\n                }\n\n                if (message.getClass() == ColumnMetadataToken.class) {\n\n                    ColumnMetadataToken token = (ColumnMetadataToken) message;\n\n                    if (!token.hasColumns()) {\n                        return;\n                    }\n\n                    if (DEBUG_ENABLED) {\n                        LOGGER.debug(this.context.getMessage(\"Result column definition: {}\"), message);\n                    }\n\n                    this.rowMetadata = MssqlRowMetadata.create(this.codecs, token);\n\n                    return;\n                }\n\n                if (rows && (message.getClass() == RowToken.class || message.getClass() == NbcRowToken.class)) {\n\n                    MssqlRowMetadata rowMetadata = this.rowMetadata;\n\n                    if (rowMetadata == null) {\n                        sink.error(new IllegalStateException(\"No MssqlRowMetadata available\"));\n                        return;\n                    }\n\n                    MssqlRow row = MssqlRow.toRow(this.codecs, (RowToken) message, rowMetadata);\n                    try {\n                        sink.next(mappingFunction.apply(row));\n                    } finally {\n                        row.release();\n                    }\n\n                    return;\n                }\n\n                if (message instanceof ErrorToken) {\n                    sink.error(ExceptionFactory.createException((ErrorToken) message, this.sql));\n                    return;\n                }\n\n                if (this.expectReturnValues && message instanceof ReturnValue) {\n                    return;\n                }\n\n                ReferenceCountUtil.release(message);\n            });\n\n        if (this.expectReturnValues) {\n            mapped = mapped.concatWith(mappedReturnValues);\n        }\n\n        return mapped;\n    }\n\n    @Override\n    public MssqlResult filter(Predicate<Segment> filter) {\n        return MssqlSegmentResult.toResult(this.sql, this.context, this.codecs, this.messages, this.expectReturnValues).filter(filter);\n    }\n\n    @Override\n    public <T> Flux<T> flatMap(Function<Segment, ? extends Publisher<? extends T>> mappingFunction) {\n        return MssqlSegmentResult.toResult(this.sql, this.context, this.codecs, this.messages, this.expectReturnValues).flatMap(mappingFunction);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/ErrorDetails.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.InfoToken;\nimport io.r2dbc.mssql.util.StringUtils;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Details for an Error.\n *\n * @author Mark Paluch\n * @see ErrorToken\n * @see InfoToken\n */\npublic final class ErrorDetails {\n\n    /**\n     * Error message.\n     */\n    private final String message;\n\n    /**\n     * Info number.\n     */\n    private final long number;\n\n    /**\n     * The error state, used as a modifier to the info Number.\n     */\n    private final int state;\n\n    /**\n     * The class (severity) of the error. A class of less than 10 indicates an informational message.\n     */\n    private final int infoClass;\n\n    /**\n     * The server name length and server name using B_VARCHAR format.\n     */\n    @Nullable\n    private final String serverName;\n\n    /**\n     * The stored procedure name length and stored procedure name using B_VARCHAR format.\n     */\n    @Nullable\n    private final String procName;\n\n    /**\n     * The line number in the SQL batch or stored procedure that caused the error.\n     * Line numbers begin at 1; therefore, if the line number is not applicable to the message as determined by the upper\n     * layer, the value of LineNumber will be 0.\n     */\n    private final long lineNumber;\n\n    /**\n     * Create {@link ErrorDetails}.\n     *\n     * @param message    the exception message.\n     * @param number     the error number.\n     * @param state      SQL state.\n     * @param infoClass  message classification.\n     * @param serverName name of the server.\n     * @param procName   procedure name.\n     * @param lineNumber line number in the offending SQL.\n     */\n    public ErrorDetails(String message, long number, int state, int infoClass, String serverName, String procName,\n                        long lineNumber) {\n        this.message = message;\n        this.number = number;\n        this.state = state;\n        this.infoClass = infoClass;\n        this.serverName = StringUtils.hasText(serverName) ? serverName : null;\n        this.procName = procName;\n        this.lineNumber = lineNumber;\n    }\n\n    /**\n     * Returns the error message.\n     *\n     * @return the error message.\n     */\n    public String getMessage() {\n        return this.message;\n    }\n\n    /**\n     * Returns the message number.\n     *\n     * @return the message number.\n     */\n    public long getNumber() {\n        return this.number;\n    }\n\n    /**\n     * The error state, used as a modifier to the message number.\n     *\n     * @return the error state.\n     */\n    public int getState() {\n        return this.state;\n    }\n\n    /**\n     * The error state code.\n     *\n     * @return the error state.\n     */\n    public String getStateCode() {\n        return getStateCode((int) getNumber(), getState());\n    }\n\n    /**\n     * Returns the severity class of this {@link ErrorDetails}.\n     *\n     * @return severity class of this {@link ErrorDetails}.\n     */\n    public int getInfoClass() {\n        return this.infoClass;\n    }\n\n    /**\n     * Returns the server name.\n     *\n     * @return the server name.\n     */\n    @Nullable\n    public String getServerName() {\n        return this.serverName;\n    }\n\n    /**\n     * Returns the procedure name.\n     *\n     * @return the procedure name.\n     */\n    @Nullable\n    public String getProcName() {\n        return this.procName;\n    }\n\n    /**\n     * The line number in the SQL batch or stored procedure that caused the error. Line numbers begin at 1; therefore, if the line number is not applicable to the message as determined by the upper\n     * layer, the value of LineNumber will be 0.\n     *\n     * @return the line number in the SQL batch.\n     */\n    public long getLineNumber() {\n        return this.lineNumber;\n    }\n\n    private String getStateCode(int errNum, int databaseState) {\n\n        switch (errNum) {\n            // case 18456: return \"08001\"; //username password wrong at login\n            case 8152:\n                return \"22001\"; // String data right truncation\n            case 515: // 2.2705\n            case 547:\n                return \"23000\";  // Integrity constraint violation\n            case 2601:\n                return \"23000\";  // Integrity constraint violation\n            case 2714:\n                return \"S0001\"; // table already exists\n            case 208:\n                return \"S0002\";  // table not found\n            case 1205:\n                return \"40001\"; // deadlock detected\n            case 2627:\n                return \"23000\"; // DPM 4.04. Primary key violation\n        }\n        return \"S000\" + databaseState;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/EscapeAwareNameMatcher.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport reactor.util.annotation.Nullable;\n\nimport java.util.Collection;\n\n/**\n * Matcher utility for column name  ({@code sysname}) comparison. Uses case-insensitive comparison by default.\n * Supports name escaping with square brackets ({@code [sysname]}) to enforce case-sensitive comparison rules.\n *\n * @author Mark Paluch\n */\nfinal class EscapeAwareNameMatcher {\n\n    @Nullable\n    public static String find(String name, Collection<String> names) {\n\n        for (String s : names) {\n            if (matches(name, s)) {\n                return s;\n            }\n        }\n\n        return null;\n    }\n\n    private static boolean matches(String o1, String o2) {\n\n        boolean exactMatch = false;\n\n        if (o1.startsWith(\"[\") && o1.endsWith(\"]\")) {\n            exactMatch = true;\n            o1 = o1.substring(1, o1.length() - 1);\n        }\n\n        if (o2.startsWith(\"[\") && o2.endsWith(\"]\")) {\n            exactMatch = true;\n            o2 = o2.substring(1, o2.length() - 1);\n        }\n\n        return exactMatch ? o1.equals(o2) : o1.equalsIgnoreCase(o2);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/ExceptionFactory.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.token.AbstractInfoToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.InfoToken;\nimport io.r2dbc.spi.R2dbcBadGrammarException;\nimport io.r2dbc.spi.R2dbcDataIntegrityViolationException;\nimport io.r2dbc.spi.R2dbcException;\nimport io.r2dbc.spi.R2dbcNonTransientException;\nimport io.r2dbc.spi.R2dbcNonTransientResourceException;\nimport io.r2dbc.spi.R2dbcPermissionDeniedException;\nimport io.r2dbc.spi.R2dbcRollbackException;\nimport io.r2dbc.spi.R2dbcTimeoutException;\nimport io.r2dbc.spi.R2dbcTransientException;\nimport io.r2dbc.spi.R2dbcTransientResourceException;\nimport reactor.core.publisher.SynchronousSink;\n\nimport static io.r2dbc.mssql.message.token.AbstractInfoToken.Classification.GENERAL_ERROR;\n\n/**\n * Factory for SQL Server-specific {@link R2dbcException}s.\n *\n * @author Mark Paluch\n */\nfinal class ExceptionFactory {\n\n    private final String sql;\n\n    private ExceptionFactory(String sql) {\n        this.sql = sql;\n    }\n\n    /**\n     * Creates a {@link ExceptionFactory} associated with a SQL query.\n     *\n     * @param sql\n     * @return\n     */\n    static ExceptionFactory withSql(String sql) {\n        return new ExceptionFactory(sql);\n    }\n\n    /**\n     * Create a {@link R2dbcException} from {@link InfoToken}.\n     *\n     * @param token\n     * @return\n     */\n    R2dbcException createException(AbstractInfoToken token) {\n        return createException(token, this.sql);\n    }\n\n    /**\n     * Creates a {@link R2dbcException} from an {@link AbstractInfoToken}.\n     *\n     * @param token the token that contains the error details.\n     * @param sql   underlying SQL.\n     * @return the {@link R2dbcException}.\n     * @see ErrorToken\n     */\n    static R2dbcException createException(AbstractInfoToken token, String sql) {\n\n        switch ((int) token.getNumber()) {\n            case 106: // Too many table names in the query. The maximum allowable is %d.\n            case 130: // Cannot perform an aggregate function on an expression containing an aggregate or a subquery.\n            case 206: // Operand type clash: %ls is incompatible with %ls.\n            case 207: // Invalid column name '%.*ls'.\n            case 208: // Invalid object name '%.*ls'.\n            case 209: // Ambiguous column name '%.*ls'.\n            case 213: // Column name or number of supplied values does not match table definition.\n            case 267: // Object '%.*ls' cannot be found.\n            case 565: // A stack overflow occurred in the server while compiling the query. Please simplify the query.\n            case 4408: // Too many tables. The query and the views or functions in it exceed the limit of %d tables. Revise the query to reduce the number of tables.\n            case 2812: // Could not find stored procedure '%.*ls'.\n                return new MssqlBadGrammarException(createErrorDetails(token), sql);\n\n            case 2601: // Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'. The duplicate key value is %ls.\n            case 2627: // Violation of %ls constraint '%.*ls'. Cannot insert duplicate key in object '%.*ls'. The duplicate key value is %ls.\n            case 544: // Cannot insert explicit value for identity column in table '%.*ls' when IDENTITY_INSERT is set to OFF.\n            case 8114: // Error converting data type %ls to %ls.\n            case 8115:  // Arithmetic overflow error converting %ls to data type %ls.\n                return new MssqlDataIntegrityViolationException(createErrorDetails(token), sql);\n\n            case 1222: // Lock request time out period exceeded.\n                return new MssqlTimeoutException(createErrorDetails(token), sql);\n\n            case 701: // Maximum number of databases used for each query has been exceeded. The maximum allowed is %d.\n            case 1204: // The instance of the SQL Server Database Engine cannot obtain a LOCK resource at this time. Rerun your statement when there are fewer active users. Ask the database\n                // administrator to check the lock and memory configuration for this instance, or to check for\n                return new MssqlTransientException(createErrorDetails(token), sql);\n\n            case 1203: // Process ID %d attempted to unlock a resource it does not own: %.*ls. Retry the transaction, because this error may be caused by a timing condition. If the problem\n                // persists, contact the database administrator.\n            case 1215:  // A conflicting ABORT_AFTER_WAIT = BLOCKERS request is waiting for existing transactions to rollback. This request cannot be executed. Please retry when the previous\n                // request is completed.\n            case 1216: // The DDL statement with ABORT_AFTER_WAIT = BLOCKERS option cannot be completed due to a conflicting system task. The request can abort only user transactions. Please wait\n                // for the system task to complete and retry.\n            case 1221: // The Database Engine is attempting to release a group of locks that are not currently held by the transaction. Retry the transaction. If the problem persists, contact your\n                // support provider.\n            case 1206: // The Microsoft Distributed Transaction Coordinator (MS DTC) has cancelled the distributed transaction.\n            case 3938: // The transaction has been stopped because it conflicted with the execution of a FILESTREAM close operation using the same transaction.  The transaction will be rolled back.\n            case 28611: // The request is aborted because the transaction has been aborted by Matrix Transaction Coordination Manager. This is mostly caused by one or more transaction particpant\n                // brick went offline.\n                return new MssqlRollbackException(createErrorDetails(token), sql);\n\n            case 921:  // Database '%.*ls' has not been recovered yet. Wait and try again.\n            case 941:  // Database '%.*ls' cannot be opened because it is not started. Retry when the database is started.\n            case 1105:  // Could not allocate space for object '%.*ls'%.*ls in database '%.*ls' because the '%.*ls' filegroup is full. Create disk space by deleting unneeded files, dropping objects\n                // in the filegroup, adding additional files to the filegroup, or setting autogrowth on\n            case 1456: // The ALTER DATABASE command could not be sent to the remote server instance '%.*ls'. The database mirroring configuration was not changed. Verify that the server is\n                // connected, and try again.\n            case 5061: // ALTER DATABASE failed because a lock could not be placed on database '%.*ls'. Try again later.\n            case 10930: // The service is currently too busy. Please try again later.\n            case 45168: // The server '%.*ls' has too many active connections.  Try again later.\n            case 40642: // The server is currently too busy.  Please try again later.\n            case 40675: // The service is currently too busy.  Please try again later.\n            case 40825: // Unable to complete request now. Please try again later.\n                return new MssqlTransientResourceException(createErrorDetails(token), sql);\n        }\n\n        if (token.getClassification() == GENERAL_ERROR && token.getNumber() == 4002) {\n            return new ProtocolException(token.getMessage());\n        }\n\n        switch (token.getClassification()) {\n            case OBJECT_DOES_NOT_EXIST:\n            case SYNTAX_ERROR:\n                return new MssqlBadGrammarException(createErrorDetails(token), sql);\n            case INCONSISTENT_NO_LOCK:\n                return new MssqlDataIntegrityViolationException(createErrorDetails(token), sql);\n            case TX_DEADLOCK:\n                return new MssqlRollbackException(createErrorDetails(token), sql);\n            case SECURITY:\n                return new MssqlPermissionDeniedException(createErrorDetails(token), sql);\n            case GENERAL_ERROR:\n                return new MssqlNonTransientException(createErrorDetails(token), sql);\n            case OUT_OF_RESOURCES:\n                return new MssqlTransientResourceException(createErrorDetails(token), sql);\n            default:\n                return new MssqlNonTransientResourceException(createErrorDetails(token), sql);\n        }\n    }\n\n    /**\n     * Handle {@link Message}s and inspect for {@link ErrorToken} to emit a {@link R2dbcException}.\n     *\n     * @param message the message.\n     * @param sink    the outbound sink.\n     */\n    void handleErrorResponse(Message message, SynchronousSink<Message> sink) {\n\n        if (message instanceof ErrorToken) {\n            sink.error(createException((ErrorToken) message, this.sql));\n        } else {\n            sink.next(message);\n        }\n    }\n\n    /**\n     * Create a {@link R2dbcException} for a {@link ErrorToken}.\n     *\n     * @param message the message.\n     */\n    RuntimeException createException(ErrorToken message) {\n        return createException(message, this.sql);\n    }\n\n    static ErrorDetails createErrorDetails(AbstractInfoToken token) {\n        return new ErrorDetails(token.getMessage(), token.getNumber(), token.getState(), token.getInfoClass(), token.getServerName(), token.getProcName(), token.getLineNumber());\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcBadGrammarException}.\n     */\n    static final class MssqlBadGrammarException extends R2dbcBadGrammarException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        MssqlBadGrammarException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcDataIntegrityViolationException}.\n     */\n    static final class MssqlDataIntegrityViolationException extends R2dbcDataIntegrityViolationException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        MssqlDataIntegrityViolationException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcNonTransientException}.\n     */\n    static final class MssqlNonTransientException extends R2dbcNonTransientException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        MssqlNonTransientException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcNonTransientResourceException}.\n     */\n    static final class MssqlNonTransientResourceException extends R2dbcNonTransientResourceException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        MssqlNonTransientResourceException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcPermissionDeniedException}.\n     */\n    static final class MssqlPermissionDeniedException extends R2dbcPermissionDeniedException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        MssqlPermissionDeniedException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcRollbackException}.\n     */\n    static final class MssqlRollbackException extends R2dbcRollbackException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        MssqlRollbackException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcTimeoutException}.\n     */\n    static final class MssqlTimeoutException extends R2dbcTimeoutException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        MssqlTimeoutException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcTimeoutException}.\n     */\n    static final class MssqlStatementTimeoutException extends R2dbcTimeoutException {\n\n        public MssqlStatementTimeoutException(String reason, String sql) {\n            super(reason, sql);\n        }\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcTransientException}.\n     */\n    static final class MssqlTransientException extends R2dbcTransientException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        public MssqlTransientException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcTransientException} upon statement cancellation due to an attention acknowledgement.\n     */\n    static final class MssqlStatementCancelled extends R2dbcTransientException {\n\n        public MssqlStatementCancelled(String sql) {\n            super(\"Statement cancelled\", null, 0, sql);\n        }\n\n    }\n\n    /**\n     * SQL Server-specific {@link R2dbcTransientResourceException}.\n     */\n    private static final class MssqlTransientResourceException extends R2dbcTransientResourceException implements MssqlException {\n\n        private final ErrorDetails errorDetails;\n\n        MssqlTransientResourceException(ErrorDetails errorDetails, String sql) {\n            super(errorDetails.getMessage(), errorDetails.getStateCode(), (int) errorDetails.getNumber(), sql);\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public ErrorDetails getErrorDetails() {\n            return this.errorDetails;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/GeneratedValues.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.token.AbstractDoneToken;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Result;\nimport reactor.core.publisher.Flux;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Utility to generated the generated values clause using {@code SCOPE_IDENTITY()}.\n *\n * @author Mark Paluch\n */\nfinal class GeneratedValues {\n\n    private static final String GENERATED_KEYS_QUERY = \"SELECT SCOPE_IDENTITY() AS \";\n\n    private static final String DEFAULT_GENERATED_KEY_NAME = \"GENERATED_KEYS\";\n\n    /**\n     * Adjust the message flow to emit the first received count frame at a later time, when a second {@link AbstractDoneToken} is emitted. This reordering prevents multi-result creation (i.e.\n     * emitting only a single {@link Result}) and merges the count into the {@link Result} by replacing the second {@link AbstractDoneToken}.\n     *\n     * @param messages the message flow.\n     * @return the transformed message flow.\n     */\n    static Flux<Message> reduceToSingleCountDoneToken(Flux<? extends Message> messages) {\n\n        return Flux.defer(() -> {\n\n            AtomicReference<AbstractDoneToken> countToken = new AtomicReference<>();\n            AtomicBoolean rerouteCountFrame = new AtomicBoolean(true);\n\n            return messages.handle((message, sink) -> {\n\n                if (rerouteCountFrame.get() && AbstractDoneToken.hasCount(message)) {\n\n                    if (countToken.get() == null) {\n                        countToken.set((AbstractDoneToken) message);\n                        return;\n                    }\n\n                    if (countToken.get() != null) {\n\n                        rerouteCountFrame.set(false);\n                        sink.next(countToken.get());\n                        return;\n                    }\n                }\n\n                sink.next(message);\n            });\n        });\n    }\n\n    /**\n     * Augment query for generated keys retrieval. Prior to augmenting, use {@link #shouldExpectGeneratedKeys(String[])} to check whether augmentation should apply.\n     *\n     * @param sql              the query to to augment.\n     * @param generatedColumns column name for the generated keys. Can be {@code null}.\n     * @return the potentially augmented query.\n     * @see #shouldExpectGeneratedKeys(String[])\n     */\n    static String augmentQuery(String sql, @Nullable String[] generatedColumns) {\n\n        if (shouldExpectGeneratedKeys(generatedColumns)) {\n            return sql + \" \" + getGeneratedKeysClause(generatedColumns);\n        }\n\n        return sql;\n    }\n\n    /**\n     * Returns {@code true} whether {@code generatedColumns} indicate the caller to expect generated keys.\n     *\n     * @param generatedColumns column name for the generated keys. Can be {@code null}.\n     * @return {@code true} whether {@code generatedColumns} indicates that keys should be generated.\n     */\n    static boolean shouldExpectGeneratedKeys(@Nullable String[] generatedColumns) {\n        return generatedColumns != null;\n    }\n\n    /**\n     * Return the key generation clause. Allows column name customization by passing a single column name. Multiple column names are not supported.\n     *\n     * @param columns column names. Defaults to {@link #DEFAULT_GENERATED_KEY_NAME} if no column name specified.\n     * @return the generated keys clause.\n     * @throws IllegalArgumentException      if {@code columns} is {@code null}.\n     * @throws UnsupportedOperationException if {@code columns} contains more than one column.\n     */\n    static String getGeneratedKeysClause(String... columns) {\n\n        Assert.requireNonNull(columns, \"Columns must not be null\");\n\n        if (columns.length == 0) {\n            return GENERATED_KEYS_QUERY + DEFAULT_GENERATED_KEY_NAME;\n        }\n\n        if (columns.length == 1) {\n\n            Assert.requireNonNull(columns[0], \"Column must not be null\");\n            return GENERATED_KEYS_QUERY + columns[0];\n        }\n\n        throw new UnsupportedOperationException(\"SQL Server does not support multiple generated keys\");\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/IndefinitePreparedStatementCache.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Function;\n\n/**\n * Cache that stores prepared statement handles eternally.\n *\n * @author Mark Paluch\n */\nclass IndefinitePreparedStatementCache implements PreparedStatementCache {\n\n    private final Map<String, Integer> preparedStatements = new ConcurrentHashMap<>();\n\n    private final Map<String, Object> parsedSql = new ConcurrentHashMap<>();\n\n    @Override\n    public int getHandle(String sql, Binding binding) {\n\n        Assert.requireNonNull(sql, \"SQL query must not be null\");\n        Assert.requireNonNull(binding, \"Binding query must not be null\");\n\n        return this.preparedStatements.getOrDefault(createKey(sql, binding), UNPREPARED);\n    }\n\n    @Override\n    public void putHandle(int handle, String sql, Binding binding) {\n\n        Assert.requireNonNull(sql, \"SQL query must not be null\");\n        Assert.requireNonNull(binding, \"Binding query must not be null\");\n\n        this.preparedStatements.put(createKey(sql, binding), handle);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> T getParsedSql(String sql, Function<String, T> parseFunction) {\n        return (T) this.parsedSql.computeIfAbsent(sql, parseFunction);\n    }\n\n    @Override\n    public int size() {\n        return this.preparedStatements.size();\n    }\n\n    private static String createKey(String sql, Binding binding) {\n        return sql + \"-\" + binding.getFormalParameters();\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [preparedStatements=\").append(this.preparedStatements);\n        sb.append(\", parsedSql=\").append(this.parsedSql);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/LoginConfiguration.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.message.token.Login7;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.StringUtils;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.UUID;\n\n/**\n * Login configuration properties. Used to build a {@link Login7} message.\n *\n * @author Mark Paluch\n */\nfinal class LoginConfiguration {\n\n    @Nullable\n    private final String applicationName;\n\n    @Nullable\n    private final UUID connectionId;\n\n    private final String database;\n\n    private final String hostname;\n\n    private final CharSequence password;\n\n    private final String serverName;\n\n    private final boolean useSsl;\n\n    private final String username;\n\n    LoginConfiguration(@Nullable String applicationName, @Nullable UUID connectionId, String database, String hostname, CharSequence password, String serverName, boolean useSsl, String username) {\n\n        this.username = Assert.requireNonNull(username, \"Username must not be null\");\n        this.password = Assert.requireNonNull(password, \"Password must not be null\");\n        this.database = Assert.requireNonNull(database, \"Database must not be null\");\n        this.hostname = Assert.requireNonNull(hostname, \"Hostname must not be null\");\n        this.applicationName = applicationName;\n        this.serverName = Assert.requireNonNull(serverName, \"Server name must not be null\");\n        this.connectionId = connectionId;\n        this.useSsl = useSsl;\n    }\n\n    @Nullable\n    UUID getConnectionId() {\n        return this.connectionId;\n    }\n\n    boolean useSsl() {\n        return this.useSsl;\n    }\n\n    Login7.Builder asBuilder() {\n\n        Login7.Builder builder = Login7.builder().username(this.username).password(this.password).database(this.database)\n            .hostName(this.hostname).serverName(this.serverName);\n\n        if (StringUtils.hasText(this.applicationName)) {\n            builder.applicationName(this.applicationName);\n        }\n        return builder;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/LoginFlow.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.client.ssl.SslState;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.TDSVersion;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.Login7;\nimport io.r2dbc.mssql.message.token.Prelogin;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Sinks;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static io.r2dbc.mssql.util.PredicateUtils.or;\n\n/**\n * A utility class that encapsulates the Login message flow.\n *\n * @author Mark Paluch\n */\nfinal class LoginFlow {\n\n    private LoginFlow() {\n    }\n\n    /**\n     * @param client the {@link Client} to exchange messages with\n     * @param login  the login configuration for login negotiation\n     * @return the messages received after authentication is complete, in response to this exchange\n     */\n    static Flux<Message> exchange(Client client, LoginConfiguration login) {\n\n        Assert.requireNonNull(client, \"client must not be null\");\n        Assert.requireNonNull(login, \"Login must not be null\");\n\n\n        Prelogin.Builder builder = Prelogin.builder();\n        if (login.getConnectionId() != null) {\n            builder.withConnectionId(login.getConnectionId());\n        }\n\n        if (login.useSsl()) {\n            builder.withEncryptionEnabled();\n        }\n\n        AtomicReference<Prelogin> preloginResponse = new AtomicReference<>();\n        Sinks.Many<ClientMessage> requests = Sinks.many().unicast().onBackpressureBuffer();\n\n        Prelogin request = builder.build();\n        requests.emitNext(request, Sinks.EmitFailureHandler.FAIL_FAST);\n\n        return client.exchange(requests.asFlux(), DoneToken::isDone) //\n            .filter(or(Prelogin.class::isInstance, SslState.class::isInstance, DoneToken.class::isInstance, ErrorToken.class::isInstance)) //\n            .handle((message, sink) -> {\n\n                try {\n\n                    if (message instanceof Prelogin) {\n\n                        Prelogin response = (Prelogin) message;\n                        preloginResponse.set(response);\n\n                        Prelogin.Encryption encryption = response.getRequiredToken(Prelogin.Encryption.class);\n\n                        if (!encryption.requiresSslHandshake()) {\n                            requests.emitNext(createLoginMessage(login, response), Sinks.EmitFailureHandler.FAIL_FAST);\n                        }\n\n                        return;\n                    }\n\n                    if (message instanceof SslState && message == SslState.NEGOTIATED) {\n\n                        Prelogin prelogin = preloginResponse.get();\n                        requests.emitNext(createLoginMessage(login, prelogin), Sinks.EmitFailureHandler.FAIL_FAST);\n                        return;\n                    }\n\n                    if (DoneToken.isDone(message)) {\n                        sink.next(message);\n                        sink.complete();\n\n                        return;\n                    }\n\n                    if (message instanceof ErrorToken) {\n                        sink.error(ExceptionFactory.createException((ErrorToken) message, \"\"));\n                        client.close().subscribe();\n                        return;\n                    }\n\n                    throw ProtocolException.unsupported(String.format(\"Unexpected login flow message: %s\", message));\n                } catch (Exception e) {\n                    requests.emitError(e, Sinks.EmitFailureHandler.FAIL_FAST);\n                    sink.error(e);\n                }\n            });\n    }\n\n    private static Login7 createLoginMessage(LoginConfiguration login, Prelogin prelogin) {\n\n        Prelogin.Version serverVersion = prelogin.getRequiredToken(Prelogin.Version.class);\n        TDSVersion tdsVersion = getTdsVersion(serverVersion.getVersion());\n\n        return login.asBuilder().tdsVersion(tdsVersion).build();\n    }\n\n    private static TDSVersion getTdsVersion(int serverVersion) {\n\n        if (serverVersion >= 11) // Denali --> TDS 7.4\n        {\n            return TDSVersion.VER_DENALI;\n        }\n\n        if (serverVersion >= 10) // Katmai (10.0) & later 7.3B\n        {\n            return TDSVersion.VER_KATMAI;\n        }\n\n        if (serverVersion >= 9) // Yukon (9.0) --> TDS 7.2 // Prelogin disconnects anything older\n        {\n            return TDSVersion.VER_YUKON;\n        }\n\n        throw ProtocolException.unsupported(\"Unsupported server version: \" + serverVersion);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlBatch.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Batch;\nimport reactor.core.publisher.Flux;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * An implementation of {@link Batch} for executing a collection of statements in a batch against a Microsoft SQL Server database.\n *\n * @author Mark Paluch\n */\npublic final class MssqlBatch implements Batch {\n\n    private final Client client;\n\n    private final ConnectionOptions connectionOptions;\n\n    private final List<String> statements = new ArrayList<>();\n\n    MssqlBatch(Client client, ConnectionOptions connectionOptions) {\n\n        this.client = Assert.requireNonNull(client, \"Client must not be null\");\n        this.connectionOptions = Assert.requireNonNull(connectionOptions, \"ConnectionOptions must not be null\");\n    }\n\n    @Override\n    public MssqlBatch add(String sql) {\n\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n\n        this.statements.add(sql);\n        return this;\n    }\n\n    @Override\n    public Flux<MssqlResult> execute() {\n        return new SimpleMssqlStatement(this.client, this.connectionOptions, String.join(\"; \", this.statements))\n            .execute();\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [client=\").append(this.client);\n        sb.append(\", connectionOptions=\").append(this.connectionOptions);\n        sb.append(\", statements=\").append(this.statements);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlColumnMetadata.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.codec.Decodable;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.ColumnMetadata;\nimport io.r2dbc.spi.Nullability;\nimport io.r2dbc.spi.OutParameterMetadata;\nimport io.r2dbc.spi.Type;\n\nimport javax.annotation.Nonnull;\n\n/**\n * Microsoft SQL Server-specific {@link ColumnMetadata} based on {@link Decodable}.\n *\n * @author Mark Paluch\n */\npublic final class MssqlColumnMetadata implements ColumnMetadata, OutParameterMetadata {\n\n    private final Decodable decodable;\n\n    private final Codecs codecs;\n\n    /**\n     * Creates a new {@link MssqlColumnMetadata}.\n     *\n     * @param decodable the column.\n     * @param codecs    the {@link Codecs codec registry}\n     */\n    MssqlColumnMetadata(Decodable decodable, Codecs codecs) {\n        this.decodable = Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        this.codecs = Assert.requireNonNull(codecs, \"Codecs must not be null\");\n    }\n\n    @Override\n    public String getName() {\n        return this.decodable.getName();\n    }\n\n    @Override\n    public Integer getPrecision() {\n        return getNativeTypeMetadata().getPrecision();\n    }\n\n    @Override\n    public Integer getScale() {\n        return getNativeTypeMetadata().getScale();\n    }\n\n    @Override\n    public Nullability getNullability() {\n        return getNativeTypeMetadata().isNullable() ? Nullability.NULLABLE : Nullability.NON_NULL;\n    }\n\n    @Override\n    public Class<?> getJavaType() {\n        return this.codecs.getJavaType(getNativeTypeMetadata());\n    }\n\n    @Override\n    public Type getType() {\n        return getNativeTypeMetadata().getServerType();\n    }\n\n    @Override\n    @Nonnull\n    public TypeInformation getNativeTypeMetadata() {\n        return this.decodable.getType();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlConnection.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.api.MssqlTransactionDefinition;\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.client.TransactionStatus;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Connection;\nimport io.r2dbc.spi.IsolationLevel;\nimport io.r2dbc.spi.Option;\nimport io.r2dbc.spi.TransactionDefinition;\nimport io.r2dbc.spi.ValidationDepth;\nimport org.reactivestreams.Publisher;\nimport org.reactivestreams.Subscription;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\n\nimport java.time.Duration;\nimport java.util.function.Function;\nimport java.util.regex.Pattern;\n\n/**\n * {@link Connection} to a Microsoft SQL Server.\n *\n * @author Mark Paluch\n * @author Hebert Coelho\n * @author Nayan Hajratwala\n * @see MssqlConnection\n * @see DefaultMssqlResult\n * @see ErrorDetails\n */\npublic final class MssqlConnection implements Connection {\n\n    private static final Pattern IDENTIFIER_PATTERN = Pattern.compile(\"[\\\\d\\\\w_]{1,32}\");\n\n    private static final Pattern IDENTIFIER128_PATTERN = Pattern.compile(\"[\\\\d\\\\w_]{1,128}\");\n\n    private static final Logger logger = Loggers.getLogger(MssqlConnection.class);\n\n    private final Client client;\n\n    private final MssqlConnectionMetadata metadata;\n\n    private final ConnectionContext context;\n\n    private final ConnectionOptions connectionOptions;\n\n    private final Flux<Long> validationQuery;\n\n    private volatile boolean autoCommit;\n\n    private volatile IsolationLevel isolationLevel;\n\n    private volatile IsolationLevel previousIsolationLevel;\n\n    private volatile boolean resetLockWaitTime = false;\n\n    MssqlConnection(Client client, MssqlConnectionMetadata connectionMetadata, ConnectionOptions connectionOptions) {\n\n        this.client = Assert.requireNonNull(client, \"Client must not be null\");\n        this.metadata = connectionMetadata;\n        this.context = client.getContext();\n        this.connectionOptions = Assert.requireNonNull(connectionOptions, \"ConnectionOptions must not be null\");\n\n        TransactionStatus transactionStatus = client.getTransactionStatus();\n        this.autoCommit = transactionStatus == TransactionStatus.AUTO_COMMIT;\n        this.isolationLevel = IsolationLevel.READ_COMMITTED;\n        this.validationQuery = new SimpleMssqlStatement(this.client, connectionOptions, \"SELECT 1\").fetchSize(0).execute().flatMap(MssqlResult::getRowsUpdated);\n    }\n\n    @Override\n    public Mono<Void> beginTransaction() {\n        return beginTransaction(EmptyTransactionDefinition.INSTANCE);\n    }\n\n    @Override\n    public Mono<Void> beginTransaction(TransactionDefinition transactionDefinition) {\n\n        return useTransactionStatus(tx -> {\n\n            if (tx == TransactionStatus.STARTED) {\n                logger.debug(this.context.getMessage(\"Skipping begin transaction because status is [{}]\"), tx);\n                return Mono.empty();\n            }\n\n            String name = transactionDefinition.getAttribute(MssqlTransactionDefinition.NAME);\n            String mark = transactionDefinition.getAttribute(MssqlTransactionDefinition.MARK);\n            IsolationLevel isolationLevel = transactionDefinition.getAttribute(MssqlTransactionDefinition.ISOLATION_LEVEL);\n            Duration lockWaitTime = transactionDefinition.getAttribute(MssqlTransactionDefinition.LOCK_WAIT_TIMEOUT);\n\n            StringBuilder builder = new StringBuilder();\n\n            builder.append(\"BEGIN TRANSACTION\");\n            if (name != null) {\n                String nameToUse = sanitize(name, 32);\n                Assert.isTrue(IDENTIFIER_PATTERN.matcher(nameToUse).matches(), \"Transaction names must contain only characters and numbers and must not exceed 32 characters\");\n                builder.append(\" \").append(nameToUse);\n\n                if (mark != null) {\n                    String markToUse = sanitize(mark, 128);\n                    Assert.isTrue(IDENTIFIER128_PATTERN.matcher(markToUse.substring(0, Math.min(128, markToUse.length()))).matches(), \"Transaction names must contain only characters and numbers and\" +\n                            \" must not exceed 128 characters\");\n                    builder.append(' ').append(\"WITH MARK '\").append(markToUse).append(\"'\");\n                }\n            }\n            builder.append(';');\n\n            if (isolationLevel != null) {\n                builder.append(renderSetIsolationLevel(isolationLevel)).append(';');\n            }\n\n            if (lockWaitTime != null) {\n                this.resetLockWaitTime = true;\n                builder.append(\"SET LOCK_TIMEOUT \").append(lockWaitTime.isNegative() ? \"-1\" : lockWaitTime.toMillis()).append(';');\n            }\n\n            logger.debug(this.context.getMessage(\"Beginning transaction from status [{}]\"), tx);\n\n            return exchange(builder.toString()).doOnSuccess(unused -> {\n\n                this.previousIsolationLevel = this.isolationLevel;\n\n                if (isolationLevel != null) {\n                    this.isolationLevel = isolationLevel;\n                }\n            });\n        });\n    }\n\n    /**\n     * Cancel an ongoing request.\n     *\n     * @return\n     */\n    Mono<Void> cancel() {\n        return this.client.attention();\n    }\n\n    @Override\n    public Mono<Void> close() {\n        return this.client.close();\n    }\n\n    @Override\n    public Mono<Void> commitTransaction() {\n\n        return useTransactionStatus(tx -> {\n\n            if (tx != TransactionStatus.STARTED) {\n                logger.debug(this.context.getMessage(\"Skipping commit transaction because status is [{}]\"), tx);\n                return Mono.empty();\n            }\n\n            logger.debug(this.context.getMessage(\"Committing transaction with status [{}]\"), tx);\n\n            return exchange(\"IF @@TRANCOUNT > 0 COMMIT TRANSACTION;\" + cleanup()).doOnSuccess(v -> {\n\n                if (this.previousIsolationLevel != null) {\n                    this.isolationLevel = this.previousIsolationLevel;\n                    this.previousIsolationLevel = null;\n                }\n\n                this.resetLockWaitTime = false;\n            });\n        });\n    }\n\n    private String cleanup() {\n\n        String cleanupSql = \"\";\n        if (this.previousIsolationLevel != null && this.previousIsolationLevel != this.isolationLevel) {\n            cleanupSql = renderSetIsolationLevel(this.previousIsolationLevel) + \";\";\n        }\n\n        if (this.resetLockWaitTime) {\n            cleanupSql += \"SET LOCK_TIMEOUT -1;\";\n        }\n\n        return cleanupSql;\n    }\n\n    @Override\n    public MssqlBatch createBatch() {\n        return new MssqlBatch(this.client, this.connectionOptions);\n    }\n\n    @Override\n    public Mono<Void> createSavepoint(String name) {\n\n        Assert.requireNonNull(name, \"Savepoint name must not be null\");\n\n        String nameToUse = sanitize(name, 32);\n        Assert.isTrue(IDENTIFIER_PATTERN.matcher(nameToUse).matches(), \"Save point names must contain only characters and numbers and must not exceed 32 characters\");\n\n        return useTransactionStatus(tx -> {\n\n            logger.debug(this.context.getMessage(\"Creating savepoint [{}] for transaction with status [{}]\"), nameToUse, tx);\n\n            if (this.autoCommit) {\n                logger.debug(this.context.getMessage(\"Setting auto-commit mode to [false]\"));\n            }\n\n            return exchange(String.format(\"SET IMPLICIT_TRANSACTIONS ON; IF @@TRANCOUNT = 0 BEGIN BEGIN TRAN IF @@TRANCOUNT = 2 COMMIT TRAN END SAVE TRAN %s;\", nameToUse)).doOnSuccess(ignore -> {\n                this.autoCommit = false;\n            });\n        });\n    }\n\n    @Override\n    public MssqlStatement createStatement(String sql) {\n\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n        logger.debug(this.context.getMessage(\"Creating statement for SQL: [{}]\"), sql);\n\n        if (ParametrizedMssqlStatement.supports(sql)) {\n            return new ParametrizedMssqlStatement(this.client, this.connectionOptions, sql);\n        }\n\n        return new SimpleMssqlStatement(this.client, this.connectionOptions, sql);\n    }\n\n    @Override\n    public Mono<Void> releaseSavepoint(String name) {\n        return Mono.empty();\n    }\n\n    @Override\n    public Mono<Void> rollbackTransaction() {\n\n        return useTransactionStatus(tx -> {\n\n            if (tx != TransactionStatus.STARTED && tx != TransactionStatus.EXPLICIT) {\n                logger.debug(this.context.getMessage(\"Skipping rollback transaction because status is [{}]\"), tx);\n                return Mono.empty();\n            }\n\n            logger.debug(this.context.getMessage(\"Rolling back transaction with status [{}]\"), tx);\n\n            return exchange(\"IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;\" + cleanup()).doOnSuccess(v -> {\n\n                if (this.previousIsolationLevel != null) {\n                    this.isolationLevel = this.previousIsolationLevel;\n                    this.previousIsolationLevel = null;\n                }\n\n                this.resetLockWaitTime = false;\n            }).doOnSuccess(v -> {\n\n                if (this.previousIsolationLevel != null) {\n                    this.isolationLevel = this.previousIsolationLevel;\n                    this.previousIsolationLevel = null;\n                }\n\n                this.resetLockWaitTime = false;\n            });\n        });\n    }\n\n    @Override\n    public Mono<Void> rollbackTransactionToSavepoint(String name) {\n\n        Assert.requireNonNull(name, \"Savepoint name must not be null\");\n        String nameToUse = sanitize(name, 32);\n        Assert.isTrue(IDENTIFIER_PATTERN.matcher(nameToUse).matches(), \"Save point names must contain only characters and numbers and must not exceed 32 characters\");\n\n        return useTransactionStatus(tx -> {\n\n            if (tx != TransactionStatus.STARTED) {\n                logger.debug(this.context.getMessage(\"Skipping rollback transaction to savepoint [{}] because status is [{}]\"), nameToUse, tx);\n                return Mono.empty();\n            }\n\n            logger.debug(this.context.getMessage(\"Rolling back transaction to savepoint [{}] with status [{}]\"), nameToUse, tx);\n\n            return exchange(String.format(\"ROLLBACK TRANSACTION %s\", nameToUse));\n        });\n    }\n\n    public boolean isAutoCommit() {\n        return this.autoCommit && this.client.getTransactionStatus() != TransactionStatus.STARTED;\n    }\n\n    public Mono<Void> setAutoCommit(boolean autoCommit) {\n\n        return Mono.defer(() -> {\n\n            StringBuilder builder = new StringBuilder();\n\n            logger.debug(this.context.getMessage(\"Setting auto-commit mode to [{}]\"), autoCommit);\n\n            if (this.autoCommit != autoCommit) {\n\n                logger.debug(this.context.getMessage(\"Committing pending transactions\"));\n                builder.append(\"IF @@TRANCOUNT > 0 COMMIT TRAN;\");\n            }\n\n            builder.append(autoCommit ? \"SET IMPLICIT_TRANSACTIONS OFF;\" : \"SET IMPLICIT_TRANSACTIONS ON;\");\n\n            return exchange(builder.toString()).doOnSuccess(ignore -> this.autoCommit = autoCommit);\n        });\n    }\n\n    /**\n     * Configure the lock wait timeout via {@code SET LOCK_TIMEOUT}. {@link Duration#isNegative() Negative values} are translated to {@code -1} meaning infinite wait.\n     *\n     * @param timeout the timeout to apply.\n     * @return a {@link Mono} that indicates that the lock wait timeout has been applied\n     * @since 0.9\n     */\n    @Override\n    public Mono<Void> setLockWaitTimeout(Duration timeout) {\n\n        Assert.requireNonNull(timeout, \"Timeout must not be null\");\n\n        return exchange(\"SET LOCK_TIMEOUT \" + (timeout.isNegative() ? \"-1\" : \"\" + timeout.toMillis()));\n    }\n\n    /**\n     * Configure the statement wait timeout. Statements exceeding the timeout are being cancelled.\n     *\n     * @param timeout the timeout to apply.\n     * @return a {@link Mono} that indicates that the statement timeout has been applied\n     * @since 0.9\n     */\n    @Override\n    public Mono<Void> setStatementTimeout(Duration timeout) {\n\n        Assert.requireNonNull(timeout, \"Timeout must not be null\");\n\n        return Mono.fromRunnable(() -> this.connectionOptions.setStatementTimeout(timeout));\n    }\n\n    @Override\n    public MssqlConnectionMetadata getMetadata() {\n        return this.metadata;\n    }\n\n    public IsolationLevel getTransactionIsolationLevel() {\n        return this.isolationLevel;\n    }\n\n    @Override\n    public Mono<Void> setTransactionIsolationLevel(IsolationLevel isolationLevel) {\n        Assert.requireNonNull(isolationLevel, \"IsolationLevel must not be null\");\n\n        return exchange(renderSetIsolationLevel(isolationLevel)).doOnSuccess(ignore -> this.isolationLevel = isolationLevel);\n    }\n\n    @Override\n    public Mono<Boolean> validate(ValidationDepth depth) {\n\n        if (depth == ValidationDepth.LOCAL) {\n            return Mono.fromSupplier(this.client::isConnected);\n        }\n\n        return Mono.create(sink -> {\n\n            if (!this.client.isConnected()) {\n                sink.success(false);\n                return;\n            }\n\n            this.validationQuery.subscribe(new CoreSubscriber<Long>() {\n\n                @Override\n                public void onSubscribe(Subscription s) {\n                    s.request(Integer.MAX_VALUE);\n                }\n\n                @Override\n                public void onNext(Long integer) {\n\n                }\n\n                @Override\n                public void onError(Throwable t) {\n\n                    logger.debug(\"Validation failed\", t);\n                    sink.success(false);\n                }\n\n                @Override\n                public void onComplete() {\n                    sink.success(true);\n                }\n            });\n        });\n    }\n\n    private static String renderSetIsolationLevel(IsolationLevel isolationLevel) {\n        return \"SET TRANSACTION ISOLATION LEVEL \" + isolationLevel.asSql();\n    }\n\n    static String sanitize(String identifier, int maxLength) {\n\n        return identifier\n                .replace('-', '_')\n                .replace('.', '_')\n                .substring(0, Math.min(identifier.length(), maxLength));\n    }\n\n    private Mono<Void> exchange(String sql) {\n\n        ExceptionFactory factory = ExceptionFactory.withSql(sql);\n        return QueryMessageFlow.exchange(this.client, sql)\n                .handle(factory::handleErrorResponse)\n                .then();\n    }\n\n    private Mono<Void> useTransactionStatus(Function<TransactionStatus, Publisher<?>> function) {\n        return Flux.defer(() -> function.apply(this.client.getTransactionStatus()))\n                .then();\n    }\n\n    enum EmptyTransactionDefinition implements TransactionDefinition {\n        INSTANCE;\n\n        @Override\n        public <T> T getAttribute(Option<T> option) {\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlConnectionConfiguration.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.handler.ssl.IdentityCipherSuiteFilter;\nimport io.netty.handler.ssl.OpenSsl;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslContextBuilder;\nimport io.r2dbc.mssql.client.ClientConfiguration;\nimport io.r2dbc.mssql.client.ssl.ExpectedHostnameX509TrustManager;\nimport io.r2dbc.mssql.client.ssl.SslConfiguration;\nimport io.r2dbc.mssql.client.ssl.TrustAllTrustManager;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.message.tds.Redirect;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.StringUtils;\nimport reactor.netty.resources.ConnectionProvider;\nimport reactor.util.annotation.Nullable;\n\nimport javax.net.ssl.SSLException;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.Socket;\nimport java.net.UnknownHostException;\nimport java.security.GeneralSecurityException;\nimport java.security.KeyStore;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Locale;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * Connection configuration information for connecting to a Microsoft SQL database.\n * Allows configuration of the connection endpoint, login credentials, database and trace details such as application name and connection Id.\n *\n * @author Mark Paluch\n * @author Alex Stockinger\n * @author Paul Johe\n */\npublic final class MssqlConnectionConfiguration {\n\n    /**\n     * Default SQL Server port.\n     */\n    public static final int DEFAULT_PORT = 1433;\n\n    /**\n     * Default connect timeout.\n     */\n    public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(30);\n\n    @Nullable\n    private final String applicationName;\n\n    private final ConnectionProvider connectionProvider;\n\n    @Nullable\n    private final UUID connectionId;\n\n    private final Duration connectTimeout;\n\n    private final String database;\n\n    private final String host;\n\n    private final String hostNameInCertificate;\n\n    private final CharSequence password;\n\n    private final Predicate<String> preferCursoredExecution;\n\n    @Nullable\n    private final Duration lockWaitTimeout;\n\n    private final int port;\n\n    private final boolean sendStringParametersAsUnicode;\n\n    private final boolean ssl;\n\n    private final Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer;\n\n    @Nullable\n    private final Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer;\n\n    private final boolean tcpKeepAlive;\n\n    private final boolean tcpNoDelay;\n\n    private final boolean trustServerCertificate;\n\n    @Nullable\n    private final File trustStore;\n\n    @Nullable\n    private final String trustStoreType;\n\n    @Nullable\n    private final char[] trustStorePassword;\n\n    private final String username;\n\n    private MssqlConnectionConfiguration(@Nullable String applicationName, @Nullable UUID connectionId, ConnectionProvider connectionProvider, Duration connectTimeout, @Nullable String database, String host, String hostNameInCertificate,\n                                         @Nullable Duration lockWaitTimeout, CharSequence password, Predicate<String> preferCursoredExecution, int port, boolean sendStringParametersAsUnicode,\n                                         boolean ssl,\n                                         Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer,\n                                         @Nullable Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer, boolean tcpKeepAlive, boolean tcpNoDelay,\n                                         boolean trustServerCertificate, @Nullable File trustStore, @Nullable String trustStoreType,\n                                         @Nullable char[] trustStorePassword, String username) {\n\n        this.applicationName = applicationName;\n        this.connectionId = connectionId;\n        this.connectionProvider = connectionProvider;\n        this.connectTimeout = Assert.requireNonNull(connectTimeout, \"connect timeout must not be null\");\n        this.database = database;\n        this.host = Assert.requireNonNull(host, \"host must not be null\");\n        this.hostNameInCertificate = Assert.requireNonNull(hostNameInCertificate, \"hostNameInCertificate must not be null\");\n        this.lockWaitTimeout = lockWaitTimeout;\n        this.password = Assert.requireNonNull(password, \"password must not be null\");\n        this.preferCursoredExecution = Assert.requireNonNull(preferCursoredExecution, \"preferCursoredExecution must not be null\");\n        this.port = port;\n        this.sendStringParametersAsUnicode = sendStringParametersAsUnicode;\n        this.ssl = ssl;\n        this.sslContextBuilderCustomizer = sslContextBuilderCustomizer;\n        this.sslTunnelSslContextBuilderCustomizer = sslTunnelSslContextBuilderCustomizer;\n        this.tcpKeepAlive = tcpKeepAlive;\n        this.tcpNoDelay = tcpNoDelay;\n        this.trustServerCertificate = trustServerCertificate;\n        this.trustStore = trustStore;\n        this.trustStoreType = trustStoreType;\n        this.trustStorePassword = trustStorePassword;\n        this.username = Assert.requireNonNull(username, \"username must not be null\");\n    }\n\n    /**\n     * Returns a new {@link Builder}.\n     *\n     * @return a new {@link Builder}\n     */\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    /**\n     * Create a new configuration instance targeting the redirect.\n     *\n     * @param redirect the redirect\n     * @return a new configuration instance\n     * @since 0.8.2\n     */\n    MssqlConnectionConfiguration withRedirect(Redirect redirect) {\n\n        String redirectServerName = redirect.getServerName();\n        String hostNameInCertificate = this.hostNameInCertificate;\n\n        // Same behavior as mssql-jdbc\n        if (this.hostNameInCertificate.startsWith(\"*\") && redirectServerName.indexOf('.') != -1) {\n\n            // Check if redirectServerName and hostNameInCertificate are from same domain.\n            boolean trustedDomain = redirectServerName.endsWith(hostNameInCertificate.substring(1));\n\n            if (trustedDomain) {\n                hostNameInCertificate = String.format(\"*%s\", redirectServerName.substring(redirectServerName.indexOf('.')));\n            }\n        }\n\n        return new MssqlConnectionConfiguration(this.applicationName, this.connectionId, this.connectionProvider, this.connectTimeout, this.database, redirectServerName, hostNameInCertificate, this.lockWaitTimeout,\n                this.password,\n                this.preferCursoredExecution, redirect.getPort(), this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer,\n                this.sslTunnelSslContextBuilderCustomizer, this.tcpKeepAlive, this.tcpNoDelay, this.trustServerCertificate, this.trustStore, this.trustStoreType, this.trustStorePassword, this.username\n        );\n    }\n\n    ClientConfiguration toClientConfiguration() {\n        return new DefaultClientConfiguration(this.connectionProvider, this.connectTimeout, this.host, this.hostNameInCertificate, this.port, this.ssl, this.sslContextBuilderCustomizer,\n                this.sslTunnelSslContextBuilderCustomizer, this.tcpKeepAlive, this.tcpNoDelay, this.trustServerCertificate, this.trustStore, this.trustStoreType, this.trustStorePassword\n        );\n    }\n\n    ConnectionOptions toConnectionOptions(Codecs codecs) {\n        return new ConnectionOptions(this.preferCursoredExecution, codecs, new IndefinitePreparedStatementCache(), this.sendStringParametersAsUnicode);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [applicationName=\\\"\").append(this.applicationName).append('\\\"');\n        sb.append(\", connectionId=\").append(this.connectionId);\n        sb.append(\", connectTimeout=\\\"\").append(this.connectTimeout).append('\\\"');\n        sb.append(\", database=\\\"\").append(this.database).append('\\\"');\n        sb.append(\", host=\\\"\").append(this.host).append('\\\"');\n        sb.append(\", hostNameInCertificate=\\\"\").append(this.hostNameInCertificate).append('\\\"');\n        sb.append(\", lockWaitTimeout=\\\"\").append(this.lockWaitTimeout).append('\\\"');\n        sb.append(\", password=\\\"\").append(repeat(this.password.length(), \"*\")).append('\\\"');\n        sb.append(\", preferCursoredExecution=\\\"\").append(this.preferCursoredExecution).append('\\\"');\n        sb.append(\", port=\").append(this.port);\n        sb.append(\", sendStringParametersAsUnicode=\").append(this.sendStringParametersAsUnicode);\n        sb.append(\", ssl=\").append(this.ssl);\n        sb.append(\", sslContextBuilderCustomizer=\").append(this.sslContextBuilderCustomizer);\n        sb.append(\", sslTunnelSslContextBuilderCustomizer=\").append(this.sslTunnelSslContextBuilderCustomizer);\n        sb.append(\", tcpKeepAlive=\\\"\").append(this.tcpKeepAlive).append(\"\\\"\");\n        sb.append(\", tcpNoDelay=\\\"\").append(this.tcpNoDelay).append(\"\\\"\");\n        sb.append(\", trustServerCertificate=\").append(this.trustServerCertificate);\n        sb.append(\", trustStore=\\\"\").append(this.trustStore).append(\"\\\"\");\n        sb.append(\", trustStorePassword=\\\"\").append(repeat(this.trustStorePassword == null ? 0 : this.trustStorePassword.length, \"*\")).append('\\\"');\n        sb.append(\", trustStoreType=\\\"\").append(this.trustStoreType).append(\"\\\"\");\n        sb.append(\", username=\\\"\").append(this.username).append('\\\"');\n        sb.append(']');\n        return sb.toString();\n    }\n\n    @Nullable\n    String getApplicationName() {\n        return this.applicationName;\n    }\n\n    @Nullable\n    UUID getConnectionId() {\n        return this.connectionId;\n    }\n\n    Duration getConnectTimeout() {\n        return this.connectTimeout;\n    }\n\n    Optional<String> getDatabase() {\n        return Optional.ofNullable(this.database);\n    }\n\n    String getHost() {\n        return this.host;\n    }\n\n    String getHostNameInCertificate() {\n        return this.hostNameInCertificate;\n    }\n\n    @Nullable\n    Duration getLockWaitTimeout() {\n        return this.lockWaitTimeout;\n    }\n\n    CharSequence getPassword() {\n        return this.password;\n    }\n\n    Predicate<String> getPreferCursoredExecution() {\n        return this.preferCursoredExecution;\n    }\n\n    int getPort() {\n        return this.port;\n    }\n\n    boolean isSendStringParametersAsUnicode() {\n        return this.sendStringParametersAsUnicode;\n    }\n\n    boolean useSsl() {\n        return this.ssl;\n    }\n\n    boolean isTcpKeepAlive() {\n        return this.tcpKeepAlive;\n    }\n\n    boolean isTcpNoDelay() {\n        return this.tcpNoDelay;\n    }\n\n    String getUsername() {\n        return this.username;\n    }\n\n    LoginConfiguration getLoginConfiguration() {\n        return new LoginConfiguration(getApplicationName(), this.connectionId, getDatabase().orElse(\"\"), lookupHostName(), getPassword(), getHost(), useSsl(), getUsername()\n        );\n    }\n\n    private static String repeat(int length, String character) {\n\n        StringBuilder builder = new StringBuilder();\n\n        for (int i = 0; i < length; i++) {\n            builder.append(character);\n        }\n\n        return builder.toString();\n    }\n\n    /**\n     * Looks up local hostname of client machine.\n     *\n     * @return hostname string or IP of host if hostname cannot be resolved. If neither hostname or IP found returns an empty string.\n     */\n    private static String lookupHostName() {\n\n        try {\n            InetAddress localAddress = InetAddress.getLocalHost();\n            if (localAddress != null) {\n                String value = localAddress.getHostName();\n                if (StringUtils.hasText(value)) {\n                    return value;\n                }\n\n                value = localAddress.getHostAddress();\n                if (StringUtils.hasText(value)) {\n                    return value;\n                }\n            }\n        } catch (UnknownHostException e) {\n            return \"\";\n        }\n        // If hostname not found, return standard \"\" string.\n        return \"\";\n    }\n\n    /**\n     * A builder for {@link MssqlConnectionConfiguration} instances.\n     * <p>\n     * <i>This class is not threadsafe</i>\n     */\n    public static final class Builder {\n\n        @Nullable\n        private String applicationName;\n\n        private ConnectionProvider connectionProvider = ConnectionProvider.newConnection();\n\n        private UUID connectionId = UUID.randomUUID();\n\n        private Duration connectTimeout = DEFAULT_CONNECT_TIMEOUT;\n\n        private String database;\n\n        private String host;\n\n        private String hostNameInCertificate;\n\n        @Nullable\n        private Duration lockWaitTimeout;\n\n        private Predicate<String> preferCursoredExecution = DefaultCursorPreference.INSTANCE;\n\n        private CharSequence password;\n\n        private int port = DEFAULT_PORT;\n\n        private boolean sendStringParametersAsUnicode = true;\n\n        private boolean ssl;\n\n        private boolean trustServerCertificate;\n\n        private Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer = Function.identity();\n\n        @Nullable\n        private Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer;\n\n        private String username;\n\n        private boolean tcpKeepAlive = false;\n\n        private boolean tcpNoDelay = true;\n\n        @Nullable\n        private File trustStore;\n\n        @Nullable\n        private String trustStoreType;\n\n        @Nullable\n        private char[] trustStorePassword;\n\n        private Builder() {\n        }\n\n        /**\n         * Configure the applicationName.\n         *\n         * @param applicationName the applicationName\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code applicationName} is {@code null}\n         */\n        public Builder applicationName(String applicationName) {\n            this.applicationName = Assert.requireNonNull(applicationName, \"applicationName must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the connectionId.\n         *\n         * @param connectionId the application name\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException when {@link UUID} is {@code null}.\n         */\n        public Builder connectionId(UUID connectionId) {\n            this.connectionId = Assert.requireNonNull(connectionId, \"connectionId must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the {@link ConnectionProvider} to be used with Reactor Netty.\n         * Defaults to {@link ConnectionProvider#newConnection()}.\n         *\n         * @param connectionProvider the connection provider\n         * @return this {@link Builder}\n         * @since 1.0.3\n         */\n        public Builder connectionProvider(ConnectionProvider connectionProvider) {\n            this.connectionProvider = Assert.requireNonNull(connectionProvider, \"connectionProvider must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the connect timeout. Defaults to 30 seconds.\n         *\n         * @param connectTimeout the connect timeout\n         * @return this {@link Builder}\n         */\n        public Builder connectTimeout(Duration connectTimeout) {\n\n            Assert.requireNonNull(connectTimeout, \"connect timeout must not be null\");\n            Assert.isTrue(!connectTimeout.isNegative(), \"connect timeout must not be negative\");\n\n            this.connectTimeout = connectTimeout;\n            return this;\n        }\n\n        /**\n         * Configure the database.\n         *\n         * @param database the database\n         * @return this {@link Builder}\n         */\n        public Builder database(@Nullable String database) {\n            this.database = database;\n            return this;\n        }\n\n        /**\n         * Enable SSL usage. This flag is also known as Use Encryption in other drivers.\n         *\n         * @return this {@link Builder}\n         */\n        public Builder enableSsl() {\n            this.ssl = true;\n            return this;\n        }\n\n        /**\n         * Enable SSL tunnel usage to encrypt all traffic right from the connect phase. This option is required when using a SSL tunnel (e.g. stunnel or other SSL terminator) in front of the SQL\n         * server and it is not related to SQL Server's built-in SSL support.\n         *\n         * @return this {@link Builder}\n         * @since 0.8.5\n         */\n        public Builder enableSslTunnel() {\n            return enableSslTunnel(Function.identity());\n        }\n\n        /**\n         * Enable SSL tunnel usage to encrypt all traffic right from the connect phase. This option is required when using a SSL tunnel (e.g. stunnel or other SSL terminator) in front of the SQL\n         * server and it is not related to SQL Server's built-in SSL support.\n         * The given customizer gets applied on each SSL connection attempt to allow for just-in-time configuration updates. The {@link Function} gets\n         * * called with the prepared {@link SslContextBuilder} that has all configuration options applied. The customizer may return the same builder or return a new builder instance to be used to\n         * * build the SSL context.\n         *\n         * @param sslTunnelSslContextBuilderCustomizer customizer function\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code sslTunnelSslContextBuilderCustomizer} is {@code null}\n         * @since 0.8.5\n         */\n        public Builder enableSslTunnel(Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer) {\n            this.sslTunnelSslContextBuilderCustomizer = Assert.requireNonNull(sslTunnelSslContextBuilderCustomizer, \"sslTunnelSslContextBuilderCustomizer must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the host.\n         *\n         * @param host the host\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code host} is {@code null}\n         */\n        public Builder host(String host) {\n            this.host = Assert.requireNonNull(host, \"host must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the expected hostname in the SSL certificate. Defaults to {@link #host(String)} if left unconfigured. Accepts wildcards such as {@code *.database.windows.net}.\n         *\n         * @param hostNameInCertificate the hostNameInCertificate\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code hostNameInCertificate} is {@code null}\n         */\n        public Builder hostNameInCertificate(String hostNameInCertificate) {\n            this.hostNameInCertificate = Assert.requireNonNull(hostNameInCertificate, \"hostNameInCertificate must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the lock wait timeout via {@code SET LOCK_TIMEOUT}. {@link Duration#isNegative() Negative values} are translated to {@code -1} meaning infinite wait.\n         *\n         * @param timeout the lock wait timeout\n         * @return this {@link Builder}\n         * @since 0.9\n         */\n        public Builder lockWaitTimeout(Duration timeout) {\n\n            Assert.requireNonNull(timeout, \"lock wait timeout must not be null\");\n\n            this.lockWaitTimeout = timeout;\n            return this;\n        }\n\n        /**\n         * Configure the password.\n         *\n         * @param password the password\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code password} is {@code null}\n         */\n        public Builder password(CharSequence password) {\n            this.password = Assert.requireNonNull(password, \"password must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure whether to prefer cursored execution.\n         *\n         * @param preferCursoredExecution {@code true} prefers cursors, {@code false} prefers direct execution. Defaults to direct execution.\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code password} is {@code null}\n         */\n        public Builder preferCursoredExecution(boolean preferCursoredExecution) {\n            return preferCursoredExecution(sql -> preferCursoredExecution);\n        }\n\n        /**\n         * Configure whether to prefer cursored execution on a statement-by-statement basis. The {@link Predicate} accepts the SQL query string and returns a boolean flag indicating preference.\n         * {@code true} prefers cursors, {@code false} prefers direct execution. Defaults to direct execution.\n         *\n         * @param preference the {@link Predicate}.\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code password} is {@code null}\n         */\n        public Builder preferCursoredExecution(Predicate<String> preference) {\n            this.preferCursoredExecution = Assert.requireNonNull(preference, \"Predicate must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the port. Defaults to {@code 5432}.\n         *\n         * @param port the port\n         * @return this {@link Builder}\n         */\n        public Builder port(int port) {\n            this.port = port;\n            return this;\n        }\n\n        /**\n         * Configure whether to send character data as unicode (NVARCHAR, NCHAR, NTEXT) or whether to use the database encoding. Enabled by default. If disabled, {@link CharSequence} data is sent\n         * using the database-specific collation such as ASCII/MBCS instead of Unicode.\n         *\n         * @param sendStringParametersAsUnicode {@literal true} to send character data as unicode (NVARCHAR, NCHAR, NTEXT) or whether to use the database encoding. Enabled by default.\n         * @return this {@link Builder}\n         */\n        public Builder sendStringParametersAsUnicode(boolean sendStringParametersAsUnicode) {\n            this.sendStringParametersAsUnicode = sendStringParametersAsUnicode;\n            return this;\n        }\n\n        /**\n         * Configure a {@link SslContextBuilder} customizer. The customizer gets applied on each SSL connection attempt to allow for just-in-time configuration updates. The {@link Function} gets\n         * called with the prepared {@link SslContextBuilder} that has all configuration options applied. The customizer may return the same builder or return a new builder instance to be used to\n         * build the SSL context.\n         *\n         * @param sslContextBuilderCustomizer customizer function\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code sslContextBuilderCustomizer} is {@code null}\n         * @since 0.8.3\n         */\n        public Builder sslContextBuilderCustomizer(Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer) {\n            this.sslContextBuilderCustomizer = Assert.requireNonNull(sslContextBuilderCustomizer, \"sslContextBuilderCustomizer must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure TCP KeepAlive. Disabled by default.\n         *\n         * @param enabled whether to enable/disable TCP KeepAlive\n         * @return this {@link Builder}\n         * @see Socket#setKeepAlive(boolean)\n         * @since 0.8.5\n         */\n        public Builder tcpKeepAlive(boolean enabled) {\n            this.tcpKeepAlive = enabled;\n            return this;\n        }\n\n        /**\n         * Configure TCP NoDelay. Enabled by default.\n         *\n         * @param enabled whether to enable/disable TCP NoDelay\n         * @return this {@link Builder}\n         * @see Socket#setTcpNoDelay(boolean)\n         * @since 0.8.5\n         */\n        public Builder tcpNoDelay(boolean enabled) {\n            this.tcpNoDelay = enabled;\n            return this;\n        }\n\n        /**\n         * Allow using SSL by fully trusting the server certificate. Enabling this option skips certificate verification.\n         *\n         * @return this {@link Builder}.\n         * @see TrustAllTrustManager\n         * @since 0.8.6\n         */\n        public Builder trustServerCertificate() {\n            return trustServerCertificate(true);\n        }\n\n        /**\n         * Allow using SSL by fully trusting the server certificate. Enabling this option skips certificate verification.\n         *\n         * @param trustServerCertificate {@code true} to trust the server certificate without further validation.\n         * @return this {@link Builder}.\n         * @see TrustAllTrustManager\n         * @since 0.8.6\n         */\n        public Builder trustServerCertificate(boolean trustServerCertificate) {\n            this.trustServerCertificate = trustServerCertificate;\n            return this;\n        }\n\n        /**\n         * Configure the trust store type.\n         *\n         * @param trustStoreType the type of the trust store to be used for SSL certificate verification. Defaults to {@link KeyStore#getDefaultType()} if not set.\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code trustStoreType} is {@code null}\n         * @since 0.8.3\n         */\n        public Builder trustStoreType(String trustStoreType) {\n            this.trustStoreType = Assert.requireNonNull(trustStoreType, \"trustStoreType must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the file path to the trust store.\n         *\n         * @param trustStoreFile the path of the trust store to be used for SSL certificate verification.\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code trustStore} is {@code null}\n         * @since 0.8.3\n         */\n        public Builder trustStore(String trustStoreFile) {\n            return trustStore(new File(Assert.requireNonNull(trustStoreFile, \"trustStore must not be null\")));\n        }\n\n        /**\n         * Configure the path to the trust store.\n         *\n         * @param trustStore the path of the trust store to be used for SSL certificate verification.\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code trustStore} is {@code null}\n         * @since 0.8.3\n         */\n        public Builder trustStore(File trustStore) {\n            this.trustStore = Assert.requireNonNull(trustStore, \"trustStore must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the password to read the trust store.\n         *\n         * @param trustStorePassword the password to read the trust store.\n         * @return this {@link Builder}\n         * @since 0.8.3\n         */\n        public Builder trustStorePassword(char[] trustStorePassword) {\n            this.trustStorePassword = Assert.requireNonNull(Arrays.copyOf(trustStorePassword, trustStorePassword.length), \"trustStorePassword must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the username.\n         *\n         * @param username the username\n         * @return this {@link Builder}\n         * @throws IllegalArgumentException if {@code username} is {@code null}\n         */\n        public Builder username(String username) {\n            this.username = Assert.requireNonNull(username, \"username must not be null\");\n            return this;\n        }\n\n        /**\n         * Returns a configured {@link MssqlConnectionConfiguration}.\n         *\n         * @return a configured {@link MssqlConnectionConfiguration}.\n         */\n        public MssqlConnectionConfiguration build() {\n\n            if (this.hostNameInCertificate == null) {\n                this.hostNameInCertificate = this.host;\n            }\n\n            return new MssqlConnectionConfiguration(this.applicationName, this.connectionId, this.connectionProvider, this.connectTimeout, this.database, this.host, this.hostNameInCertificate, this.lockWaitTimeout,\n                    this.password,\n                    this.preferCursoredExecution, this.port, this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer,\n                    this.sslTunnelSslContextBuilderCustomizer, this.tcpKeepAlive,\n                    this.tcpNoDelay, this.trustServerCertificate, this.trustStore,\n                    this.trustStoreType, this.trustStorePassword, this.username);\n        }\n\n    }\n\n    static class DefaultClientConfiguration implements ClientConfiguration {\n\n        private final ConnectionProvider connectionProvider;\n\n        private final Duration connectTimeout;\n\n        private final String host;\n\n        private final String hostNameInCertificate;\n\n        private final int port;\n\n        private final boolean ssl;\n\n        private final Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer;\n\n        @Nullable\n        private final Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer;\n\n        private final boolean tcpKeepAlive;\n\n        private final boolean tcpNoDelay;\n\n        private final boolean trustServerCertificate;\n\n        @Nullable\n        private final File trustStore;\n\n        @Nullable\n        private final String trustStoreType;\n\n        @Nullable\n        private final char[] trustStorePassword;\n\n        DefaultClientConfiguration(ConnectionProvider connectionProvider, Duration connectTimeout, String host, String hostNameInCertificate, int port, boolean ssl,\n                                   Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer,\n                                   @Nullable Function<SslContextBuilder, SslContextBuilder> sslTunnelSslContextBuilderCustomizer, boolean tcpKeepAlive, boolean tcpNoDelay,\n                                   boolean trustServerCertificate, @Nullable File trustStore, @Nullable String trustStoreType, @Nullable char[] trustStorePassword) {\n\n            this.connectTimeout = connectTimeout;\n            this.host = host;\n            this.hostNameInCertificate = hostNameInCertificate;\n            this.port = port;\n            this.ssl = ssl;\n            this.sslContextBuilderCustomizer = sslContextBuilderCustomizer;\n            this.sslTunnelSslContextBuilderCustomizer = sslTunnelSslContextBuilderCustomizer;\n            this.tcpKeepAlive = tcpKeepAlive;\n            this.tcpNoDelay = tcpNoDelay;\n            this.trustServerCertificate = trustServerCertificate;\n            this.trustStore = trustStore;\n            this.trustStoreType = trustStoreType;\n            this.trustStorePassword = trustStorePassword;\n            this.connectionProvider = connectionProvider;\n        }\n\n        @Override\n        public String getHost() {\n            return this.host;\n        }\n\n        @Override\n        public int getPort() {\n            return this.port;\n        }\n\n        @Override\n        public Duration getConnectTimeout() {\n            return this.connectTimeout;\n        }\n\n        @Override\n        public boolean isTcpKeepAlive() {\n            return this.tcpKeepAlive;\n        }\n\n        @Override\n        public boolean isTcpNoDelay() {\n            return this.tcpNoDelay;\n        }\n\n        @Override\n        public ConnectionProvider getConnectionProvider() {\n            return this.connectionProvider;\n        }\n\n        @Override\n        public boolean isSslEnabled() {\n            return this.ssl;\n        }\n\n        @Override\n        public SslContext getSslContext() throws GeneralSecurityException {\n\n            SslContextBuilder sslContextBuilder = createSslContextBuilder();\n\n            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n            KeyStore ks = loadCustomTrustStore();\n            tmf.init(ks);\n\n            TrustManager[] trustManagers = tmf.getTrustManagers();\n            TrustManager result;\n\n            if (isSslEnabled() && !this.trustServerCertificate) {\n                result = new ExpectedHostnameX509TrustManager((X509TrustManager) trustManagers[0], this.hostNameInCertificate);\n            } else {\n                result = TrustAllTrustManager.INSTANCE;\n            }\n\n            sslContextBuilder.trustManager(result);\n\n            try {\n                return this.sslContextBuilderCustomizer.apply(sslContextBuilder).build();\n            } catch (SSLException e) {\n                throw new GeneralSecurityException(e);\n            }\n        }\n\n        @Nullable\n        KeyStore loadCustomTrustStore() throws GeneralSecurityException {\n\n            if (this.trustStore == null) {\n                return null;\n            }\n\n            KeyStore trustStoreInstance = KeyStore.getInstance(this.trustStoreType == null ? KeyStore.getDefaultType() : this.trustStoreType);\n\n            try (FileInputStream fis = new FileInputStream(this.trustStore)) {\n                trustStoreInstance.load(fis, this.trustStorePassword);\n                return trustStoreInstance;\n            } catch (IOException e) {\n                throw new GeneralSecurityException(String.format(\"Could not load custom trust store from %s\", this.trustStore), e);\n            }\n        }\n\n        @Override\n        public SslConfiguration getSslTunnelConfiguration() {\n\n            if (this.sslTunnelSslContextBuilderCustomizer == null) {\n                return ClientConfiguration.super.getSslTunnelConfiguration();\n            }\n\n            return new SslConfiguration() {\n\n                @Override\n                public boolean isSslEnabled() {\n                    return true;\n                }\n\n                @Override\n                public SslContext getSslContext() throws GeneralSecurityException {\n\n                    SslContextBuilder sslContextBuilder = createSslContextBuilder();\n\n                    try {\n                        return DefaultClientConfiguration.this.sslTunnelSslContextBuilderCustomizer.apply(sslContextBuilder).build();\n                    } catch (SSLException e) {\n                        throw new GeneralSecurityException(e);\n                    }\n                }\n            };\n        }\n\n    }\n\n    private static SslContextBuilder createSslContextBuilder() {\n        SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();\n        sslContextBuilder.sslProvider(\n                        OpenSsl.isAvailable() ?\n                                io.netty.handler.ssl.SslProvider.OPENSSL :\n                                io.netty.handler.ssl.SslProvider.JDK)\n                .ciphers(null, IdentityCipherSuiteFilter.INSTANCE)\n                .applicationProtocolConfig(null);\n        return sslContextBuilder;\n    }\n\n\n    static class DefaultCursorPreference implements Predicate<String> {\n\n        static final DefaultCursorPreference INSTANCE = new DefaultCursorPreference();\n\n        @Override\n        public boolean test(String sql) {\n\n            if (sql.isEmpty()) {\n                return false;\n            }\n\n            String lc = sql.trim().toLowerCase(Locale.ENGLISH);\n            if (lc.contains(\"for xml\") || lc.contains(\"for json\")) {\n                return false;\n            }\n\n            char c = sql.charAt(0);\n\n            return (c == 's' || c == 'S') && lc.startsWith(\"select\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlConnectionFactory.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.client.ClientConfiguration;\nimport io.r2dbc.mssql.client.ReactorNettyClient;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.message.tds.Redirect;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.ConnectionFactory;\nimport io.r2dbc.spi.R2dbcNonTransientResourceException;\nimport io.r2dbc.spi.Row;\nimport reactor.core.publisher.Mono;\n\nimport java.util.function.Function;\n\n/**\n * An implementation of {@link ConnectionFactory} for creating connections to a Microsoft SQL Server database.\n *\n * @author Mark Paluch\n * @author Lars Haatveit\n */\npublic final class MssqlConnectionFactory implements ConnectionFactory {\n\n    private final String METADATA_QUERY = \" SELECT \" +\n        \"CAST(SERVERPROPERTY('Edition') AS VARCHAR(255)) AS Edition, \" +\n        \"CAST(@@VERSION AS VARCHAR(255)) as VersionString\";\n\n    private final Function<MssqlConnectionConfiguration, Mono<Client>> clientFactory;\n\n    private final MssqlConnectionConfiguration configuration;\n\n    private final DefaultCodecs codecs = new DefaultCodecs();\n\n    /**\n     * Creates a new connection factory.\n     *\n     * @param configuration the configuration to use connections\n     * @throws IllegalArgumentException when {@link MssqlConnectionConfiguration} is {@code null}.\n     */\n    public MssqlConnectionFactory(MssqlConnectionConfiguration configuration) {\n        this(MssqlConnectionFactory::connect, configuration);\n    }\n\n    MssqlConnectionFactory(Function<MssqlConnectionConfiguration, Mono<Client>> clientFactory,\n                           MssqlConnectionConfiguration configuration) {\n\n        this.clientFactory = Assert.requireNonNull(clientFactory, \"clientFactory must not be null\");\n        this.configuration = Assert.requireNonNull(configuration, \"configuration must not be null\");\n    }\n\n    private static Mono<Client> connect(MssqlConnectionConfiguration configuration) {\n\n        return Mono.defer(() -> {\n            Assert.requireNonNull(configuration, \"configuration must not be null\");\n\n            return ReactorNettyClient.connect(configuration.toClientConfiguration(), configuration.getApplicationName(),\n                configuration.getConnectionId());\n        });\n    }\n\n    private Mono<Client> initializeClient(MssqlConnectionConfiguration configuration, boolean allowReroute) {\n\n        LoginConfiguration loginConfiguration = configuration.getLoginConfiguration();\n\n        return this.clientFactory.apply(configuration)\n            .delayUntil(client -> LoginFlow.exchange(client, loginConfiguration)\n                .onErrorResume(e -> propagateError(client.close(), e)))\n            .flatMap(client -> {\n                return client.getRedirect().map(redirect -> {\n                    if (allowReroute) {\n                        return redirectClient(client, redirect);\n                    } else {\n                        return this.<Client>propagateError(client.close(), new MssqlRoutingException(\"Client was redirected more than once\"));\n                    }\n                }).orElse(Mono.just(client));\n            });\n    }\n\n    private Mono<Client> redirectClient(Client client, Redirect redirect) {\n\n        MssqlConnectionConfiguration routeConfiguration = this.configuration.withRedirect(redirect);\n\n        return client.close().then(this.initializeClient(routeConfiguration, false));\n    }\n\n    private <T> Mono<T> propagateError(Mono<?> action, Throwable e) {\n\n        return action.onErrorResume(suppressed -> {\n            e.addSuppressed(suppressed);\n            return Mono.error(e);\n        }).then(Mono.error(e));\n    }\n\n    @Override\n    public Mono<MssqlConnection> create() {\n\n        return initializeClient(this.configuration, true)\n            .flatMap(it -> {\n\n                ConnectionOptions connectionOptions = getConnectionOptions();\n                Mono<MssqlConnection> connectionMono =\n                    new SimpleMssqlStatement(it, connectionOptions, this.METADATA_QUERY).execute()\n                        .flatMap(result -> result.map((row, rowMetadata) -> toConnectionMetadata(it.getDatabaseVersion().orElse(\"unknown\"), row))).map(metadata -> {\n                        return new MssqlConnection(it, metadata, connectionOptions);\n                    }).last();\n\n                if (this.configuration.getLockWaitTimeout() != null) {\n                    connectionMono = connectionMono.flatMap(connection -> connection.setLockWaitTimeout(this.configuration.getLockWaitTimeout()).thenReturn(connection));\n                }\n\n                return connectionMono.onErrorResume(throwable -> {\n                    return it.close().then(Mono.error(new R2dbcNonTransientResourceException(\"Cannot connect to \" + this.configuration.getHost() + \":\" + this.configuration.getPort(), throwable)));\n                });\n            });\n    }\n\n    private static MssqlConnectionMetadata toConnectionMetadata(String version, Row row) {\n        return MssqlConnectionMetadata.from(row.get(\"Edition\", String.class), version, row.get(\"VersionString\", String.class));\n    }\n\n    MssqlConnectionConfiguration getConfiguration() {\n        return this.configuration;\n    }\n\n    ClientConfiguration getClientConfiguration() {\n        return this.configuration.toClientConfiguration();\n    }\n\n    ConnectionOptions getConnectionOptions() {\n        return this.configuration.toConnectionOptions(this.codecs);\n    }\n\n    @Override\n    public MssqlConnectionFactoryMetadata getMetadata() {\n        return MssqlConnectionFactoryMetadata.INSTANCE;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [configuration=\").append(this.configuration);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    static class MssqlRoutingException extends R2dbcNonTransientResourceException {\n\n        public MssqlRoutingException(String reason) {\n            super(reason);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlConnectionFactoryMetadata.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\n\n/**\n * An implementation of {@link ConnectionFactoryMetadata} for a Microsoft SQL Server database.\n *\n * @author Mark Paluch\n */\nenum MssqlConnectionFactoryMetadata implements ConnectionFactoryMetadata {\n\n    INSTANCE;\n\n    /**\n     * The name of the Microsoft SQL Server database product.\n     */\n    public static final String NAME = \"Microsoft SQL Server\";\n\n    MssqlConnectionFactoryMetadata() {\n    }\n\n    @Override\n    public String getName() {\n        return NAME;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlConnectionFactoryProvider.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.handler.ssl.SslContextBuilder;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport io.r2dbc.spi.ConnectionFactoryProvider;\nimport io.r2dbc.spi.Option;\nimport reactor.netty.resources.ConnectionProvider;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\n\nimport java.io.File;\nimport java.util.UUID;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\nimport static io.r2dbc.spi.ConnectionFactoryOptions.*;\n\n/**\n * An implementation of {@link ConnectionFactoryProvider} for creating {@link MssqlConnectionFactory}s.\n *\n * @author Mark Paluch\n * @author Paul Johe\n */\npublic final class MssqlConnectionFactoryProvider implements ConnectionFactoryProvider {\n\n    private final Logger logger = Loggers.getLogger(this.getClass());\n\n    /**\n     * Application name.\n     */\n    public static final Option<String> APPLICATION_NAME = Option.valueOf(\"applicationName\");\n\n    /**\n     * Connection Id\n     */\n    public static final Option<UUID> CONNECTION_ID = Option.valueOf(\"connectionId\");\n\n    /**\n     * Optional {@link reactor.netty.resources.ConnectionProvider} to control Netty configuration directly. Defaults to {@link ConnectionProvider#newConnection()}.\n     *\n     * @since 1.0.3\n     */\n    public static final Option<ConnectionProvider> CONNECTION_PROVIDER = Option.valueOf(\"connectionProvider\");\n\n    /**\n     * Expected Hostname in SSL certificate. Supports wildcards.\n     */\n    public static final Option<String> HOSTNAME_IN_CERTIFICATE = Option.valueOf(\"hostNameInCertificate\");\n\n    /**\n     * Configure whether to prefer cursored execution on a statement-by-statement basis. Value can be {@link Boolean}, a {@link Predicate}, or a {@link Class class name}. The {@link Predicate}\n     * accepts the SQL query string and returns a boolean flag indicating preference.\n     * {@code true} prefers cursors, {@code false} prefers direct execution.\n     */\n    public static final Option<Object> PREFER_CURSORED_EXECUTION = Option.valueOf(\"preferCursoredExecution\");\n\n    /**\n     * Configure whether to send character data as unicode (NVARCHAR, NCHAR, NTEXT) or whether to use the database encoding. Enabled by default.\n     * If disabled, {@link CharSequence} data is sent using the database-specific collation such as ASCII/MBCS instead of Unicode.\n     */\n    public static final Option<Boolean> SEND_STRING_PARAMETERS_AS_UNICODE = Option.valueOf(\"sendStringParametersAsUnicode\");\n\n    /**\n     * Customizer {@link Function} for {@link SslContextBuilder}.\n     *\n     * @since 0.8.3\n     */\n    public static final Option<Function<SslContextBuilder, SslContextBuilder>> SSL_CONTEXT_BUILDER_CUSTOMIZER = Option.valueOf(\"sslContextBuilderCustomizer\");\n\n    /**\n     * Enable SSL tunnel usage to encrypt all traffic right from the connect phase by providing a customizer {@link Function}. This option is required when using a SSL tunnel (e.g. stunnel or other\n     * SSL terminator) in front of the SQL server and it is not related to SQL Server's built-in SSL support.\n     *\n     * @since 0.8.5\n     */\n    public static final Option<Function<SslContextBuilder, SslContextBuilder>> SSL_TUNNEL = Option.valueOf(\"sslTunnel\");\n\n    /**\n     * Enable/Disable TCP KeepAlive.\n     *\n     * @since 0.8.5\n     */\n    public static final Option<Boolean> TCP_KEEPALIVE = Option.valueOf(\"tcpKeepAlive\");\n\n    /**\n     * Enable/Disable TCP NoDelay.\n     *\n     * @since 0.8.5\n     */\n    public static final Option<Boolean> TCP_NODELAY = Option.valueOf(\"tcpNoDelay\");\n\n    /**\n     * Allow using SSL by fully trusting the server certificate. Enabling this option skips certificate verification.\n     *\n     * @since 0.8.6\n     */\n    public static final Option<Boolean> TRUST_SERVER_CERTIFICATE = Option.valueOf(\"trustServerCertificate\");\n\n    /**\n     * Type of the TrustStore.\n     *\n     * @since 0.8.3\n     */\n    public static final Option<String> TRUST_STORE_TYPE = Option.valueOf(\"trustStoreType\");\n\n    /**\n     * Path to the certificate TrustStore file.\n     *\n     * @since 0.8.3\n     */\n    public static final Option<File> TRUST_STORE = Option.valueOf(\"trustStore\");\n\n    /**\n     * Password used to check the integrity of the TrustStore data.\n     *\n     * @since 0.8.3\n     */\n    public static final Option<char[]> TRUST_STORE_PASSWORD = Option.valueOf(\"trustStorePassword\");\n\n    /**\n     * Driver option value.\n     */\n    public static final String MSSQL_DRIVER = \"sqlserver\";\n\n    /**\n     * Driver option value.\n     */\n    public static final String ALTERNATE_MSSQL_DRIVER = \"mssql\";\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public MssqlConnectionFactory create(ConnectionFactoryOptions connectionFactoryOptions) {\n\n        Assert.requireNonNull(connectionFactoryOptions, \"connectionFactoryOptions must not be null\");\n\n        MssqlConnectionConfiguration.Builder builder = MssqlConnectionConfiguration.builder();\n\n        OptionMapper mapper = OptionMapper.create(connectionFactoryOptions);\n\n        mapper.fromTyped(APPLICATION_NAME).to(builder::applicationName);\n        mapper.from(CONNECTION_ID).map(OptionMapper::toUuid).to(builder::connectionId);\n        mapper.fromTyped(CONNECTION_PROVIDER).to(builder::connectionProvider);\n        mapper.from(CONNECT_TIMEOUT).map(OptionMapper::toDuration).to(builder::connectTimeout);\n        mapper.fromTyped(DATABASE).to(builder::database);\n        mapper.fromTyped(HOSTNAME_IN_CERTIFICATE).to(builder::hostNameInCertificate);\n        mapper.from(LOCK_WAIT_TIMEOUT).map(OptionMapper::toDuration).to(builder::lockWaitTimeout);\n        mapper.from(PORT).map(OptionMapper::toInteger).to(builder::port);\n        mapper.from(PREFER_CURSORED_EXECUTION).map(OptionMapper::toStringPredicate).to(builder::preferCursoredExecution);\n        mapper.from(SEND_STRING_PARAMETERS_AS_UNICODE).map(OptionMapper::toBoolean).to(builder::sendStringParametersAsUnicode);\n        mapper.from(SSL).map(OptionMapper::toBoolean).to(ssl -> {\n\n            if (ssl) {\n                builder.enableSsl();\n            }\n        });\n        mapper.fromTyped(SSL_CONTEXT_BUILDER_CUSTOMIZER).to(builder::sslContextBuilderCustomizer);\n        mapper.from(SSL_TUNNEL).map(it -> {\n\n            if (it instanceof Boolean) {\n                if ((Boolean) it) {\n                    return Function.identity();\n                }\n                return null;\n            }\n\n            return it;\n\n        }).to(it -> {\n\n            if (it != null) {\n                builder.enableSslTunnel((Function<SslContextBuilder, SslContextBuilder>) it);\n            }\n        });\n\n        mapper.from(TCP_KEEPALIVE).map(OptionMapper::toBoolean).to(builder::tcpKeepAlive);\n        mapper.from(TCP_NODELAY).map(OptionMapper::toBoolean).to(builder::tcpNoDelay);\n        mapper.from(TRUST_SERVER_CERTIFICATE).map(OptionMapper::toBoolean).to((Consumer<Boolean>) builder::trustServerCertificate);\n        mapper.from(TRUST_STORE).map(OptionMapper::toFile).to(builder::trustStore);\n        mapper.fromTyped(TRUST_STORE_TYPE).to(builder::trustStoreType);\n        mapper.from(TRUST_STORE_PASSWORD).map(it -> it instanceof String ? ((String) it).toCharArray() : (char[]) it).to(builder::trustStorePassword);\n\n        builder.host(connectionFactoryOptions.getRequiredValue(HOST).toString());\n        builder.password((CharSequence) connectionFactoryOptions.getRequiredValue(PASSWORD));\n        builder.username(connectionFactoryOptions.getRequiredValue(USER).toString());\n\n        MssqlConnectionConfiguration configuration = builder.build();\n        if (this.logger.isDebugEnabled()) {\n            this.logger.debug(String.format(\"Creating MssqlConnectionFactory with configuration [%s] from options [%s]\", configuration, connectionFactoryOptions));\n        }\n        return new MssqlConnectionFactory(configuration);\n    }\n\n    @Override\n    public boolean supports(ConnectionFactoryOptions connectionFactoryOptions) {\n\n        Assert.requireNonNull(connectionFactoryOptions, \"connectionFactoryOptions must not be null\");\n\n        Object driver = connectionFactoryOptions.getValue(DRIVER);\n        if (driver == null || !(driver.equals(MSSQL_DRIVER) || driver.equals(ALTERNATE_MSSQL_DRIVER))) {\n            return false;\n        }\n\n        if (!connectionFactoryOptions.hasOption(HOST)) {\n            return false;\n        }\n\n        if (!connectionFactoryOptions.hasOption(PASSWORD)) {\n            return false;\n        }\n\n        return connectionFactoryOptions.hasOption(USER);\n    }\n\n    @Override\n    public String getDriver() {\n        return MSSQL_DRIVER;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlConnectionMetadata.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.spi.ConnectionMetadata;\n\n/**\n * Connection metadata for a connection connected to Microsoft SQL Server database.\n *\n * @author Mark Paluch\n */\npublic final class MssqlConnectionMetadata implements ConnectionMetadata {\n\n    private final String databaseVersion;\n\n    private final String databaseProductName;\n\n    MssqlConnectionMetadata(String databaseProductName, String databaseVersion) {\n        this.databaseVersion = databaseVersion;\n        this.databaseProductName = databaseProductName;\n    }\n\n    /**\n     * Construct {@link MssqlConnectionMetadata} from a metadata query.\n     *\n     * @param edition         SQL Server edition.\n     * @param version         SQL Server version number.\n     * @param spVersionOutput output of {@code @@VERSION()} function.\n     * @return the {@link MssqlConnectionMetadata}.\n     */\n    public static MssqlConnectionMetadata from(String edition, String version, String spVersionOutput) {\n\n        String databaseProductName = spVersionOutput;\n\n        int separator = spVersionOutput.indexOf(\" - \");\n\n        if (separator > -1) {\n            databaseProductName = spVersionOutput.substring(0, separator) + \" - \" + edition;\n        }\n\n        return new MssqlConnectionMetadata(databaseProductName, version);\n    }\n\n    /**\n     * Retrieves the name of this database product.\n     *\n     * @return database product name\n     */\n    public String getDatabaseProductName() {\n        return this.databaseProductName;\n    }\n\n    /**\n     * Retrieves the version of this database product.\n     *\n     * @return database product name\n     */\n    public String getDatabaseVersion() {\n        return this.databaseVersion;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlException.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.spi.R2dbcException;\n\n/**\n * Interface for SQL Server-specific extension to {@link R2dbcException} providing {@link ErrorDetails}.\n *\n * @author Mark Paluch\n * @see ErrorDetails\n */\npublic interface MssqlException {\n\n    /**\n     * Returns additional error information.\n     *\n     * @return the {@link ErrorDetails}.\n     */\n    ErrorDetails getErrorDetails();\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlIsolationLevel.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.spi.IsolationLevel;\n\n/**\n * SQL Server-specific transaction isolation levels.\n * <p>\n * For more information check:\n * <a href=\"https://docs.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?view=sql-server-2017\">SQL Server Isolation Levels</a>\n *\n * @author Hebert Coelho\n * @author Mark Paluch\n * @see IsolationLevel\n */\npublic final class MssqlIsolationLevel {\n\n    private MssqlIsolationLevel() {\n\n    }\n\n    /**\n     * The read committed isolation level.\n     */\n    public static final IsolationLevel READ_COMMITTED = IsolationLevel.READ_COMMITTED;\n\n    /**\n     * The read uncommitted isolation level.\n     */\n    public static final IsolationLevel READ_UNCOMMITTED = IsolationLevel.READ_UNCOMMITTED;\n\n    /**\n     * The repeatable read isolation level.\n     */\n    public static final IsolationLevel REPEATABLE_READ = IsolationLevel.REPEATABLE_READ;\n\n    /**\n     * The serializable isolation level.\n     */\n    public static final IsolationLevel SERIALIZABLE = IsolationLevel.SERIALIZABLE;\n\n    /**\n     * The snapshot isolation level.\n     */\n    public static final IsolationLevel SNAPSHOT = IsolationLevel.valueOf(\"SNAPSHOT\");\n\n    /**\n     * Unspecified isolation level.\n     *\n     * @since 0.9\n     */\n    public static final IsolationLevel UNSPECIFIED = IsolationLevel.valueOf(\"UNSPECIFIED\");\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlResult.java",
    "content": "/*\n * Copyright 2021-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.spi.Result;\nimport io.r2dbc.spi.Row;\nimport io.r2dbc.spi.RowMetadata;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * A strongly typed implementation of {@link Result} for a Microsoft SQL Server database.\n *\n * @author Mark Paluch\n */\npublic interface MssqlResult extends Result {\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    Mono<Long> getRowsUpdated();\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    <T> Flux<T> map(BiFunction<Row, RowMetadata, ? extends T> mappingFunction);\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    MssqlResult filter(Predicate<Segment> filter);\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    <T> Flux<T> flatMap(Function<Segment, ? extends Publisher<? extends T>> mappingFunction);\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlReturnValues.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.util.ReferenceCounted;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.token.ReturnValue;\nimport io.r2dbc.mssql.message.token.RowToken;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.OutParameters;\nimport io.r2dbc.spi.Row;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.List;\n\n/**\n * Microsoft SQL Server-specific {@link Row} implementation synthesized from {@link ReturnValue return values}.\n * This object is no longer usable once it was {@link #release() released}.\n *\n * @author Mark Paluch\n * @see #release()\n * @see ReferenceCounted\n */\nfinal class MssqlReturnValues implements OutParameters, Message {\n\n    private static final int STATE_ACTIVE = 0;\n\n    private static final int STATE_RELEASED = 1;\n\n    private final Codecs codecs;\n\n    private final MssqlReturnValuesMetadata metadata;\n\n    private final List<ReturnValue> returnValues;\n\n    private volatile int state = STATE_ACTIVE;\n\n    private MssqlReturnValues(Codecs codecs, List<ReturnValue> returnValues, MssqlReturnValuesMetadata metadata) {\n\n        this.codecs = codecs;\n        this.metadata = metadata;\n        this.returnValues = returnValues;\n    }\n\n    /**\n     * Create a new {@link MssqlReturnValues}.\n     *\n     * @param codecs       the codecs to decode tabular data.\n     * @param returnValues the return values.\n     * @return\n     */\n    static MssqlReturnValues toReturnValues(Codecs codecs, List<ReturnValue> returnValues) {\n\n        Assert.requireNonNull(codecs, \"Codecs must not be null\");\n        Assert.requireNonNull(returnValues, \"ReturnValues must not be null\");\n\n        return new MssqlReturnValues(codecs, returnValues, new MssqlReturnValuesMetadata(codecs, returnValues.toArray(new ReturnValue[0])));\n    }\n\n    /**\n     * Returns the {@link MssqlRowMetadata} associated with this {@link Row}.\n     *\n     * @return the {@link MssqlRowMetadata} associated with this {@link Row}.\n     */\n    public MssqlReturnValuesMetadata getMetadata() {\n        return this.metadata;\n    }\n\n    @Override\n    public <T> T get(int index, Class<T> type) {\n\n        Assert.requireNonNull(type, \"Type must not be null\");\n        requireNotReleased();\n\n        ReturnValue returnValue = this.metadata.get(index);\n        return doGet(returnValue, type);\n    }\n\n    @Override\n    public <T> T get(String name, Class<T> type) {\n\n        Assert.requireNonNull(name, \"Name must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n        requireNotReleased();\n\n        ReturnValue returnValue = this.metadata.get(name);\n        return doGet(returnValue, type);\n    }\n\n    @Nullable\n    private <T> T doGet(@Nullable ReturnValue returnValue, Class<T> type) {\n\n        if (returnValue == null) {\n            return null;\n        }\n\n        if (returnValue.getValueType().getServerType() == SqlServerType.SQL_VARIANT) {\n            throw new UnsupportedOperationException(\"sql_variant columns not supported. See https://github.com/r2dbc/r2dbc-mssql/issues/67.\");\n        }\n\n        ByteBuf value = returnValue.getValue();\n        value.markReaderIndex();\n\n        try {\n            return this.codecs.decode(value, returnValue.asDecodable(), type);\n        } finally {\n            value.resetReaderIndex();\n        }\n    }\n\n    /**\n     * Decrement the reference count and release the {@link RowToken} to allow deallocation of underlying memory.\n     */\n    public void release() {\n        requireNotReleased();\n        this.state = STATE_RELEASED;\n        this.returnValues.forEach(ReturnValue::release);\n    }\n\n    private void requireNotReleased() {\n        if (this.state == STATE_RELEASED) {\n            throw new IllegalStateException(\"Value cannot be retrieved after row has been released\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlReturnValuesMetadata.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.message.token.ReturnValue;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.OutParametersMetadata;\nimport io.r2dbc.spi.RowMetadata;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Microsoft SQL Server-specific {@link RowMetadata} backed by {@link ReturnValue}.\n *\n * @author Mark Paluch\n */\nfinal class MssqlReturnValuesMetadata extends NamedCollectionSupport<ReturnValue> implements OutParametersMetadata, Collection<String> {\n\n    private final Codecs codecs;\n\n    @Nullable\n    private Map<ReturnValue, MssqlColumnMetadata> metadataCache;\n\n    /**\n     * Creates a new {@link MssqlReturnValuesMetadata}.\n     *\n     * @param codecs       the codec registry.\n     * @param returnValues collection of {@link ReturnValue}s.\n     */\n    MssqlReturnValuesMetadata(Codecs codecs, ReturnValue[] returnValues) {\n        super(returnValues, toMap(returnValues, ReturnValue::getParameterName), ReturnValue::getParameterName, \"return value\");\n        this.codecs = Assert.requireNonNull(codecs, \"Codecs must not be null\");\n    }\n\n    /**\n     * Creates a new {@link MssqlReturnValuesMetadata}.\n     *\n     * @param codecs         the codec registry.\n     * @param columnMetadata the column metadata.\n     */\n    public static MssqlReturnValuesMetadata create(Codecs codecs, List<ReturnValue> returnValues) {\n\n        Assert.notNull(returnValues, \"ReturnValues must not be null\");\n\n        return new MssqlReturnValuesMetadata(codecs, returnValues.toArray(new ReturnValue[0]));\n    }\n\n    @Override\n    public MssqlColumnMetadata getParameterMetadata(int index) {\n        if (this.metadataCache == null) {\n            this.metadataCache = new HashMap<>();\n        }\n        return this.metadataCache.computeIfAbsent(this.get(index), returnValue -> new MssqlColumnMetadata(returnValue.asDecodable(), this.codecs));\n    }\n\n    @Override\n    public MssqlColumnMetadata getParameterMetadata(String identifier) {\n        if (this.metadataCache == null) {\n            this.metadataCache = new HashMap<>();\n        }\n        return this.metadataCache.computeIfAbsent(this.get(identifier), returnValue -> new MssqlColumnMetadata(returnValue.asDecodable(), this.codecs));\n    }\n\n    @Override\n    public List<MssqlColumnMetadata> getParameterMetadatas() {\n\n        if (this.metadataCache == null) {\n            this.metadataCache = new HashMap<>();\n        }\n\n        List<MssqlColumnMetadata> metadatas = new ArrayList<>(this.getCount());\n\n        for (int i = 0; i < this.getCount(); i++) {\n\n            MssqlColumnMetadata columnMetadata = this.metadataCache.computeIfAbsent(this.get(i), returnValue -> new MssqlColumnMetadata(returnValue.asDecodable(), this.codecs));\n            metadatas.add(columnMetadata);\n        }\n\n        return metadatas;\n    }\n\n    @Override\n    ReturnValue find(String name) {\n\n        ReturnValue returnValue = super.find(name);\n\n        if (returnValue == null && !name.startsWith(\"@\")) {\n            return super.find(\"@\" + name);\n        }\n\n        return returnValue;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlRow.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.util.ReferenceCounted;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.token.RowToken;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Row;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Microsoft SQL Server-specific {@link Row} implementation.\n * A {@link Row} is stateful regarding its data state. It holds a {@link RowToken} along with row data that needs to be deallocated after processing the row. This row is no longer usable once it\n * was {@link #release() released}.\n *\n * @author Mark Paluch\n * @see #release()\n * @see ReferenceCounted\n */\nfinal class MssqlRow implements Row {\n\n    private static final int STATE_ACTIVE = 0;\n\n    private static final int STATE_RELEASED = 1;\n\n    private final Codecs codecs;\n\n    private final MssqlRowMetadata metadata;\n\n    private final RowToken rowToken;\n\n    private volatile int state = STATE_ACTIVE;\n\n    MssqlRow(Codecs codecs, RowToken rowToken, MssqlRowMetadata metadata) {\n\n        this.codecs = codecs;\n        this.metadata = metadata;\n        this.rowToken = rowToken;\n    }\n\n    /**\n     * Create a new {@link MssqlRow}.\n     *\n     * @param codecs   the codecs to decode tabular data.\n     * @param rowToken the row data.\n     * @param columns  column specifications.\n     * @return\n     */\n    static MssqlRow toRow(Codecs codecs, RowToken rowToken, MssqlRowMetadata metadata) {\n\n        Assert.requireNonNull(codecs, \"Codecs must not be null\");\n        Assert.requireNonNull(rowToken, \"RowToken must not be null\");\n        Assert.requireNonNull(metadata, \"MssqlRowMetadata must not be null\");\n\n        return new MssqlRow(codecs, rowToken, metadata);\n    }\n\n    /**\n     * Returns the {@link MssqlRowMetadata} associated with this {@link Row}.\n     *\n     * @return the {@link MssqlRowMetadata} associated with this {@link Row}.\n     */\n    public MssqlRowMetadata getMetadata() {\n        return this.metadata;\n    }\n\n    @Override\n    public <T> T get(int index, Class<T> type) {\n\n        Assert.requireNonNull(type, \"Type must not be null\");\n        requireNotReleased();\n\n        Column column = this.metadata.get(index);\n        return doGet(column, type);\n    }\n\n    @Override\n    public <T> T get(String name, Class<T> type) {\n\n        Assert.requireNonNull(name, \"Name must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n        requireNotReleased();\n\n        Column column = this.metadata.get(name);\n        return doGet(column, type);\n    }\n\n    @Nullable\n    private <T> T doGet(Column column, Class<T> type) {\n\n        ByteBuf columnData = this.rowToken.getColumnData(column.getIndex());\n\n        if (columnData == null) {\n            return null;\n        }\n\n        if (column.getType().getServerType() == SqlServerType.SQL_VARIANT) {\n            throw new UnsupportedOperationException(\"sql_variant columns not supported. See https://github.com/r2dbc/r2dbc-mssql/issues/67.\");\n        }\n\n        columnData.markReaderIndex();\n\n        try {\n            return this.codecs.decode(columnData, column, type);\n        } finally {\n            columnData.resetReaderIndex();\n        }\n    }\n\n    /**\n     * Decrement the reference count and release the {@link RowToken} to allow deallocation of underlying memory.\n     */\n    public void release() {\n        requireNotReleased();\n        this.state = STATE_RELEASED;\n        this.rowToken.release();\n    }\n\n    private void requireNotReleased() {\n        if (this.state == STATE_RELEASED) {\n            throw new IllegalStateException(\"Value cannot be retrieved after row has been released\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlRowMetadata.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.token.ColumnMetadataToken;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.RowMetadata;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Microsoft SQL Server-specific {@link RowMetadata}.\n *\n * @author Mark Paluch\n */\nfinal class MssqlRowMetadata extends NamedCollectionSupport<Column> implements RowMetadata, Collection<String> {\n\n    private final Codecs codecs;\n\n    @Nullable\n    private Map<Column, MssqlColumnMetadata> metadataCache;\n\n    /**\n     * Creates a new {@link MssqlColumnMetadata}.\n     *\n     * @param codecs           the codec registry.\n     * @param columns          collection of {@link Column}s.\n     * @param nameKeyedColumns name-keyed {@link Map} of {@link Column}s.\n     */\n    MssqlRowMetadata(Codecs codecs, Column[] columns, Map<String, Column> nameKeyedColumns) {\n        super(columns, nameKeyedColumns, Column::getName, \"column\");\n        this.codecs = Assert.requireNonNull(codecs, \"Codecs must not be null\");\n    }\n\n    /**\n     * Creates a new {@link MssqlColumnMetadata}.\n     *\n     * @param codecs         the codec registry.\n     * @param columnMetadata the column metadata.\n     */\n    public static MssqlRowMetadata create(Codecs codecs, ColumnMetadataToken columnMetadata) {\n\n        Assert.notNull(columnMetadata, \"ColumnMetadata must not be null\");\n\n        return new MssqlRowMetadata(codecs, columnMetadata.getColumns(), columnMetadata.toMap());\n    }\n\n    @Override\n    public MssqlColumnMetadata getColumnMetadata(int index) {\n        if (this.metadataCache == null) {\n            this.metadataCache = new HashMap<>();\n        }\n        return this.metadataCache.computeIfAbsent(this.get(index), column -> new MssqlColumnMetadata(column, this.codecs));\n    }\n\n    @Override\n    public MssqlColumnMetadata getColumnMetadata(String identifier) {\n        if (this.metadataCache == null) {\n            this.metadataCache = new HashMap<>();\n        }\n        return this.metadataCache.computeIfAbsent(this.get(identifier), column -> new MssqlColumnMetadata(column, this.codecs));\n    }\n\n    @Override\n    public List<MssqlColumnMetadata> getColumnMetadatas() {\n\n        if (this.metadataCache == null) {\n            this.metadataCache = new HashMap<>();\n        }\n\n        List<MssqlColumnMetadata> metadatas = new ArrayList<>(this.getCount());\n\n        for (int i = 0; i < this.getCount(); i++) {\n\n            MssqlColumnMetadata columnMetadata = this.metadataCache.computeIfAbsent(this.get(i), column -> new MssqlColumnMetadata(column, this.codecs));\n            metadatas.add(columnMetadata);\n        }\n\n        return metadatas;\n    }\n\n    @Override\n    public boolean contains(String columnName) {\n        return find(columnName) != null;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlSegmentResult.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.util.AbstractReferenceCounted;\nimport io.netty.util.ReferenceCountUtil;\nimport io.netty.util.ReferenceCounted;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.message.token.AbstractDoneToken;\nimport io.r2dbc.mssql.message.token.AbstractInfoToken;\nimport io.r2dbc.mssql.message.token.ColumnMetadataToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.NbcRowToken;\nimport io.r2dbc.mssql.message.token.ReturnValue;\nimport io.r2dbc.mssql.message.token.RowToken;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.OutParameters;\nimport io.r2dbc.spi.R2dbcException;\nimport io.r2dbc.spi.Readable;\nimport io.r2dbc.spi.Result;\nimport io.r2dbc.spi.Row;\nimport io.r2dbc.spi.RowMetadata;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * {@link Result} of query results.\n *\n * @author Mark Paluch\n * @since 0.9\n */\nfinal class MssqlSegmentResult implements MssqlResult {\n\n    private static final Logger LOGGER = Loggers.getLogger(MssqlSegmentResult.class);\n\n    public static final boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();\n\n    private final String sql;\n\n    private final ConnectionContext context;\n\n    private final Codecs codecs;\n\n    private final Flux<Segment> segments;\n\n    private MssqlSegmentResult(String sql, ConnectionContext context, Codecs codecs, Flux<Segment> segments) {\n\n        this.sql = sql;\n        this.context = context;\n        this.codecs = codecs;\n        this.segments = segments;\n    }\n\n    /**\n     * Create a {@link MssqlSegmentResult}.\n     *\n     * @param sql                the underlying SQL statement.\n     * @param codecs             the codecs to use.\n     * @param messages           message stream.\n     * @param expectReturnValues {@code true} if the result is expected to have result values.\n     * @return {@link Result} object.\n     */\n    static MssqlSegmentResult toResult(String sql, ConnectionContext context, Codecs codecs, Flux<io.r2dbc.mssql.message.Message> messages, boolean expectReturnValues) {\n\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n        Assert.requireNonNull(codecs, \"Codecs must not be null\");\n        Assert.requireNonNull(context, \"ConnectionContext must not be null\");\n        Assert.requireNonNull(messages, \"Messages must not be null\");\n\n        LOGGER.debug(context.getMessage(\"Creating new result\"));\n\n        return new MssqlSegmentResult(sql, context, codecs, toSegments(sql, codecs, messages, expectReturnValues));\n    }\n\n    private static Flux<Segment> toSegments(String sql, Codecs codecs, Flux<io.r2dbc.mssql.message.Message> messages, boolean expectReturnValues) {\n\n        Flux<Segment> returnValueStream = Flux.empty();\n        Flux<io.r2dbc.mssql.message.Message> messageStream = messages;\n\n        if (expectReturnValues) {\n\n            List<ReturnValue> returnValues = new ArrayList<>();\n\n            messageStream = messageStream.doOnNext(message -> {\n\n                if (message instanceof ReturnValue) {\n                    returnValues.add((ReturnValue) message);\n                }\n            }).filter(it -> !(it instanceof ReturnValue));\n\n            returnValueStream = Flux.defer(() -> {\n\n                if (returnValues.size() != 0) {\n                    return Flux.just(new MsqlOutSegment(codecs, returnValues));\n                }\n\n                return Flux.empty();\n            });\n        }\n\n        AtomicReference<MssqlRowMetadata> metadataRef = new AtomicReference<>();\n        Flux<Segment> segments = messageStream.handle((message, sink) -> {\n\n            if (message instanceof AbstractDoneToken) {\n\n                AbstractDoneToken doneToken = (AbstractDoneToken) message;\n                if (doneToken.isAttentionAck()) {\n                    sink.error(new ExceptionFactory.MssqlStatementCancelled(sql));\n                    return;\n                }\n            }\n\n            if (message.getClass() == ColumnMetadataToken.class) {\n\n                ColumnMetadataToken token = (ColumnMetadataToken) message;\n\n                if (token.hasColumns()) {\n                    metadataRef.set(MssqlRowMetadata.create(codecs, token));\n                }\n                return;\n            }\n\n            if (message.getClass() == RowToken.class || message.getClass() == NbcRowToken.class) {\n\n                MssqlRowMetadata rowMetadata = metadataRef.get();\n\n                if (rowMetadata == null) {\n                    return;\n                }\n\n                sink.next(new MssqlRowSegment(codecs, (RowToken) message, rowMetadata));\n                return;\n            }\n\n            if (message instanceof AbstractInfoToken) {\n                sink.next(createMessage(sql, (AbstractInfoToken) message));\n                return;\n            }\n\n            if (message instanceof AbstractDoneToken) {\n\n                AbstractDoneToken doneToken = (AbstractDoneToken) message;\n                if (doneToken.hasCount()) {\n                    sink.next(doneToken);\n                }\n            }\n\n            ReferenceCountUtil.release(message);\n        });\n\n        if (expectReturnValues) {\n            segments = segments.concatWith(returnValueStream);\n        }\n\n        return segments;\n    }\n\n    @Override\n    public Mono<Long> getRowsUpdated() {\n\n        return this.segments\n            .<Long>handle((segment, sink) -> {\n\n                if (segment instanceof UpdateCount) {\n\n                    UpdateCount updateCount = (UpdateCount) segment;\n\n                    if (DEBUG_ENABLED) {\n                        LOGGER.debug(this.context.getMessage(\"Incoming row count: {}\"), updateCount);\n                    }\n\n                    sink.next(updateCount.value());\n                }\n\n                if (isError(segment)) {\n                    sink.error(((Message) segment).exception());\n                    return;\n                }\n\n                ReferenceCountUtil.release(segment);\n            }).reduce(Long::sum);\n    }\n\n    @Override\n    public <T> Flux<T> map(BiFunction<Row, RowMetadata, ? extends T> mappingFunction) {\n\n        Assert.requireNonNull(mappingFunction, \"Mapping function must not be null\");\n\n        return doMap(true, false, readable -> {\n            Row row = (Row) readable;\n            return mappingFunction.apply(row, row.getMetadata());\n        });\n    }\n\n    @Override\n    public <T> Flux<T> map(Function<? super Readable, ? extends T> mappingFunction) {\n\n        Assert.requireNonNull(mappingFunction, \"Mapping function must not be null\");\n\n        return doMap(true, true, mappingFunction);\n    }\n\n    private <T> Flux<T> doMap(boolean rows, boolean outparameters, Function<? super Readable, ? extends T> mappingFunction) {\n\n        return this.segments\n            .handle((segment, sink) -> {\n\n                if (rows && segment instanceof RowSegment) {\n\n                    RowSegment data = (RowSegment) segment;\n                    Row row = data.row();\n                    try {\n                        sink.next(mappingFunction.apply(row));\n                    } finally {\n                        ReferenceCountUtil.release(data);\n                    }\n\n                    return;\n                }\n\n                if (outparameters && segment instanceof OutSegment) {\n\n                    OutSegment data = (OutSegment) segment;\n                    OutParameters outParameters = data.outParameters();\n                    try {\n                        sink.next(mappingFunction.apply(outParameters));\n                    } finally {\n                        ReferenceCountUtil.release(data);\n                    }\n\n                    return;\n                }\n\n                if (isError(segment)) {\n                    sink.error(((Message) segment).exception());\n                    return;\n                }\n\n                ReferenceCountUtil.release(segment);\n            });\n    }\n\n    @Override\n    public MssqlResult filter(Predicate<Segment> filter) {\n\n        Assert.requireNonNull(filter, \"filter must not be null\");\n\n        Flux<Segment> filteredSegments = this.segments.filter(message -> {\n\n            if (filter.test(message)) {\n                return true;\n            }\n\n            ReferenceCountUtil.release(message);\n            return false;\n        });\n\n        return new MssqlSegmentResult(this.sql, this.context, this.codecs, filteredSegments);\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <T> Flux<T> flatMap(Function<Segment, ? extends Publisher<? extends T>> mappingFunction) {\n\n        Assert.requireNonNull(mappingFunction, \"mappingFunction must not be null\");\n\n        return this.segments\n            .concatMap(segment -> {\n\n                Publisher<? extends T> result = mappingFunction.apply(segment);\n\n                if (result == null) {\n                    return Mono.error(new IllegalStateException(\"The mapper returned a null Publisher\"));\n                }\n\n                // doAfterTerminate to not release resources before they had a chance to get emitted\n                if (result instanceof Mono) {\n                    return ((Mono<T>) result).doFinally(s -> ReferenceCountUtil.release(segment));\n                }\n\n                return Flux.from(result).doFinally(s -> ReferenceCountUtil.release(segment));\n            });\n    }\n\n    private boolean isError(Segment segment) {\n        return segment instanceof MssqlMessage && ((MssqlMessage) segment).isError();\n    }\n\n    private static Message createMessage(String sql, AbstractInfoToken message) {\n\n        ErrorDetails errorDetails = ExceptionFactory.createErrorDetails(message);\n\n        return new MssqlMessage(message, sql, errorDetails);\n    }\n\n    static class MssqlMessage implements Message {\n\n        private final ErrorDetails errorDetails;\n\n        private final AbstractInfoToken message;\n\n        private final String sql;\n\n        public MssqlMessage(AbstractInfoToken message, String sql, ErrorDetails errorDetails) {\n            this.message = message;\n            this.sql = sql;\n            this.errorDetails = errorDetails;\n        }\n\n        @Override\n        public R2dbcException exception() {\n            return ExceptionFactory.createException(this.message, this.sql);\n        }\n\n        @Override\n        public int errorCode() {\n            return (int) this.errorDetails.getNumber();\n        }\n\n        @Override\n        public String sqlState() {\n            return this.errorDetails.getStateCode();\n        }\n\n        @Override\n        public String message() {\n            return this.errorDetails.getMessage();\n        }\n\n        public boolean isError() {\n            return this.message instanceof ErrorToken;\n        }\n\n    }\n\n    private static class MssqlRowSegment extends AbstractReferenceCounted implements RowSegment {\n\n        private final RowToken rowToken;\n\n        private final MssqlRow row;\n\n        public MssqlRowSegment(Codecs codecs, RowToken rowToken, MssqlRowMetadata rowMetadata) {\n            this.rowToken = rowToken;\n            this.row = MssqlRow.toRow(codecs, this.rowToken, rowMetadata);\n        }\n\n        @Override\n        public Row row() {\n            return this.row;\n        }\n\n        @Override\n        public ReferenceCounted touch(Object hint) {\n            return this;\n        }\n\n        @Override\n        protected void deallocate() {\n            this.rowToken.release();\n        }\n\n    }\n\n    private static class MsqlOutSegment extends AbstractReferenceCounted implements OutSegment {\n\n        private final MssqlReturnValues returnValues;\n\n        public MsqlOutSegment(Codecs codecs, List<ReturnValue> returnValues) {\n            this.returnValues = MssqlReturnValues.toReturnValues(codecs, returnValues);\n        }\n\n        @Override\n        public OutParameters outParameters() {\n            return this.returnValues;\n        }\n\n        @Override\n        public ReferenceCounted touch(Object hint) {\n            return this;\n        }\n\n        @Override\n        protected void deallocate() {\n            this.returnValues.release();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlStatement.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.spi.Statement;\nimport reactor.core.publisher.Flux;\n\n/**\n * A strongly typed implementation of {@link Statement} for a Microsoft SQL Server database.\n * <p>\n * Microsoft SQL Server uses named parameters for parametrized statements:\n * <pre class=\"code\">\n * INSERT INTO person (id, first_name, last_name) VALUES(@id, @firstname, @lastname)\n * </pre>\n * Use {@link #bind(String, Object)} and {@link #bindNull(String, Class)} over positional ({@link #bind(int, Object)}) binding.\n *\n * @author Mark Paluch\n */\npublic interface MssqlStatement extends Statement {\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    MssqlStatement add();\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    MssqlStatement bind(int index, Object value);\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    MssqlStatement bindNull(int index, Class<?> type);\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    MssqlStatement bind(String identifier, Object value);\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    MssqlStatement bindNull(String identifier, Class<?> type);\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    Flux<MssqlResult> execute();\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    MssqlStatement returnGeneratedValues(String... columns);\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    MssqlStatement fetchSize(int fetchSize);\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/MssqlStatementSupport.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.scheduler.Schedulers;\nimport reactor.util.annotation.Nullable;\n\nimport java.sql.Statement;\nimport java.time.Duration;\nimport java.util.concurrent.TimeoutException;\n\n/**\n * Base class for {@link Statement} implementations.\n * <p>This class considers {@link #returnGeneratedValues(String...)} and {@link #fetchSize(int)} and cursor/direct execution preferences.\n * <p>Cursor/direct execution preference is considered as initial hint. A statement can be forced to be executed directly by setting {@link #fetchSize(int)} to zero. Alternatively, cursored\n * execution can be forced by setting {@link #fetchSize(int)} to a non-zero value.\n *\n * @author Mark Paluch\n */\nabstract class MssqlStatementSupport implements MssqlStatement {\n\n    static final int FETCH_SIZE = 128;\n\n    static final int FETCH_UNCONFIGURED = -1;\n\n    private final boolean preferCursoredExecution;\n\n    @Nullable\n    private String[] generatedColumns;\n\n    private int fetchSize = FETCH_UNCONFIGURED;\n\n    MssqlStatementSupport(boolean preferCursoredExecution) {\n        this.preferCursoredExecution = preferCursoredExecution;\n    }\n\n    /**\n     * Returns the effective fetch size.\n     * <p>\n     * A fetch size of zero indicates direct execution.\n     *\n     * @return the effective fetch size. Can be zero. Defaults to {@link #FETCH_SIZE} if cursored execution is preferred.\n     */\n    int getEffectiveFetchSize() {\n\n        if (this.preferCursoredExecution) {\n            return this.fetchSize == FETCH_UNCONFIGURED ? FETCH_SIZE : this.fetchSize;\n        }\n\n        return this.fetchSize == FETCH_UNCONFIGURED ? 0 : this.fetchSize;\n    }\n\n    @Nullable\n    String[] getGeneratedColumns() {\n        return this.generatedColumns;\n    }\n\n    @Override\n    public MssqlStatementSupport returnGeneratedValues(String... columns) {\n\n        Assert.requireNonNull(columns, \"columns must not be null\");\n\n        this.generatedColumns = columns;\n        return this;\n    }\n\n    @Override\n    public MssqlStatementSupport fetchSize(int fetchSize) {\n\n        Assert.isTrue(fetchSize >= 0, \"Fetch size must be greater or equal to zero\");\n\n        this.fetchSize = fetchSize;\n        return this;\n    }\n\n    Flux<Message> potentiallyAttachTimeout(Flux<Message> exchange, ConnectionOptions connectionOptions, Client client, String sql) {\n\n        Duration statementTimeout = connectionOptions.getStatementTimeout();\n\n        if (statementTimeout.isZero()) {\n            return exchange;\n        }\n\n        Mono<Long> timeout = Mono.delay(statementTimeout, Schedulers.parallel()).onErrorReturn(0L);\n        return exchange.timeout(timeout).onErrorResume(TimeoutException.class, e -> client.attention().then(Mono.error(new ExceptionFactory.MssqlStatementTimeoutException(String.format(\"Statement \" +\n            \"did not yield a result within %dms\", statementTimeout.toMillis()),\n            sql))));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/NamedCollectionSupport.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\nimport java.lang.reflect.Array;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Object that provides access to items (columns, return values) by index and by name. The index starts at {@literal 0} (zero-based index).\n *\n * @author Mark Paluch\n */\nabstract class NamedCollectionSupport<N> implements Collection<String> {\n\n    private final N[] items;\n\n    private final Map<String, N> nameKeyed;\n\n    private final Function<N, String> nameMapper;\n\n    private final String itemName;\n\n    @SuppressWarnings(\"unchecked\")\n    NamedCollectionSupport(N[] items, Map<String, N> nameKeyed, Function<N, String> nameMapper, String itemName) {\n\n        this.nameMapper = nameMapper;\n        this.itemName = itemName;\n\n        if (shouldStripROWSTAT(items)) {\n\n            this.items = (N[]) Array.newInstance(items.getClass().getComponentType(), items.length - 1);\n            System.arraycopy(items, 0, this.items, 0, this.items.length);\n\n            this.nameKeyed = toMap(this.items, nameMapper);\n        } else {\n\n            this.items = items;\n            this.nameKeyed = nameKeyed;\n        }\n    }\n\n    static <N> Map<String, N> toMap(N[] named, Function<N, String> nameMapper) {\n\n        Map<String, N> nameKeyed = new HashMap<>(named.length, 1);\n\n        for (N n : named) {\n\n            N old = nameKeyed.put(nameMapper.apply(n), n);\n            if (old != null) {\n                nameKeyed.put(nameMapper.apply(n), old);\n            }\n        }\n        return nameKeyed;\n    }\n\n    // Hide ROWSTAT column from metatada if it's the last column. Typically synthesized in cursored fetch.\n    private boolean shouldStripROWSTAT(N[] columns) {\n        return columns.length > 0 && \"ROWSTAT\".equals(this.nameMapper.apply(columns[columns.length - 1]));\n    }\n\n    /**\n     * Lookup {@link Column} by index or by its name.\n     *\n     * @param identifier the index or name.\n     * @return the identifier.\n     * @throws IllegalArgumentException if the item cannot be retrieved.\n     * @throws IllegalArgumentException when {@code identifier} is {@code null}.\n     */\n    N get(Object identifier) {\n\n        Assert.requireNonNull(identifier, \"Identifier must not be null\");\n\n        if (identifier instanceof Integer) {\n            return get((int) identifier);\n        }\n\n        if (identifier instanceof String) {\n            return get((String) identifier);\n        }\n\n        throw new IllegalArgumentException(String.format(\"Identifier [%s] is not a valid identifier. Should either be an Integer index or a String %s name.\", identifier, this.itemName));\n    }\n\n    /**\n     * Lookup item by its {@code index}.\n     *\n     * @param index the item index. Must be greater zero and less than {@link #getCount()}.\n     * @return the item.\n     */\n    N get(int index) {\n\n        if (this.items.length > index && index >= 0) {\n            return this.items[index];\n        }\n\n        throw new IndexOutOfBoundsException(String.format(\"Index [%d] is larger than the number of %ss [%d]\", index, this.itemName, this.items.length));\n    }\n\n    /**\n     * Lookup item by its {@code name}.\n     *\n     * @param name the item name.\n     * @return the item.\n     */\n    N get(String name) {\n\n        N item = find(name);\n\n        if (item == null) {\n            throw new NoSuchElementException(String.format(\"[%s] does not exist in %s names %s\", name, this.itemName, this.nameKeyed.keySet()));\n        }\n\n        return item;\n    }\n\n    /**\n     * Lookup item by its {@code name}.\n     *\n     * @param name the item name.\n     * @return the item.\n     */\n    @Nullable\n    N find(String name) {\n\n        N item = this.nameKeyed.get(name);\n\n        if (item == null) {\n            name = EscapeAwareNameMatcher.find(name, this.nameKeyed.keySet());\n            if (name != null) {\n                item = this.nameKeyed.get(name);\n            }\n        }\n\n        return item;\n    }\n\n    /**\n     * Returns the number of items.\n     *\n     * @return the number of itemss.\n     */\n    int getCount() {\n        return this.items.length;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [\").append(Arrays.stream(this.items).map(this.nameMapper).collect(Collectors.joining(\", \"))).append(\"]\");\n        return sb.toString();\n    }\n\n    @Override\n    public int size() {\n        return this.getCount();\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    @Override\n    public boolean contains(Object o) {\n\n        if (o instanceof String) {\n            return this.find((String) o) != null;\n        }\n\n        return false;\n    }\n\n    @Override\n    public boolean containsAll(Collection<?> c) {\n\n        for (Object o : c) {\n            if (!contains(o)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    @Override\n    public Iterator<String> iterator() {\n\n        N[] items = this.items;\n\n        return new Iterator<String>() {\n\n            int index = 0;\n\n            @Override\n            public boolean hasNext() {\n                return items.length > this.index;\n            }\n\n            @Override\n            public String next() {\n                N item = items[this.index++];\n                return NamedCollectionSupport.this.nameMapper.apply(item);\n            }\n        };\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <T> T[] toArray(T[] a) {\n\n        if (a.length < size()) {\n            a = (T[]) Array.newInstance(a.getClass().getComponentType(), size());\n        }\n\n        for (int i = 0; i < size(); i++) {\n            a[i] = (T) this.nameMapper.apply(this.get(i));\n        }\n\n        return a;\n    }\n\n    @Override\n    public Object[] toArray() {\n\n        Object[] result = new Object[size()];\n\n        for (int i = 0; i < size(); i++) {\n            result[i] = this.nameMapper.apply(this.get(i));\n        }\n\n        return result;\n    }\n\n    @Override\n    public boolean add(String s) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean remove(Object o) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean addAll(Collection<? extends String> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean removeAll(Collection<?> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean retainAll(Collection<?> c) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void clear() {\n        throw new UnsupportedOperationException();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/OptionMapper.java",
    "content": "/*\n * Copyright 2020-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport io.r2dbc.spi.Option;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.UUID;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.function.Supplier;\n\n/**\n * An utility data parser for {@link Option}.\n *\n * @author Mark Paluch\n * @since 0.8.3\n */\nfinal class OptionMapper {\n\n    private final ConnectionFactoryOptions options;\n\n    private OptionMapper(ConnectionFactoryOptions options) {\n        this.options = options;\n    }\n\n    /**\n     * Construct a new {@link OptionMapper} given {@link ConnectionFactoryOptions}.\n     *\n     * @param options must not be {@code null}.\n     * @return the option mapper.\n     */\n    public static OptionMapper create(ConnectionFactoryOptions options) {\n        return new OptionMapper(options);\n    }\n\n    /**\n     * Construct a new {@link Source} for a {@link Option}. Options without a value are not bound or mapped in the later stages of {@link Source}.\n     *\n     * @param option the option to apply.\n     * @param <T>    inferred option type.\n     * @return the source object.\n     */\n    public Source<Object> from(Option<?> option) {\n\n        if (this.options.hasOption(option)) {\n\n            return new AvailableSource<>(() -> {\n                return this.options.getRequiredValue(option);\n            }, option.name());\n        }\n\n        return NullSource.instance();\n    }\n\n    /**\n     * Construct a new {@link Source} for a {@link Option} using type inference. Options without a value are not bound or mapped in the later stages of {@link Source}.\n     *\n     * @param option the option to apply.\n     * @param <T>    inferred option type.\n     * @return the source object.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> Source<T> fromTyped(Option<T> option) {\n\n        if (this.options.hasOption(option)) {\n\n            return new AvailableSource<>(() -> {\n                return (T) this.options.getRequiredValue(option);\n            }, option.name());\n        }\n\n        return NullSource.instance();\n    }\n\n    /**\n     * Parse an {@link Option} to boolean.\n     */\n    static boolean toBoolean(Object value) {\n\n        if (value instanceof Boolean) {\n            return ((Boolean) value).booleanValue();\n        }\n\n        if (value instanceof String) {\n            return Boolean.parseBoolean(value.toString());\n        }\n\n        throw new IllegalArgumentException(String.format(\"Cannot convert value %s to boolean\", value));\n    }\n\n    /**\n     * Parse an ISO-8601 formatted {@link Option} to {@link Duration}.\n     */\n    static Duration toDuration(Object value) {\n\n        if (value instanceof Duration) {\n            return ((Duration) value);\n        }\n\n        if (value instanceof String) {\n            return Duration.parse(value.toString());\n        }\n\n        throw new IllegalArgumentException(String.format(\"Cannot convert value %s to Duration\", value));\n    }\n\n    /**\n     * Parse an {@link Option} to {@link File}.\n     */\n    static File toFile(Object value) {\n\n        if (value instanceof File) {\n            return (File) value;\n        }\n\n        if (value instanceof String) {\n            return new File(value.toString());\n        }\n\n        throw new IllegalArgumentException(String.format(\"Cannot convert value %s to File\", value));\n    }\n\n    /**\n     * Parse an {@link Option} to int.\n     */\n    static int toInteger(Object value) {\n\n        if (value instanceof Number) {\n            return ((Number) value).intValue();\n        }\n\n        if (value instanceof String) {\n            return Integer.parseInt(value.toString());\n        }\n\n        throw new IllegalArgumentException(String.format(\"Cannot convert value %s to integer\", value));\n    }\n\n    /**\n     * Parse an {@link Option} to {@link Predicate}.\n     */\n    @SuppressWarnings(\"unchecked\")\n    static Predicate<String> toStringPredicate(Object value) {\n\n        if (value instanceof Predicate) {\n            return (Predicate) value;\n        }\n\n        if (value instanceof Boolean) {\n            boolean choice = (boolean) value;\n            return s -> choice;\n        }\n\n        if (value instanceof String) {\n            String stringValue = value.toString();\n            if (\"true\".equalsIgnoreCase(stringValue) || \"false\".equalsIgnoreCase(stringValue)) {\n                return toStringPredicate(Boolean.parseBoolean(stringValue));\n            } else {\n\n                try {\n                    Object predicate = Class.forName(stringValue).getDeclaredConstructor().newInstance();\n                    if (predicate instanceof Predicate) {\n                        return toStringPredicate(predicate);\n                    } else {\n                        throw new IllegalArgumentException(\"Value '\" + value + \"' must be an instance of Predicate\");\n                    }\n                } catch (ReflectiveOperationException e) {\n                    throw new IllegalArgumentException(\"Cannot instantiate '\" + value + \"'\", e);\n                }\n            }\n        }\n\n        throw new IllegalArgumentException(String.format(\"Cannot convert value %s to Predicate\", value));\n    }\n\n    /**\n     * Parse an {@link Option} to {@link UUID}.\n     */\n    static UUID toUuid(Object value) {\n\n        if (value instanceof UUID) {\n            return (UUID) value;\n        }\n\n        if (value instanceof String) {\n            return UUID.fromString(value.toString());\n        }\n\n        throw new IllegalArgumentException(String.format(\"Cannot convert value %s to UUID\", value));\n    }\n\n    public interface Source<T> {\n\n        /**\n         * Return an mapped version of the source changed via the given mapping function.\n         *\n         * @param <R>             the resulting type\n         * @param mappingFunction the mapping function to apply\n         * @return a new adapted source instance\n         */\n        <R> Source<R> map(Function<Object, R> mappingFunction);\n\n        /**\n         * Complete the mapping by passing any non-filtered value to the specified\n         * consumer.\n         *\n         * @param consumer the consumer that should accept the value if it's not been\n         *                 filtered\n         */\n        Otherwise to(Consumer<T> consumer);\n\n        /**\n         * Complete the mapping by passing any non-filtered value to the specified\n         * consumer.\n         *\n         * @param consumer the runnable that should be invoked.\n         */\n        Otherwise to(Runnable consumer);\n\n    }\n\n    public interface Otherwise {\n\n        /**\n         * Invoked if the previous {@link Source} outcome did not match.\n         *\n         * @param consumer the runnable that should be invoked.\n         */\n        void otherwise(Runnable consumer);\n\n    }\n\n    private enum Otherwises implements Otherwise {\n\n        NONE {\n            @Override\n            public void otherwise(Runnable consumer) {\n                // no-op\n            }\n        }, FALLBACK {\n            @Override\n            public void otherwise(Runnable consumer) {\n                consumer.run();\n            }\n        }\n    }\n\n    @SuppressWarnings({\"unchecked\", \"rawtypes\"})\n    private enum NullSource implements Source<Object> {\n\n        INSTANCE;\n\n        public static <T> Source<T> instance() {\n            return (Source) INSTANCE;\n        }\n\n        @Override\n        public <R> Source<R> map(Function<Object, R> mappingFunction) {\n            return (Source) this;\n        }\n\n        @Override\n        public Otherwise to(Consumer<Object> consumer) {\n            return Otherwises.FALLBACK;\n        }\n\n        @Override\n        public Otherwise to(Runnable consumer) {\n            return Otherwises.FALLBACK;\n        }\n    }\n\n    private static class AvailableSource<T> implements Source<T> {\n\n        private final Supplier<T> supplier;\n\n        private final String optionName;\n\n        private AvailableSource(Supplier<T> supplier, String optionName) {\n            this.supplier = supplier;\n            this.optionName = optionName;\n        }\n\n        /**\n         * Return an mapped version of the source changed via the given mapping function.\n         *\n         * @param <R>             the resulting type.\n         * @param mappingFunction the mapping function to apply.\n         * @return a new mapped source instance.\n         */\n        @Override\n        public <R> Source<R> map(Function<Object, R> mappingFunction) {\n            Assert.requireNonNull(mappingFunction, \"Mapping function must not be null\");\n\n            Supplier<R> supplier = () -> mappingFunction.apply(this.supplier.get());\n\n            return new AvailableSource<>(supplier, this.optionName);\n        }\n\n        /**\n         * Complete the mapping by passing any non-filtered value to the specified\n         * consumer.\n         *\n         * @param consumer the consumer that should accept the value.\n         */\n        @Override\n        public Otherwise to(Consumer<T> consumer) {\n            Assert.requireNonNull(consumer, \"Consumer must not be null\");\n            try {\n                T value = this.supplier.get();\n\n                if (value != null) {\n                    consumer.accept(value);\n                    return Otherwises.NONE;\n                }\n            } catch (Exception e) {\n                throw new IllegalArgumentException(String.format(\"Cannot assign option %s\", this.optionName), e);\n            }\n\n            return Otherwises.FALLBACK;\n        }\n\n        @Override\n        public Otherwise to(Runnable consumer) {\n            return to(ignore -> consumer.run());\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/ParametrizedMssqlStatement.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.codec.Encoded;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.codec.RpcParameterContext;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.token.AbstractDoneToken;\nimport io.r2dbc.mssql.message.token.DoneInProcToken;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Clob;\nimport io.r2dbc.spi.Parameter;\nimport io.r2dbc.spi.Statement;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Sinks;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Parametrized {@link Statement} with parameter markers executed against a Microsoft SQL Server database.\n * <p>\n * T-SQL uses named parameters that are at-prefixed ({@literal @}). Examples for parameter names are:\n * <pre class=\"code\">\n * &#x40;p0\n *\n * &#x40;myparam\n *\n * &#x40;first_name\n * </pre>\n *\n * @author Mark Paluch\n */\nfinal class ParametrizedMssqlStatement extends MssqlStatementSupport implements MssqlStatement {\n\n    private static final Logger LOGGER = Loggers.getLogger(ParametrizedMssqlStatement.class);\n\n    private static final boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();\n\n    private static final Pattern PARAMETER_MATCHER = Pattern.compile(\"@([\\\\p{Alpha}@][@$\\\\d\\\\w_]{0,127})\");\n\n    private final PreparedStatementCache statementCache;\n\n    private final Client client;\n\n    private final ConnectionOptions connectionOptions;\n\n    private final ConnectionContext context;\n\n    private final Codecs codecs;\n\n    private final ParsedQuery parsedQuery;\n\n    private final Bindings bindings = new Bindings();\n\n    private final boolean sendStringParametersAsUnicode;\n\n    private volatile boolean executed = false;\n\n    ParametrizedMssqlStatement(Client client, ConnectionOptions connectionOptions, String sql) {\n\n        super(connectionOptions.prefersCursors(sql));\n        this.connectionOptions = connectionOptions;\n\n        Assert.requireNonNull(client, \"Client must not be null\");\n        Assert.requireNonNull(connectionOptions, \"ConnectionOptions must not be null\");\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n\n        this.statementCache = connectionOptions.getPreparedStatementCache();\n        this.client = client;\n        this.context = client.getContext();\n        this.codecs = connectionOptions.getCodecs();\n        this.parsedQuery = this.statementCache.getParsedSql(sql, ParsedQuery::parse);\n        this.sendStringParametersAsUnicode = connectionOptions.isSendStringParametersAsUnicode();\n    }\n\n    @Override\n    public ParametrizedMssqlStatement add() {\n\n        assertNotExecuted();\n        this.bindings.finish();\n        this.bindings.getCurrent();\n        return this;\n    }\n\n    @Override\n    public Flux<MssqlResult> execute() {\n\n        int effectiveFetchSize = getEffectiveFetchSize();\n        return Flux.defer(() -> {\n\n            assertNotExecuted();\n\n            this.executed = true;\n\n            boolean useGeneratedKeysClause = GeneratedValues.shouldExpectGeneratedKeys(this.getGeneratedColumns());\n            String sql = useGeneratedKeysClause ? GeneratedValues.augmentQuery(this.parsedQuery.sql, getGeneratedColumns()) : this.parsedQuery.sql;\n\n            if (this.bindings.bindings.isEmpty()) {\n\n                Flux<Message> exchange = potentiallyAttachTimeout(QueryMessageFlow.exchange(this.client, sql), this.connectionOptions, this.client, sql);\n                return exchange.windowUntil(AbstractDoneToken.class::isInstance).map(it -> DefaultMssqlResult.toResult(this.parsedQuery.getSql(), this.context, this.codecs, it, false));\n            }\n\n            if (this.bindings.bindings.size() == 1) {\n\n                Binding binding = this.bindings.bindings.get(0);\n                Flux<Message> exchange = exchange(effectiveFetchSize, useGeneratedKeysClause, sql, binding);\n\n                return exchange.windowUntil(DoneInProcToken.class::isInstance).map(it -> DefaultMssqlResult.toResult(this.parsedQuery.getSql(), this.context, this.codecs, it,\n                    binding.hasOutParameters()));\n            }\n\n            Sinks.Many<Binding> sink = Sinks.many().unicast().onBackpressureBuffer();\n            Iterator<Binding> iterator = this.bindings.bindings.iterator();\n\n            AtomicBoolean cancelled = new AtomicBoolean();\n\n            return sink.asFlux().flatMap(binding -> {\n\n                Flux<Message> exchange = exchange(effectiveFetchSize, useGeneratedKeysClause, sql, binding);\n\n                return exchange.doOnComplete(() -> {\n                    tryNextBinding(iterator, sink, cancelled);\n                }).windowUntil(DoneInProcToken.class::isInstance).map(it -> DefaultMssqlResult.toResult(this.parsedQuery.getSql(), this.context, this.codecs, it, binding.hasOutParameters()));\n            }).doOnSubscribe(it -> {\n\n                Binding initial = iterator.next();\n                sink.emitNext(initial, Sinks.EmitFailureHandler.FAIL_FAST);\n            })\n                .doOnCancel(() -> {\n                    cancelled.set(true);\n                    clearBindings(iterator);\n                })\n                .doOnError(e -> clearBindings(iterator));\n\n        });\n    }\n\n    private Flux<Message> exchange(int effectiveFetchSize, boolean useGeneratedKeysClause, String sql, Binding it) {\n        Flux<Message> exchange;\n\n        if (effectiveFetchSize > 0) {\n\n            if (DEBUG_ENABLED) {\n                LOGGER.debug(this.context.getMessage(\"Start cursored exchange for {} with fetch size {}\"), sql, effectiveFetchSize);\n            }\n\n            exchange = RpcQueryMessageFlow.exchange(this.statementCache, this.client, this.codecs, sql, it, effectiveFetchSize);\n        } else {\n\n            if (DEBUG_ENABLED) {\n                LOGGER.debug(this.context.getMessage(\"Start direct exchange for {}\"), sql);\n            }\n\n            exchange = RpcQueryMessageFlow.exchange(this.client, sql, it);\n        }\n\n        if (useGeneratedKeysClause) {\n            exchange = exchange.transform(GeneratedValues::reduceToSingleCountDoneToken);\n        }\n        return potentiallyAttachTimeout(exchange, this.connectionOptions, this.client, sql);\n    }\n\n    private void clearBindings(Iterator<Binding> iterator) {\n\n        while (iterator.hasNext()) {\n            // exhaust iterator, ignore returned elements\n            iterator.next();\n        }\n\n        this.bindings.clear();\n    }\n\n    @Override\n    public ParametrizedMssqlStatement returnGeneratedValues(String... columns) {\n\n        super.returnGeneratedValues(columns);\n        return this;\n    }\n\n    @Override\n    public ParametrizedMssqlStatement fetchSize(int fetchSize) {\n\n        super.fetchSize(fetchSize);\n        return this;\n    }\n\n    private static void tryNextBinding(Iterator<Binding> iterator, Sinks.Many<Binding> boundRequests, AtomicBoolean cancelled) {\n\n        if (cancelled.get()) {\n            return;\n        }\n\n        try {\n            if (iterator.hasNext()) {\n                boundRequests.emitNext(iterator.next(), Sinks.EmitFailureHandler.FAIL_FAST);\n            } else {\n                boundRequests.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST);\n            }\n        } catch (Exception e) {\n            boundRequests.emitError(e, Sinks.EmitFailureHandler.FAIL_FAST);\n        }\n    }\n\n    @Override\n    public ParametrizedMssqlStatement bind(String identifier, Object value) {\n\n        Assert.requireNonNull(identifier, \"identifier must not be null\");\n        Assert.isInstanceOf(String.class, identifier, \"identifier must be a String\");\n\n        boolean isIn = !(value instanceof Parameter.Out);\n        RpcParameterContext parameterContext = createContext(isIn, null);\n        if (isTextual(value) || (value instanceof Parameter && isTextual(((Parameter) value).getValue()))) {\n            parameterContext = createContext(isIn, new RpcParameterContext.CharacterValueContext(this.client.getRequiredCollation(), this.sendStringParametersAsUnicode));\n        }\n\n        Encoded encoded = this.codecs.encode(this.client.getByteBufAllocator(), parameterContext, value);\n\n        addBinding(getParameterName(identifier), isIn ? RpcDirection.IN : RpcDirection.OUT, encoded);\n\n        return this;\n    }\n\n    @Override\n    public ParametrizedMssqlStatement bind(int index, Object value) {\n\n        Assert.requireNonNull(value, \"value must not be null\");\n\n        return bind(getParameterName(index), value);\n    }\n\n    @Override\n    public ParametrizedMssqlStatement bindNull(String identifier, Class<?> type) {\n\n        Assert.requireNonNull(identifier, \"Identifier must not be null\");\n        Assert.isInstanceOf(String.class, identifier, \"Identifier must be a String\");\n        Assert.requireNonNull(type, \"type must not be null\");\n\n        if (this.executed) {\n            throw new IllegalStateException(\"Statement was already executed\");\n        }\n\n        Encoded encoded = this.codecs.encodeNull(this.client.getByteBufAllocator(), type);\n        addBinding(getParameterName(identifier), RpcDirection.IN, encoded);\n        return this;\n    }\n\n    @Override\n    public ParametrizedMssqlStatement bindNull(int index, Class<?> type) {\n\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        return bindNull(getParameterName(index), type);\n    }\n\n    private static RpcParameterContext createContext(boolean in, @Nullable RpcParameterContext.ValueContext value) {\n\n        if (in) {\n            return value != null ? RpcParameterContext.in(value) : RpcParameterContext.in();\n        }\n\n        return value != null ? RpcParameterContext.out(value) : RpcParameterContext.out();\n    }\n\n    private void addBinding(String name, RpcDirection rpcDirection, Encoded parameter) {\n\n        assertNotExecuted();\n\n        this.bindings.getCurrent().add(name, rpcDirection, parameter);\n    }\n\n    private void assertNotExecuted() {\n        if (this.executed) {\n            throw new IllegalStateException(\"Statement was already executed\");\n        }\n    }\n\n    /**\n     * Returns the {@link Bindings}.\n     *\n     * @return the {@link Bindings}.\n     */\n    Bindings getBindings() {\n        return this.bindings;\n    }\n\n    private String getParameterName(int index) {\n        return this.parsedQuery.getParameterName(index);\n    }\n\n    private String getParameterName(String name) {\n        return this.parsedQuery.getParameterName(name);\n    }\n\n    /**\n     * Returns whether the {@code sql} query is supported by this statement.\n     *\n     * @param sql the SQL to check.\n     * @return {@code true} if supported.\n     * @throws IllegalArgumentException when {@code sql} is {@code null}.\n     */\n    public static boolean supports(String sql) {\n\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n        return sql.lastIndexOf('@') != -1;\n    }\n\n    private static boolean isTextual(@Nullable Object value) {\n        return value instanceof CharSequence || value instanceof Clob;\n    }\n\n    /**\n     * Locates the first occurrence of {@code needle} in {@code sql} starting at {@code offset}. The SQL string may contain:\n     *\n     * <ul>\n     * <li>Literals, enclosed in single quotes ({@literal '}) </li>\n     * <li>Literals, enclosed in double quotes ({@literal \"}) </li>\n     * <li>Escape sequences, enclosed in square brackets ({@literal []}) </li>\n     * <li>Escaped escapes or literal delimiters (i.e. {@literal ''}, {@literal \"\"} or {@literal ]])</li>\n     * <li>C-style single-line comments beginning with {@literal --}</li>\n     * <li>C-style multi-line comments beginning enclosed</li>\n     * </ul>\n     *\n     * @param needle the character to search for.\n     * @param sql    the SQL string to search in.\n     * @param offset the offset to start searching.\n     * @return the offset or {@literal -1} if not found.\n     */\n    @SuppressWarnings({\"fallthrough\"})\n    private static int findCharacter(char needle, CharSequence sql, int offset) {\n\n        char chQuote;\n        char character;\n        int length = sql.length();\n\n        while (offset < length && offset != -1) {\n\n            character = sql.charAt(offset++);\n            switch (character) {\n                case '/':\n                    if (offset == length) {\n                        break;\n                    }\n\n                    if (sql.charAt(offset) == '*') { // If '/* ... */' comment\n                        while (++offset < length) { // consume comment\n                            if (sql.charAt(offset) == '*' && offset + 1 < length && sql.charAt(offset + 1) == '/') { // If\n                                // end\n                                // of\n                                // comment\n                                offset += 2;\n                                break;\n                            }\n                        }\n                        break;\n                    }\n\n                    if (sql.charAt(offset) == '-') {\n                        break;\n                    }\n\n                    // Fall through - will fail next if and end up in default case\n                case '-':\n                    if (sql.charAt(offset) == '-') { // If '-- ... \\n' comment\n                        while (++offset < length) { // consume comment\n                            if (sql.charAt(offset) == '\\n' || sql.charAt(offset) == '\\r') { // If end of comment\n                                offset++;\n                                break;\n                            }\n                        }\n                        break;\n                    }\n                    // Fall through to test character\n                default:\n                    if (needle == character) {\n                        return offset - 1;\n                    }\n                    break;\n\n                case '[':\n                    character = ']';\n                case '\\'':\n                case '\"':\n                    chQuote = character;\n                    while (offset < length) {\n                        if (sql.charAt(offset++) == chQuote) {\n                            if (length == offset || sql.charAt(offset) != chQuote) {\n                                break;\n                            }\n\n                            ++offset;\n                        }\n                    }\n                    break;\n            }\n        }\n\n        return -1;\n    }\n\n    /**\n     * A parsed SQL query with its variable names.\n     */\n    static class ParsedQuery {\n\n        private final String sql;\n\n        private final List<ParsedParameter> parameters;\n\n        private final Map<String, ParsedParameter> parametersByName = new LinkedHashMap<>();\n\n        ParsedQuery(String sql, List<ParsedParameter> parameters) {\n\n            this.sql = sql;\n            this.parameters = parameters;\n\n            for (ParsedParameter parameter : parameters) {\n                this.parametersByName.put(parameter.getName(), parameter);\n            }\n        }\n\n        /**\n         * Parse the {@code sql} query and resolve variable parameters.\n         *\n         * @param sql the SQL query to parse.\n         * @return the parsed query.\n         * @throws IllegalArgumentException when {@code sql} is {@code null}.\n         */\n        static ParsedQuery parse(String sql) {\n\n            Assert.requireNonNull(sql, \"SQL must not be null\");\n\n            List<ParsedParameter> variables = new ArrayList<>();\n\n            int offset = 0;\n            while (offset != -1) {\n\n                offset = findCharacter('@', sql, offset);\n\n                if (offset != -1) {\n\n                    Matcher matcher = PARAMETER_MATCHER.matcher(sql.substring(offset));\n                    offset++;\n                    if (matcher.find()) {\n\n                        String name = matcher.group(1);\n                        variables.add(new ParsedParameter(name, offset));\n                    }\n                }\n            }\n\n            return new ParsedQuery(sql, variables);\n        }\n\n        /**\n         * Returns the  {@link ParsedParameter} name by {@code name}.\n         *\n         * @param name the parameter name.\n         * @return the {@link ParsedParameter} name.\n         */\n        String getParameterName(String name) {\n\n            ParsedParameter parsedParameter = this.parametersByName.get(name);\n\n            if (name.startsWith(\"@\")) {\n                parsedParameter = this.parametersByName.get(name.substring(1));\n            }\n\n            if (parsedParameter == null) {\n                throw new NoSuchElementException(String.format(\"Parameter [%s] does not exist in query [%s]\", name, this.sql));\n            }\n\n            return parsedParameter.getName();\n        }\n\n        /**\n         * Returns the parameter name at the positional {@code index}.\n         *\n         * @param index\n         * @return\n         */\n        public String getParameterName(int index) {\n\n            if (index < 0) {\n                throw new IndexOutOfBoundsException(\"Index must be greater or equal to zero\");\n            }\n\n            if (index >= getParameterCount()) {\n                throw new IndexOutOfBoundsException(String.format(\"No such parameter with index [%d]  in query [%s]\", index, this.sql));\n            }\n\n            return this.parameters.get(index).getName();\n        }\n\n        public String getSql() {\n            return this.sql;\n        }\n\n        /**\n         * @return the number of parameters.\n         */\n        public int getParameterCount() {\n            return this.parameters.size();\n        }\n\n        public List<ParsedParameter> getParameters() {\n            return this.parameters;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) {\n                return true;\n            }\n            if (!(o instanceof ParsedQuery)) {\n                return false;\n            }\n            ParsedQuery that = (ParsedQuery) o;\n            return Objects.equals(this.sql, that.sql) &&\n                Objects.equals(this.parameters, that.parameters);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(this.sql, this.parameters);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [sql='\").append(this.sql).append('\\'');\n            sb.append(\", variables=\").append(this.parameters);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * A SQL parameter within a SQL query.\n     */\n    static class ParsedParameter {\n\n        private final String name;\n\n        private final int position;\n\n        ParsedParameter(String name, int position) {\n            this.name = name;\n            this.position = position;\n        }\n\n        public String getName() {\n            return this.name;\n        }\n\n        public int getPosition() {\n            return this.position;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) {\n                return true;\n            }\n            if (!(o instanceof ParsedParameter)) {\n                return false;\n            }\n            ParsedParameter that = (ParsedParameter) o;\n            return this.position == that.position &&\n                Objects.equals(this.name, that.name);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(this.name, this.position);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [name='\").append(this.name).append('\\'');\n            sb.append(\", position=\").append(this.position);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    static final class Bindings {\n\n        private final List<Binding> bindings = new ArrayList<>();\n\n        private Binding current;\n\n        private void finish() {\n            this.current = null;\n        }\n\n        Binding first() {\n            return this.bindings.stream().findFirst().orElseThrow(() -> new IllegalStateException(\"No parameters have been bound\"));\n        }\n\n        Binding getCurrent() {\n            if (this.current == null) {\n                this.current = new Binding();\n                this.bindings.add(this.current);\n            }\n\n            return this.current;\n        }\n\n        /**\n         * Clear/release binding values.\n         */\n        void clear() {\n            this.bindings.forEach(Binding::clear);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/PreparedStatementCache.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport java.util.function.Function;\n\n/**\n * Cache for prepared statements.\n *\n * @author Mark Paluch\n */\ninterface PreparedStatementCache {\n\n    /**\n     * Marker for no prepared statement found/no prepared statement.\n     */\n    int UNPREPARED = 0;\n\n    /**\n     * Returns a prepared statement handle for the given {@code sql} query and the {@link Binding}.\n     *\n     * @param sql     the SQL query.\n     * @param binding bound parameters. Parameter types impact the prepared query.\n     * @return the prepared statement handle. {@value 0} has a specific meaning as it indicates that no cached SQL statement was found.\n     * @see #UNPREPARED\n     */\n    int getHandle(String sql, Binding binding);\n\n    /**\n     * Returns a prepared statement {@code handle} for the given {@code sql} query and the {@link Binding}.\n     *\n     * @param handle  the prepared statement handle.\n     * @param sql     the SQL query.\n     * @param binding bound parameters. Parameter types impact the prepared query.\n     */\n    void putHandle(int handle, String sql, Binding binding);\n\n    /**\n     * Returns the parsed and potentially cached representation of the {@code sql} statement.\n     *\n     * @param sql           query to parse.\n     * @param parseFunction parse function.\n     * @param <T>\n     * @return the parsed SQL representation.\n     */\n    <T> T getParsedSql(String sql, Function<String, T> parseFunction);\n\n    /**\n     * Returns the number of cached prepared statement handles in this cache.\n     *\n     * @return the number of prepared statement handles in this cache.\n     */\n    int size();\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/QueryLogger.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\n\n/**\n * Query logger to log queries.\n *\n * @author Mark Paluch\n */\nfinal class QueryLogger {\n\n    private static final Logger QUERY_LOGGER = Loggers.getLogger(\"io.r2dbc.mssql.QUERY\");\n\n    static void logQuery(ConnectionContext context, String query) {\n        QUERY_LOGGER.debug(context.getMessage(\"Executing query: {}\"), query);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/QueryMessageFlow.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.util.ReferenceCountUtil;\nimport io.netty.util.ReferenceCounted;\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.SqlBatch;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.Operators;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.publisher.SynchronousSink;\n\nimport java.util.function.BiConsumer;\n\n/**\n * Simple (direct) query message flow using {@link SqlBatch}.\n * <p>\n * Commands require deferred creation because {@link Client} can be used concurrently and we must fetch the latest state (e.g. {@link TransactionDescriptor}) to issue a command with the appropriate\n * state.\n *\n * @author Mark Paluch\n */\nfinal class QueryMessageFlow {\n\n    /**\n     * Execute a simple query using {@link SqlBatch}. Query execution terminates with a {@link DoneToken}.\n     *\n     * @param client the {@link Client} to exchange messages with.\n     * @param query  the query to execute.\n     * @return the messages received in response to this exchange.\n     */\n    static Flux<Message> exchange(Client client, String query) {\n\n        Assert.requireNonNull(client, \"Client must not be null\");\n        Assert.requireNonNull(query, \"Query must not be null\");\n\n        return client.exchange(Mono.fromSupplier(() -> SqlBatch.create(1, client.getTransactionDescriptor(), query)), DoneToken::isDone)\n            .doOnSubscribe(ignore -> QueryLogger.logQuery(client.getContext(), query))\n            .handle(DoneHandler.INSTANCE)\n            .transform(Operators::discardOnCancel).doOnDiscard(ReferenceCounted.class, ReferenceCountUtil::release);\n    }\n\n    enum DoneHandler implements BiConsumer<Message, SynchronousSink<Message>> {\n\n        INSTANCE;\n\n        @Override\n        public void accept(Message message, SynchronousSink<Message> sink) {\n            sink.next(message);\n\n            if (DoneToken.isDone(message)) {\n                sink.complete();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/RpcQueryMessageFlow.java",
    "content": "/*\n * Copyright 2018-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.util.ReferenceCountUtil;\nimport io.netty.util.ReferenceCounted;\nimport io.r2dbc.mssql.RpcQueryMessageFlow.CursorState.Phase;\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.token.*;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.Operators;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.publisher.Sinks;\nimport reactor.core.publisher.SynchronousSink;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\n\nimport javax.annotation.processing.Completion;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicIntegerFieldUpdater;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\nimport static io.r2dbc.mssql.util.PredicateUtils.or;\n\n/**\n * Query message flow using cursors. The cursored query message flow uses {@link RpcRequest RPC} calls to open, fetch and close cursors.\n * <p>\n * Commands require deferred creation because {@link Client} can be used concurrently and we must fetch the latest state (e.g. {@link TransactionDescriptor}) to issue a command with the appropriate\n * state.\n *\n * @author Mark Paluch\n * @see RpcRequest\n */\nfinal class RpcQueryMessageFlow {\n\n    private static final Predicate<Message> FILTER_PREDICATE = or(RowToken.class::isInstance,\n        ColumnMetadataToken.class::isInstance,\n        ReturnValue.class::isInstance,\n        DoneInProcToken.class::isInstance,\n        IntermediateCount.class::isInstance,\n        AbstractInfoToken.class::isInstance,\n        Completion.class::isInstance,\n        AbstractDoneToken::isAttentionAck);\n\n    private static final Logger logger = Loggers.getLogger(RpcQueryMessageFlow.class);\n\n    static final RpcRequest.OptionFlags NO_METADATA = RpcRequest.OptionFlags.empty().disableMetadata();\n\n    // Constants for server-cursored result sets.\n    // See the Engine Cursors Functional Specification for details.\n    static final int FETCH_FIRST = 1;\n\n    static final int FETCH_NEXT = 2;\n\n    static final int FETCH_PREV = 4;\n\n    static final int FETCH_LAST = 8;\n\n    static final int FETCH_ABSOLUTE = 16;\n\n    static final int FETCH_RELATIVE = 32;\n\n    static final int FETCH_REFRESH = 128;\n\n    static final int FETCH_INFO = 256;\n\n    static final int FETCH_PREV_NOADJUST = 512;\n\n    // Scroll options and concurrency options lifted out\n    // of the the Yukon cursors spec for sp_cursoropen.\n    final static int SCROLLOPT_KEYSET = 1;\n\n    final static int SCROLLOPT_DYNAMIC = 2;\n\n    final static int SCROLLOPT_FORWARD_ONLY = 4;\n\n    final static int SCROLLOPT_STATIC = 8;\n\n    final static int SCROLLOPT_FAST_FORWARD = 16;\n\n    static final int SCROLLOPT_PARAMETERIZED_STMT = 4096;\n\n    static final int CCOPT_READ_ONLY = 1;\n\n    static final int CCOPT_ALLOW_DIRECT = 8192;\n\n    /**\n     * Execute a direct query with parameters.\n     *\n     * @param client the {@link Client} to exchange messages with.\n     * @param query  the query to execute.\n     * @return the messages received in response to this exchange.\n     */\n    static Flux<Message> exchange(Client client, String query, Binding binding) {\n\n        Assert.requireNonNull(client, \"Client must not be null\");\n        Assert.requireNonNull(query, \"Query must not be null\");\n\n\n        CursorState state = new CursorState();\n        state.directMode = true;\n\n        Flux<Message> exchange = client.exchange(Mono.fromSupplier(() -> spExecuteSql(query, binding, client.getRequiredCollation(), client.getTransactionDescriptor())), DoneProcToken::isDone);\n        OnCursorComplete cursorComplete = new OnCursorComplete();\n\n        Flux<Message> messages = exchange //\n            .<Message>handle((message, sink) -> {\n\n                state.update(message);\n\n                handleMessage(client, 0, state, message, sink, cursorComplete, true);\n            })\n            .filter(FILTER_PREDICATE)\n            .doOnCancel(cursorComplete);\n\n        return messages.doOnSubscribe(subscription -> {\n            QueryLogger.logQuery(client.getContext(), query);\n        })\n            .transform(it -> Operators.discardOnCancel(it, state::cancel).doOnDiscard(ReferenceCounted.class, ReferenceCountUtil::release)).takeUntilOther(cursorComplete.takeUntil());\n    }\n\n    /**\n     * Execute a cursored query.\n     *\n     * @param client    the {@link Client} to exchange messages with.\n     * @param codecs    the codecs to decode {@link ReturnValue}s from RPC calls.\n     * @param query     the query to execute.\n     * @param fetchSize the number of rows to fetch. TODO: Try to determine fetch size from current demand and apply demand function.\n     * @return the messages received in response to this exchange.\n     */\n    static Flux<Message> exchange(Client client, Codecs codecs, String query, int fetchSize) {\n\n        Assert.requireNonNull(client, \"Client must not be null\");\n        Assert.requireNonNull(query, \"Query must not be null\");\n\n        Sinks.Many<ClientMessage> outbound = Sinks.many().unicast().onBackpressureBuffer();\n\n        CursorState state = new CursorState();\n\n        Flux<Message> exchange = client.exchange(Flux.defer(() -> {\n            outbound.emitNext(spCursorOpen(query, client.getRequiredCollation(), client.getTransactionDescriptor()), Sinks.EmitFailureHandler.FAIL_FAST);\n            return outbound.asFlux();\n        }), isFinalToken(state));\n\n        OnCursorComplete cursorComplete = new OnCursorComplete();\n\n        Flux<Message> messages = exchange //\n            .<Message>handle((message, sink) -> {\n\n                boolean emit = true;\n                if (message.getClass() == ReturnValue.class) {\n\n                    ReturnValue returnValue = (ReturnValue) message;\n\n                    // cursor Id\n                    if (returnValue.getOrdinal() == 0) {\n                        state.cursorId = parseCursorId(codecs, state, returnValue);\n                    }\n\n                    // skip spCursorOpen OUT\n                    if (returnValue.getOrdinal() < 5) {\n                        returnValue.release();\n                        emit = false;\n                    }\n                }\n\n                state.update(message);\n\n                handleMessage(client, fetchSize, outbound, state, message, sink, cursorComplete, emit);\n            })\n            .filter(FILTER_PREDICATE);\n\n        return messages.doOnSubscribe(subscription -> {\n            QueryLogger.logQuery(client.getContext(), query);\n        })\n            .transform(it -> Operators.discardOnCancel(it, state::cancel).doOnDiscard(ReferenceCounted.class, ReferenceCountUtil::release)).takeUntilOther(cursorComplete.takeUntil());\n    }\n\n    /**\n     * Execute a cursored query with RPC parameters.\n     *\n     * @param statementCache the {@link PreparedStatementCache} to keep track of prepared statement handles.\n     * @param client         the {@link Client} to exchange messages with.\n     * @param codecs         the codecs to decode {@link ReturnValue}s from RPC calls.\n     * @param query          the query to execute.\n     * @param binding        parameter bindings.\n     * @param fetchSize      the number of rows to fetch. TODO: Try to determine fetch size from current demand and apply demand function.\n     * @return the messages received in response to this exchange.\n     * @throws IllegalArgumentException when {@link Client} or {@code query} is {@code null}.\n     */\n    static Flux<Message> exchange(PreparedStatementCache statementCache, Client client, Codecs codecs, String query, Binding binding, int fetchSize) {\n\n        Assert.requireNonNull(client, \"Client must not be null\");\n        Assert.requireNonNull(query, \"Query must not be null\");\n\n        Sinks.Many<ClientMessage> outbound = Sinks.many().unicast().onBackpressureBuffer();\n        int handle = statementCache.getHandle(query, binding);\n\n        AtomicBoolean retryReprepare = new AtomicBoolean(true);\n        AtomicBoolean needsPrepare = new AtomicBoolean(false);\n\n        Flux<ClientMessage> messageProducer;\n\n        if (handle == PreparedStatementCache.UNPREPARED) {\n            messageProducer = Flux.defer(() -> {\n                outbound.emitNext(spCursorPrepExec(PreparedStatementCache.UNPREPARED, query, binding, client.getRequiredCollation(),\n                        client.getTransactionDescriptor()), Sinks.EmitFailureHandler.FAIL_FAST);\n                return outbound.asFlux();\n            });\n\n            needsPrepare.set(true);\n        } else {\n            messageProducer = Flux.defer(() -> {\n                outbound.emitNext(spCursorExec(handle, binding, client.getTransactionDescriptor()), Sinks.EmitFailureHandler.FAIL_FAST);\n                return outbound.asFlux();\n            });\n            needsPrepare.set(false);\n        }\n\n        CursorState state = new CursorState();\n        Flux<Message> exchange = client.exchange(messageProducer, isFinalToken(state));\n        OnCursorComplete cursorComplete = new OnCursorComplete();\n\n        Flux<Message> messages = exchange //\n            .<Message>handle((message, sink) -> {\n\n                boolean emit = true;\n                if (message.getClass() == ReturnValue.class) {\n\n                    ReturnValue returnValue = (ReturnValue) message;\n\n                    emit = handleSpCursorReturnValue(statementCache, codecs, query, binding, state, needsPrepare.get(), returnValue);\n\n                    if (!emit) {\n                        returnValue.release();\n                    }\n                }\n\n                state.update(message);\n\n                if (message instanceof ErrorToken) {\n                    if (isPreparedStatementNotFound(((ErrorToken) message).getNumber()) && retryReprepare.compareAndSet(true, false)) {\n                        logger.debug(\"Prepared statement no longer valid: {}\", handle);\n                        state.update(Phase.PREPARE_RETRY);\n                    }\n                }\n\n                if (state.phase == Phase.PREPARE_RETRY) {\n                    emit = false;\n                }\n\n                if (DoneProcToken.isDone(message) && state.phase == Phase.PREPARE_RETRY) {\n\n                    logger.debug(\"Attempting to re-prepare statement: {}\", query);\n                    needsPrepare.set(true);\n                    state.update(Phase.NONE);\n                    outbound.emitNext(spCursorPrepExec(PreparedStatementCache.UNPREPARED, query, binding, client.getRequiredCollation(),\n                            client.getTransactionDescriptor()), Sinks.EmitFailureHandler.FAIL_FAST);\n                    return;\n                }\n\n                handleMessage(client, fetchSize, outbound, state, message, sink, cursorComplete, emit);\n            })\n            .filter(FILTER_PREDICATE);\n\n        return messages.doOnSubscribe(subscription -> {\n            QueryLogger.logQuery(client.getContext(), query);\n        })\n            .transform(it -> Operators.discardOnCancel(it, state::cancel).doOnDiscard(ReferenceCounted.class, ReferenceCountUtil::release)).takeUntilOther(cursorComplete.takeUntil());\n    }\n\n    /**\n     * Check whether the error indicates a prepared statement requiring reprepare.\n     * <p>\n     * <ul><li>586: The prepared statement handle %d is not valid in this context. Please verify that current database, user\n     * default schema ANSI_NULLS and QUOTED_IDENTIFIER set options are not changed since the handle is prepared.</li>\n     * <li>8179: Could not find prepared statement with handle %d.</li>\n     * </ul>\n     *\n     * @param errorNumber\n     * @return\n     */\n    private static boolean isPreparedStatementNotFound(long errorNumber) {\n        return errorNumber == 8179 || errorNumber == 586;\n    }\n\n    private static boolean handleSpCursorReturnValue(PreparedStatementCache statementCache, Codecs codecs, String query, Binding binding, CursorState state, boolean needsPrepare,\n                                                     ReturnValue returnValue) {\n\n        // cursor Id\n        if (returnValue.getOrdinal() == 1) {\n            state.cursorId = parseCursorId(codecs, state, returnValue);\n        }\n\n        if (needsPrepare) {\n\n            // prepared statement handle\n            if (returnValue.getOrdinal() == 0) {\n\n                int preparedStatementHandle = codecs.decode(returnValue.getValue(), returnValue.asDecodable(), Integer.class);\n                logger.debug(\"Prepared statement with handle: {}\", preparedStatementHandle);\n                statementCache.putHandle(preparedStatementHandle, query, binding);\n            }\n\n            // skip spCursorPrepExec OUT\n            return returnValue.getOrdinal() >= 7;\n        } else {\n            // skip spCursorExec OUT\n            return returnValue.getOrdinal() >= 5;\n        }\n    }\n\n    private static int parseCursorId(Codecs codecs, CursorState state, ReturnValue returnValue) {\n\n        Integer cursorId = codecs.decode(returnValue.getValue(), returnValue.asDecodable(), Integer.class);\n        logger.debug(\"CursorId: {}\", cursorId);\n        return cursorId;\n    }\n\n    private static void handleMessage(Client client, int fetchSize, CursorState state, Message message, SynchronousSink<Message> sink,\n                                      Runnable onCursorComplete, boolean emit) {\n        handleMessage(client, fetchSize, it -> {\n            throw new UnsupportedOperationException(\"Cannot accept subsequent messages\");\n        }, state, message, sink, onCursorComplete, emit);\n    }\n\n    private static void handleMessage(Client client, int fetchSize, Sinks.Many<ClientMessage> requests, CursorState state, Message message, SynchronousSink<Message> sink,\n                                      Runnable onCursorComplete, boolean emit) {\n        handleMessage(client, fetchSize, t -> requests.emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST), state, message, sink, onCursorComplete, emit);\n    }\n\n    private static void handleMessage(Client client, int fetchSize, Consumer<ClientMessage> requests, CursorState state, Message message, SynchronousSink<Message> sink,\n                                      Runnable onCursorComplete, boolean emit) {\n\n        if (message instanceof ColumnMetadataToken && !((ColumnMetadataToken) message).hasColumns()) {\n            return;\n        }\n\n        if (message instanceof AbstractInfoToken) {\n\n            // direct mode\n            if (((AbstractInfoToken) message).getNumber() == 16954) {\n                state.directMode = true;\n            }\n        }\n\n        if (message instanceof DoneInProcToken) {\n\n            DoneInProcToken doneToken = (DoneInProcToken) message;\n            state.hasMore = doneToken.hasMore();\n\n            if (!state.directMode) {\n\n                if (state.phase == Phase.FETCHING && doneToken.hasCount()) {\n                    sink.next(new IntermediateCount(doneToken));\n                }\n                return;\n            }\n\n            sink.next(doneToken);\n            return;\n        }\n\n        if (AbstractDoneToken.isAttentionAck(message)) {\n\n            state.update(Phase.CLOSED);\n            sink.next(message);\n            return;\n        }\n\n        if (!(message instanceof DoneProcToken)) {\n\n            if (emit) {\n                sink.next(message);\n            }\n            return;\n        }\n\n        if (state.hasSeenError) {\n            state.update(Phase.ERROR);\n        }\n\n        if (DoneProcToken.isDone(message)) {\n            onDone(client, fetchSize, requests, state, onCursorComplete);\n        }\n    }\n\n    static void onDone(Client client, int fetchSize, Consumer<ClientMessage> requests, CursorState state, Runnable completion) {\n\n        Phase phase = state.phase;\n\n        if (isFinalState(state)) {\n\n            completion.run();\n\n            state.update(Phase.CLOSED);\n            return;\n        }\n\n        if (phase == Phase.NONE || phase == Phase.FETCHING) {\n\n            if (((state.hasMore && phase == Phase.NONE) || state.hasSeenRows) && state.wantsMore()) {\n                if (phase == Phase.NONE) {\n                    state.update(Phase.FETCHING);\n                }\n                requests.accept(spCursorFetch(state.cursorId, FETCH_NEXT, fetchSize, client.getTransactionDescriptor()));\n            } else {\n                state.update(Phase.CLOSING);\n                // TODO: spCursorClose should happen also if a subscriber cancels its subscription.\n                requests.accept(spCursorClose(state.cursorId, client.getTransactionDescriptor()));\n            }\n\n            state.hasSeenRows = false;\n        }\n    }\n\n    private static Predicate<Message> isFinalToken(CursorState state) {\n\n        return message -> {\n\n            if (!DoneProcToken.isDone(message)) {\n                return false;\n            }\n\n            return isFinalState(state);\n        };\n    }\n\n    private static boolean isFinalState(CursorState state) {\n\n        Phase phase = state.phase;\n\n        if (phase == Phase.NONE || phase == Phase.FETCHING) {\n\n            if (state.cursorId == 0) {\n                return true;\n            }\n        }\n\n        return phase == Phase.ERROR || phase == Phase.CLOSING || phase == Phase.CLOSED;\n    }\n\n    /**\n     * Creates a {@link RpcRequest} for {@link RpcRequest#Sp_ExecuteSql} to execute a SQL statement that returns directly results.\n     *\n     * @param query                 the query to execute.\n     * @param binding               bound parameters\n     * @param collation             the database collation.\n     * @param transactionDescriptor transaction descriptor.\n     * @return {@link RpcRequest} for {@link RpcRequest#Sp_CursorOpen}.\n     * @throws IllegalArgumentException when {@code query}, {@link Collation}, or {@link TransactionDescriptor} is {@code null}.\n     */\n    static RpcRequest spExecuteSql(String query, Binding binding, Collation collation, TransactionDescriptor transactionDescriptor) {\n\n        Assert.requireNonNull(query, \"Query must not be null\");\n        Assert.requireNonNull(collation, \"Collation must not be null\");\n        Assert.requireNonNull(transactionDescriptor, \"TransactionDescriptor must not be null\");\n\n        RpcRequest.Builder builder = RpcRequest.builder() //\n            .withProcId(RpcRequest.Sp_ExecuteSql) //\n            .withTransactionDescriptor(transactionDescriptor) //\n            .withParameter(RpcDirection.IN, collation, query) //\n            .withParameter(RpcDirection.IN, collation, binding.getFormalParameters()); // formal parameter defn\n\n        binding.forEach((name, parameter) -> {\n            builder.withNamedParameter(parameter.rpcDirection, name, parameter.encoded);\n        });\n\n        return builder.build();\n    }\n\n    /**\n     * Creates a {@link RpcRequest} for {@link RpcRequest#Sp_CursorOpen} to execute a SQL statement that returns a cursor.\n     *\n     * @param query                 the query to execute.\n     * @param collation             the database collation.\n     * @param transactionDescriptor transaction descriptor.\n     * @return {@link RpcRequest} for {@link RpcRequest#Sp_CursorOpen}.\n     * @throws IllegalArgumentException when {@code query}, {@link Collation}, or {@link TransactionDescriptor} is {@code null}.\n     */\n    static RpcRequest spCursorOpen(String query, Collation collation, TransactionDescriptor transactionDescriptor) {\n\n        Assert.requireNonNull(query, \"Query must not be null\");\n        Assert.requireNonNull(collation, \"Collation must not be null\");\n        Assert.requireNonNull(transactionDescriptor, \"TransactionDescriptor must not be null\");\n\n        int resultSetScrollOpt = SCROLLOPT_FORWARD_ONLY;\n        int resultSetCCOpt = CCOPT_READ_ONLY | CCOPT_ALLOW_DIRECT;\n\n        return RpcRequest.builder() //\n            .withProcId(RpcRequest.Sp_CursorOpen) //\n            .withTransactionDescriptor(transactionDescriptor) //\n            .withParameter(RpcDirection.OUT, 0) // cursor\n            .withParameter(RpcDirection.IN, collation, query)\n            .withParameter(RpcDirection.IN, resultSetScrollOpt)  // scrollopt\n            .withParameter(RpcDirection.IN, resultSetCCOpt) // ccopt\n            .withParameter(RpcDirection.OUT, 0) // rowcount\n            .build();\n    }\n\n    /**\n     * Creates a {@link RpcRequest} for {@link RpcRequest#Sp_CursorFetch} to fetch {@code rowCount} from the given {@literal cursor}.\n     *\n     * @param cursor                the cursor Id.\n     * @param fetchType             the type of fetch operation (first, next, …).\n     * @param rowCount              number of rows to fetch\n     * @param transactionDescriptor transaction descriptor.\n     * @return {@link RpcRequest} for {@link RpcRequest#Sp_CursorFetch}.\n     * @throws IllegalArgumentException when {@link TransactionDescriptor} is {@code null}.\n     * @throws IllegalArgumentException when {@code rowCount} is less than zero.\n     */\n    static RpcRequest spCursorFetch(int cursor, int fetchType, int rowCount, TransactionDescriptor transactionDescriptor) {\n\n        Assert.isTrue(rowCount >= 0, \"Row count must be greater or equal to zero\");\n        Assert.requireNonNull(transactionDescriptor, \"TransactionDescriptor must not be null\");\n\n        return RpcRequest.builder() //\n            .withProcId(RpcRequest.Sp_CursorFetch) //\n            .withTransactionDescriptor(transactionDescriptor) //\n            .withOptionFlags(NO_METADATA) //\n            .withParameter(RpcDirection.IN, cursor) // cursor\n            .withParameter(RpcDirection.IN, fetchType) // fetch type\n            .withParameter(RpcDirection.IN, 0)  // startRow\n            .withParameter(RpcDirection.IN, rowCount) // numRows\n            .build();\n    }\n\n    /**\n     * Creates a {@link RpcRequest} for {@link RpcRequest#Sp_CursorClose} release server resources.\n     *\n     * @param cursor                the cursor Id.\n     * @param transactionDescriptor transaction descriptor.\n     * @return {@link RpcRequest} for {@link RpcRequest#Sp_CursorFetch}.\n     * @throws IllegalArgumentException when {@link TransactionDescriptor} is {@code null}.\n     */\n    static RpcRequest spCursorClose(int cursor, TransactionDescriptor transactionDescriptor) {\n\n        Assert.requireNonNull(transactionDescriptor, \"TransactionDescriptor must not be null\");\n\n        return RpcRequest.builder() //\n            .withProcId(RpcRequest.Sp_CursorClose) //\n            .withTransactionDescriptor(transactionDescriptor) //\n            .withParameter(RpcDirection.IN, cursor) // cursor\n            .build();\n    }\n\n    /**\n     * Creates a {@link RpcRequest} for {@link RpcRequest#Sp_CursorPrepare} to prepare and execute a {@code query}.\n     *\n     * @param preparedStatementHandle handle to a previously prepared statement. This call un-prepares a previously prepared statement.\n     * @param query                   the query to execute.\n     * @param binding                 bound parameters\n     * @param collation               the database collation.\n     * @param transactionDescriptor   transaction descriptor.\n     * @return {@link RpcRequest} for {@link RpcRequest#Sp_CursorFetch}.\n     */\n    static RpcRequest spCursorPrepExec(int preparedStatementHandle, String query, Binding binding, Collation collation, TransactionDescriptor transactionDescriptor) {\n\n        int resultSetScrollOpt = SCROLLOPT_FORWARD_ONLY | (binding.isEmpty() ? 0 : SCROLLOPT_PARAMETERIZED_STMT);\n        int resultSetCCOpt = CCOPT_READ_ONLY | CCOPT_ALLOW_DIRECT;\n\n        RpcRequest.Builder builder = RpcRequest.builder() //\n            .withProcId(RpcRequest.Sp_CursorPrepExec) //\n            .withTransactionDescriptor(transactionDescriptor) //\n\n            // <prepared handle>\n            // IN (reprepare): Old handle to unprepare before repreparing\n            // OUT: The newly prepared handle\n            .withParameter(RpcDirection.OUT, preparedStatementHandle)\n            .withParameter(RpcDirection.OUT, 0) // cursor\n            .withParameter(RpcDirection.IN, collation, binding.getFormalParameters()) // formal parameter defn\n            .withParameter(RpcDirection.IN, collation, query) // statement\n            .withParameter(RpcDirection.IN, resultSetScrollOpt) // scrollopt\n            .withParameter(RpcDirection.IN, resultSetCCOpt) // ccopt\n            .withParameter(RpcDirection.OUT, 0);// rowcount\n\n        binding.forEach((name, parameter) -> {\n            builder.withNamedParameter(parameter.rpcDirection, name, parameter.encoded);\n        });\n\n        return builder.build();\n    }\n\n    /**\n     * Creates a {@link RpcRequest} for {@link RpcRequest#Sp_CursorExecute} to and execute prepared statement.\n     *\n     * @param preparedStatementHandle handle to a previously prepared statement.\n     * @param binding                 bound parameters\n     * @param transactionDescriptor   transaction descriptor.\n     * @return {@link RpcRequest} for {@link RpcRequest#Sp_CursorFetch}.\n     */\n    static RpcRequest spCursorExec(int preparedStatementHandle, Binding binding, TransactionDescriptor transactionDescriptor) {\n\n        Assert.isTrue(preparedStatementHandle != PreparedStatementCache.UNPREPARED, \"Invalid PreparedStatement handle\");\n\n        int resultSetScrollOpt = SCROLLOPT_FORWARD_ONLY;\n        int resultSetCCOpt = CCOPT_READ_ONLY | CCOPT_ALLOW_DIRECT;\n\n        RpcRequest.Builder builder = RpcRequest.builder() //\n            .withProcId(RpcRequest.Sp_CursorExecute) //\n            .withTransactionDescriptor(transactionDescriptor) //\n\n            // <prepared handle>\n            // IN (reprepare): Old handle to unprepare before repreparing\n            // OUT: The newly prepared handle\n            .withParameter(RpcDirection.IN, preparedStatementHandle)\n            .withParameter(RpcDirection.OUT, 0) // cursor\n            .withParameter(RpcDirection.IN, resultSetScrollOpt) // scrollopt\n            .withParameter(RpcDirection.IN, resultSetCCOpt) // ccopt\n            .withParameter(RpcDirection.OUT, 0);// rowcount\n\n        binding.forEach((name, parameter) -> {\n            builder.withNamedParameter(parameter.rpcDirection, name, parameter.encoded);\n        });\n\n        return builder.build();\n    }\n\n    /**\n     * Cursoring state.\n     */\n    static class CursorState {\n\n        volatile int cursorId;\n\n        // hasMore flag from the DoneInProc token\n        volatile boolean hasMore;\n\n        // hasMore typically reports true, but we need to check whether we've seen rows to determine whether to end cursoring.\n        volatile boolean hasSeenRows;\n\n        volatile boolean hasSeenError;\n\n        volatile boolean directMode;\n\n        volatile boolean cancelRequested;\n\n        volatile ErrorToken errorToken;\n\n        Phase phase = Phase.NONE;\n\n        boolean wantsMore() {\n            return !this.cancelRequested;\n        }\n\n        void cancel() {\n            this.cancelRequested = true;\n        }\n\n        void update(Message it) {\n            if (it instanceof RowToken) {\n                this.hasSeenRows = true;\n            }\n\n            if (it instanceof ErrorToken) {\n                this.errorToken = (ErrorToken) it;\n                this.hasSeenError = true;\n            }\n        }\n\n        public void update(Phase newPhase) {\n\n            this.phase = newPhase;\n\n            if (newPhase == Phase.PREPARE_RETRY) {\n                errorToken = null;\n                hasSeenError = false;\n            }\n        }\n\n        enum Phase {\n            NONE, FETCHING, PREPARE_RETRY, CLOSING, CLOSED, ERROR\n        }\n\n    }\n\n    static class IntermediateCount extends AbstractDoneToken {\n\n        public IntermediateCount(DoneInProcToken token) {\n            super(token.getType(), token.getStatus(), token.getCurrentCommand(), token.getRowCount());\n        }\n\n        @Override\n        public String getName() {\n            return \"INTERMEDIATE_COUNT\";\n        }\n\n    }\n\n    static class OnCursorComplete implements Runnable {\n\n        private static final int STATE_ACTIVE = 0;\n\n        private static final int STATE_CANCELLED = 1;\n\n        private static final AtomicIntegerFieldUpdater<OnCursorComplete> STATE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(OnCursorComplete.class, \"state\");\n\n        private final Sinks.Empty<Void> trigger = Sinks.empty();\n\n        private volatile int state = STATE_ACTIVE;\n\n        @Override\n        public void run() {\n\n            if (STATE_UPDATER.compareAndSet(this, STATE_ACTIVE, STATE_CANCELLED)) {\n                this.trigger.tryEmitEmpty();\n            }\n        }\n\n        public Publisher<Void> takeUntil() {\n            return this.trigger.asMono();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/SimpleMssqlStatement.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.token.AbstractDoneToken;\nimport io.r2dbc.mssql.message.token.DoneInProcToken;\nimport io.r2dbc.mssql.message.token.SqlBatch;\nimport io.r2dbc.mssql.util.Assert;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\n\nimport java.util.function.Predicate;\n\n/**\n * Simple SQL statement without SQL parameter (variables) using direct ({@link SqlBatch}) execution.\n *\n * @author Mark Paluch\n */\nfinal class SimpleMssqlStatement extends MssqlStatementSupport implements MssqlStatement {\n\n    private static final Logger logger = Loggers.getLogger(SimpleMssqlStatement.class);\n\n    private final Client client;\n\n    private final Codecs codecs;\n\n    private final ConnectionOptions connectionOptions;\n\n    private final ConnectionContext context;\n\n    private final String sql;\n\n    /**\n     * Creates a new {@link SimpleMssqlStatement}.\n     *\n     * @param client            the client to exchange messages with.\n     * @param connectionOptions the connection options.\n     * @param sql               the query to execute.\n     * @throws IllegalArgumentException when {@link Client}, {@link ConnectionOptions}, or {@code sql} is {@code null}.\n     */\n    SimpleMssqlStatement(Client client, ConnectionOptions connectionOptions, String sql) {\n\n        super(connectionOptions.prefersCursors(sql));\n        this.connectionOptions = connectionOptions;\n\n        Assert.requireNonNull(client, \"Client must not be null\");\n        Assert.requireNonNull(connectionOptions, \"ConnectionOptions must not be null\");\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n\n        Assert.isTrue(sql.trim().length() > 0, \"SQL must contain text\");\n\n        this.client = client;\n        this.context = client.getContext();\n        this.codecs = connectionOptions.getCodecs();\n        this.sql = sql;\n    }\n\n    @Override\n    public SimpleMssqlStatement add() {\n        return this;\n    }\n\n    @Override\n    public SimpleMssqlStatement bind(String identifier, Object value) {\n        throw new UnsupportedOperationException(\n            String.format(\"Binding parameters is not supported for the statement [%s]\", this.sql));\n    }\n\n    @Override\n    public SimpleMssqlStatement bind(int index, Object value) {\n        throw new UnsupportedOperationException(\n            String.format(\"Binding parameters is not supported for the statement [%s]\", this.sql));\n    }\n\n    @Override\n    public SimpleMssqlStatement bindNull(String identifier, Class<?> type) {\n        throw new UnsupportedOperationException(\n            String.format(\"Binding parameters is not supported for the statement [%s]\", this.sql));\n    }\n\n    @Override\n    public SimpleMssqlStatement bindNull(int index, Class<?> type) {\n        throw new UnsupportedOperationException(\n            String.format(\"Binding parameters is not supported for the statement [%s]\", this.sql));\n    }\n\n    @Override\n    public Flux<MssqlResult> execute() {\n\n        int effectiveFetchSize = getEffectiveFetchSize();\n\n        return Flux.defer(() -> {\n\n            boolean useGeneratedKeysClause = GeneratedValues.shouldExpectGeneratedKeys(this.getGeneratedColumns());\n            String sql = useGeneratedKeysClause ? GeneratedValues.augmentQuery(this.sql, getGeneratedColumns()) : this.sql;\n\n            Flux<Message> exchange;\n\n            if (effectiveFetchSize > 0) {\n\n                if (logger.isDebugEnabled()) {\n                    logger.debug(this.context.getMessage(\"Start cursored exchange for {} with fetch size {}\"), sql, effectiveFetchSize);\n                }\n\n                exchange = potentiallyAttachTimeout(RpcQueryMessageFlow.exchange(this.client, this.codecs, this.sql, effectiveFetchSize), this.connectionOptions, this.client, this.sql);\n\n                return createResultStream(useGeneratedKeysClause, exchange, DoneInProcToken.class::isInstance);\n            } else {\n\n                if (logger.isDebugEnabled()) {\n                    logger.debug(this.context.getMessage(\"Start direct exchange for {}\"), sql);\n                }\n\n                exchange = potentiallyAttachTimeout(QueryMessageFlow.exchange(this.client, sql), this.connectionOptions, this.client, this.sql);\n\n                return createResultStream(useGeneratedKeysClause, exchange, AbstractDoneToken.class::isInstance);\n            }\n        });\n    }\n\n    private Publisher<MssqlResult> createResultStream(boolean useGeneratedKeysClause, Flux<Message> exchange, Predicate<Message> windowUntil) {\n        if (useGeneratedKeysClause) {\n            exchange = exchange.transform(GeneratedValues::reduceToSingleCountDoneToken);\n        }\n\n        return exchange.windowUntil(windowUntil) //\n            .map(it -> DefaultMssqlResult.toResult(this.sql, this.context, this.codecs, it, false));\n    }\n\n    @Override\n    public SimpleMssqlStatement returnGeneratedValues(String... columns) {\n\n        super.returnGeneratedValues(columns);\n        return this;\n    }\n\n    @Override\n    public SimpleMssqlStatement fetchSize(int fetchSize) {\n\n        super.fetchSize(fetchSize);\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/api/MssqlTransactionDefinition.java",
    "content": "/*\n * Copyright 2021-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.api;\n\nimport io.r2dbc.spi.IsolationLevel;\nimport io.r2dbc.spi.Option;\nimport io.r2dbc.spi.TransactionDefinition;\n\nimport java.time.Duration;\n\n/**\n * {@link TransactionDefinition} for a SQL Server database.\n *\n * @author Mark Paluch\n * @since 0.9\n */\npublic interface MssqlTransactionDefinition extends TransactionDefinition {\n\n    /**\n     * The {@code WITH MARK} description is a string that marks the transaction in the transaction log and being stored in the {@code msdb.dbo.logmarkhistory} table.\n     */\n    Option<String> MARK = Option.valueOf(\"mark\");\n\n    /**\n     * Creates a {@link MssqlTransactionDefinition} given {@link IsolationLevel}.\n     *\n     * @param isolationLevel the isolation level to use during the transaction.\n     * @return a new {@link MssqlTransactionDefinition} using {@link IsolationLevel}.\n     * @throws IllegalArgumentException if {@code isolationLevel} is {@code null}.\n     */\n    static MssqlTransactionDefinition from(IsolationLevel isolationLevel) {\n        return SimpleTransactionDefinition.EMPTY.isolationLevel(isolationLevel);\n    }\n\n    /**\n     * Creates a {@link MssqlTransactionDefinition} specifying the transaction name.\n     *\n     * @param name the transaction name. Must not exceed 32 characters. The name is always case sensitive, even when the instance of SQL Server is not case sensitive\n     * @return a new {@link MssqlTransactionDefinition} using transaction {@code name}.\n     */\n    static MssqlTransactionDefinition named(String name) {\n        return SimpleTransactionDefinition.EMPTY.name(name);\n    }\n\n    /**\n     * Creates a {@link MssqlTransactionDefinition} retaining all configured options and applying {@link IsolationLevel}.\n     *\n     * @param isolationLevel the isolation level to use during the transaction.\n     * @return a new {@link MssqlTransactionDefinition} retaining all configured options and applying {@link IsolationLevel}.\n     * @throws IllegalArgumentException if {@code isolationLevel} is {@code null}.\n     */\n    MssqlTransactionDefinition isolationLevel(IsolationLevel isolationLevel);\n\n    /**\n     * Creates a {@link MssqlTransactionDefinition} retaining all configured options and applying {@link Duration lock timeout}.\n     *\n     * @param timeout the lock timeout.\n     * @return a new {@link MssqlTransactionDefinition} retaining all configured options and applying {@link Duration lock timeout}.\n     * @throws IllegalArgumentException if {@code timeout} is {@code null}.\n     */\n    MssqlTransactionDefinition lockTimeout(Duration timeout);\n\n    /**\n     * Creates a {@link MssqlTransactionDefinition} retaining all configured options and using the given transaction {@code name}.\n     *\n     * @param name the transaction name. Must not exceed 32 characters. The name is always case sensitive, even when the instance of SQL Server is not case sensitive\n     * @return a new {@link MssqlTransactionDefinition} retaining all configured options and using the given transaction {@code name}.\n     * @throws IllegalArgumentException if {@code name} is {@code null}.\n     */\n    MssqlTransactionDefinition name(String name);\n\n    /**\n     * Creates a {@link MssqlTransactionDefinition} retaining all configured options and using the given transaction {@code mark}.\n     * Specifies that the transaction is marked in the log. This method updates the transaction name to {@code mark} if no name was set.\n     * <p>\n     * If {@code WITH MARK} is used, a transaction name must be specified. {@code WITH MARK} allows for restoring a transaction log to a named mark.\n     *\n     * @param mark describes the mark. A description longer than 128 characters is truncated\n     *             to 128 characters before being stored in the {@code msdb.dbo.logmarkhistory} table.\n     * @return a new {@link MssqlTransactionDefinition} retaining all configured options and using the given transaction {@code mark}.\n     * @throws IllegalArgumentException if {@code mark} is {@code null}.\n     */\n    MssqlTransactionDefinition mark(String mark);\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/api/SimpleTransactionDefinition.java",
    "content": "/*\n * Copyright 2021-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.api;\n\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.IsolationLevel;\nimport io.r2dbc.spi.Option;\nimport io.r2dbc.spi.TransactionDefinition;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@SuppressWarnings(\"unchecked\")\nfinal class SimpleTransactionDefinition implements MssqlTransactionDefinition {\n\n    public static final SimpleTransactionDefinition EMPTY = new SimpleTransactionDefinition(Collections.emptyMap());\n\n    private final Map<Option<?>, Object> options;\n\n    SimpleTransactionDefinition(Map<Option<?>, Object> options) {\n        this.options = options;\n    }\n\n    @Override\n    public <T> T getAttribute(Option<T> option) {\n        return (T) this.options.get(option);\n    }\n\n    public MssqlTransactionDefinition with(Option<?> option, Object value) {\n\n        Map<Option<?>, Object> options = new HashMap<>(this.options);\n        options.put(Assert.requireNonNull(option, \"option must not be null\"), Assert.requireNonNull(value, \"value must not be null\"));\n\n        return new SimpleTransactionDefinition(options);\n    }\n\n    @Override\n    public MssqlTransactionDefinition isolationLevel(IsolationLevel isolationLevel) {\n        return with(MssqlTransactionDefinition.ISOLATION_LEVEL, isolationLevel);\n    }\n\n    @Override\n    public MssqlTransactionDefinition lockTimeout(Duration timeout) {\n        return with(MssqlTransactionDefinition.LOCK_WAIT_TIMEOUT, timeout);\n    }\n\n    @Override\n    public MssqlTransactionDefinition name(String name) {\n        return with(MssqlTransactionDefinition.NAME, name);\n    }\n\n    @Override\n    public MssqlTransactionDefinition mark(String mark) {\n        if (getAttribute(TransactionDefinition.NAME) == null) {\n            name(mark);\n        }\n\n        return with(MARK, mark);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/api/package-info.java",
    "content": "/*\n * Copyright 2021-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * R2DBC driver API with SQL Server-specific extensions.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.api;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/Client.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.tds.Redirect;\nimport io.r2dbc.mssql.message.type.Collation;\nimport org.reactivestreams.Publisher;\nimport org.reactivestreams.Subscriber;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport java.util.Optional;\nimport java.util.function.Predicate;\n\n/**\n * An abstraction that wraps the networking part of exchanging {@link Message}s.\n *\n * @author Mark Paluch\n * @author Lars Haatveit\n */\npublic interface Client {\n\n    /**\n     * Send an attention token to interrupt an active statement.\n     *\n     * @return\n     * @since 0.9\n     */\n    Mono<Void> attention();\n\n    /**\n     * Release any resources held by the {@link Client}.\n     *\n     * @return a {@link Mono} that indicates that a client has been closed\n     */\n    Mono<Void> close();\n\n    /**\n     * Perform an exchange of messages. Calling this method while a previous exchange is active will return a deferred handle and queue the request until the previous exchange terminates.\n     *\n     * @param requests  the publisher of outbound messages\n     * @param takeUntil {@link Predicate} determining the last response frame to {@link Subscriber#onComplete() complete} the stream and prevent multiple subscribers from consuming\n     *                  previous, active response streams. Note that the last frame that matches {@code takeUntil} is emitted through the resulting {@link Flux}.\n     * @return a {@link Flux} of incoming messages that ends with the end of the frame.\n     */\n    Flux<Message> exchange(Publisher<? extends ClientMessage> requests, Predicate<Message> takeUntil);\n\n    /**\n     * Returns the {@link ByteBufAllocator}.\n     *\n     * @return the {@link ByteBufAllocator}\n     */\n    ByteBufAllocator getByteBufAllocator();\n\n    /**\n     * Returns the {@link ConnectionContext}.\n     *\n     * @return the {@link ConnectionContext}.\n     */\n    ConnectionContext getContext();\n\n    /**\n     * Returns the database {@link Collation}.\n     *\n     * @return the database {@link Collation}.\n     */\n    Optional<Collation> getDatabaseCollation();\n\n    /**\n     * Returns the database version.\n     *\n     * @return the database version.\n     */\n    Optional<String> getDatabaseVersion();\n\n    /**\n     * Returns the server {@link Redirect}.\n     *\n     * @return the server redirect.\n     * @since 0.8.2\n     */\n    Optional<Redirect> getRedirect();\n\n    /**\n     * Returns the {@link TransactionDescriptor}.\n     *\n     * @return the {@link TransactionDescriptor} describing the server-side transaction.\n     */\n    TransactionDescriptor getTransactionDescriptor();\n\n    /**\n     * Returns the {@link TransactionStatus}.\n     *\n     * @return the current {@link TransactionStatus}.\n     */\n    TransactionStatus getTransactionStatus();\n\n    /**\n     * @return the required {@link Collation} for the current database.\n     * @throws IllegalStateException if no {@link Collation} is available.\n     */\n    default Collation getRequiredCollation() {\n        return getDatabaseCollation().orElseThrow(() -> new IllegalStateException(\"Collation not available\"));\n    }\n\n    /**\n     * Returns whether the server supports column encryption.\n     *\n     * @return {@code true} if the server supports column encryption.\n     */\n    boolean isColumnEncryptionSupported();\n\n    /**\n     * Returns whether the client is connected to a server.\n     *\n     * @return {@literal true} if the client is connected to a server.\n     */\n    boolean isConnected();\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ClientConfiguration.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.r2dbc.mssql.client.ssl.SslConfiguration;\nimport reactor.netty.resources.ConnectionProvider;\n\nimport java.time.Duration;\n\n/**\n * Connection configuration details.\n *\n * @author Mark Paluch\n */\npublic interface ClientConfiguration extends SslConfiguration {\n\n    /**\n     * @return server hostname.\n     */\n    String getHost();\n\n    /**\n     * @return server port.\n     */\n    int getPort();\n\n    /**\n     * @return connection timeout.\n     */\n    Duration getConnectTimeout();\n\n    /**\n     * @return whether TCP KeepAlive is enabled.\n     * @since 0.8.5\n     */\n    boolean isTcpKeepAlive();\n\n    /**\n     * @return whether TCP NoDelay is enabled.\n     * @since 0.8.5\n     */\n    boolean isTcpNoDelay();\n\n    /**\n     * @return connection provider.\n     */\n    ConnectionProvider getConnectionProvider();\n\n    /**\n     * @return the SSL tunnel configuration.\n     * @since 0.8.5\n     */\n    default SslConfiguration getSslTunnelConfiguration() {\n        return DisabledSslTunnel.INSTANCE;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ConnectionContext.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.r2dbc.mssql.MssqlConnectionConfiguration;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\n\nimport javax.annotation.Nullable;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * Value object capturing diagnostic connection context. Allows for log-message post-processing with {@link #getMessage(String) if the logger category for\n * {@code io.r2dbc.mssql.client.ConnectionContext} is enabled for DEBUG/TRACE logs.\n * <p>\n * Captures also the configured {@link MssqlConnectionConfiguration#getApplicationName()}  application name} and {@link MssqlConnectionConfiguration#getConnectionId() connection Id}.\n *\n * @author Mark Paluch\n */\npublic class ConnectionContext {\n\n    private final static Logger LOGGER = Loggers.getLogger(ConnectionContext.class.getName() + \".context\");\n\n    private final static boolean CONTEXT_ENABLED = LOGGER.isDebugEnabled();\n\n    private final static boolean CHANNEL_ID_ENABLED = LOGGER.isTraceEnabled();\n\n    private static final AtomicLong CONN_ID = new AtomicLong();\n\n    @Nullable\n    private final String applicationName;\n\n    @Nullable\n    private final UUID connectionId;\n\n    @Nullable\n    private final String channelId;\n\n    private final String connectionCounter;\n\n    private final String connectionIdPrefix;\n\n    /**\n     * Create a new {@link ConnectionContext} with a unique connection Id.\n     */\n    public ConnectionContext() {\n        this.applicationName = null;\n        this.connectionId = null;\n        this.connectionCounter = incrementConnectionCounter();\n        this.connectionIdPrefix = getConnectionIdPrefix();\n        this.channelId = null;\n    }\n\n    /**\n     * Create a new {@link ConnectionContext} with a unique connection Id.\n     */\n    public ConnectionContext(@Nullable String applicationName, @Nullable UUID connectionId) {\n\n        this.applicationName = applicationName;\n        this.connectionId = connectionId;\n        this.connectionCounter = incrementConnectionCounter();\n        this.connectionIdPrefix = getConnectionIdPrefix();\n        this.channelId = null;\n    }\n\n    private ConnectionContext(@Nullable String applicationName, @Nullable UUID connectionId, @Nullable String channelId, String connectionCounter, String connectionIdPrefix) {\n        this.applicationName = applicationName;\n        this.connectionId = connectionId;\n        this.channelId = channelId;\n        this.connectionCounter = connectionCounter;\n        this.connectionIdPrefix = connectionIdPrefix;\n    }\n\n    private String incrementConnectionCounter() {\n        return Long.toHexString(CONN_ID.incrementAndGet());\n    }\n\n    private String getConnectionIdPrefix() {\n        return \"[cid: 0x\" + this.connectionCounter + \"] \";\n    }\n\n    /**\n     * Process the {@code original} message to inject potentially debug information such as the channel Id or the connection Id.\n     *\n     * @param original the original message.\n     * @return the post-processed log message.\n     */\n    public String getMessage(String original) {\n\n        if (CHANNEL_ID_ENABLED) {\n            return this.connectionIdPrefix + this.channelId + \" \" + original;\n        }\n\n        if (CONTEXT_ENABLED) {\n            return this.connectionIdPrefix + original;\n        }\n\n        return original;\n    }\n\n    /**\n     * Create a new {@link ConnectionContext} by associating the {@code channelId}.\n     *\n     * @param channelId the channel identifier.\n     * @return a new {@link ConnectionContext} with all previously set values and the associated {@code channelId}.\n     */\n    public ConnectionContext withChannelId(String channelId) {\n        return new ConnectionContext(this.applicationName, this.connectionId, channelId, this.connectionCounter, this.connectionIdPrefix);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ConnectionState.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.Channel;\nimport io.netty.handler.ssl.SslHandler;\nimport io.r2dbc.mssql.client.ssl.SslState;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.token.AbstractDoneToken;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.Login7;\nimport io.r2dbc.mssql.message.token.Prelogin;\nimport io.r2dbc.mssql.message.token.Tabular;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.core.publisher.SynchronousSink;\nimport reactor.netty.Connection;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport static io.r2dbc.mssql.message.header.Status.StatusBit;\n\n/**\n * Connection state according to the TDS state machine. The flow is defined as:\n * <ul>\n * <li>Create the transport connection from the client to the server</li>\n * <li>Enter {@link #PRELOGIN} state and send a {@link Prelogin} message</li>\n * <li>If encryption is required/off/on, then enter {@link #PRELOGIN_SSL_NEGOTIATION}. Note that\n * {@link Prelogin.Encryption#ENCRYPT_OFF} requires SSL negotiation for the {@link Login7} message.</li>\n * <li>Enter {@link #LOGIN} state once SSL is negotiated and send a {@link Login7} message</li>\n * <li>Enter {@link #POST_LOGIN} after receiving login ack</li>\n * </ul>\n * Connection states can {@link #canAdvance(Message) advance} triggered by a received {@link Message}. A state can provide a {@link MessageDecoder} function to decode messages exchanged in that\n * state. Note that message decoding is not supported in all states as per TDS state specification.\n *\n * @author Mark Paluch\n */\npublic enum ConnectionState {\n\n    /**\n     * State directly after the establishing the transport connection.\n     * <p/>\n     * The only allowed message to send and receive is {@link Prelogin}.\n     */\n    PRELOGIN {\n        @Override\n        MessageDecoder decoder(Client client) {\n\n            return (header, byteBuf) -> {\n\n                Assert.isTrue(header.getType() == Type.TABULAR_RESULT, () -> \"Expected tabular message, header type is: \" + header.getType());\n                Assert.isTrue(header.is(StatusBit.EOM), \"Prelogin response packet must not be chunked\");\n\n                return Collections.singletonList(Prelogin.decode(byteBuf));\n            };\n        }\n\n        @Override\n        public boolean canAdvance(Message message) {\n\n            Prelogin prelogin = (Prelogin) message;\n\n            Prelogin.Version version = prelogin.getRequiredToken(Prelogin.Version.class);\n\n            if (version.getVersion() >= 9) {\n                return true;\n            }\n\n            throw ProtocolException.unsupported(\"Unsupported SQL server version: \" + version.getVersion());\n        }\n\n        @Override\n        public ConnectionState next(Message message, Connection connection) {\n\n            Prelogin prelogin = (Prelogin) message;\n            Prelogin.Encryption encryption = prelogin.getRequiredToken(Prelogin.Encryption.class);\n\n            if (encryption.requiresLoginSslHandshake()) {\n\n                Channel channel = connection.channel();\n                channel.pipeline().fireUserEventTriggered(SslState.LOGIN_ONLY);\n\n                return PRELOGIN_SSL_NEGOTIATION;\n            }\n\n            if (encryption.requiresConnectionSslHandshake()) {\n\n                Channel channel = connection.channel();\n                channel.pipeline().fireUserEventTriggered(SslState.CONNECTION);\n\n                return PRELOGIN_SSL_NEGOTIATION;\n            }\n\n            return PRELOGIN;\n        }\n    },\n\n    /**\n     * SSL negotiation state. This state is handled entirely on the transport level.\n     *\n     * @see SslHandler\n     */\n    PRELOGIN_SSL_NEGOTIATION {\n        @Override\n        public boolean canAdvance(Message message) {\n            return message == SslState.NEGOTIATED;\n        }\n\n        @Override\n        public ConnectionState next(Message message, Connection connection) {\n            return LOGIN;\n        }\n\n        @Override\n        MessageDecoder decoder(Client client) {\n            return (header, byteBuf) -> {\n                throw ProtocolException.invalidTds(\"Nothing to decode during SSL negotiation\");\n            };\n        }\n    },\n\n    /**\n     * State during login.\n     *\n     * @see Login7\n     */\n    LOGIN {\n        @Override\n        public boolean canAdvance(Message message) {\n            return message instanceof DoneToken;\n        }\n\n        @Override\n        public ConnectionState next(Message message, Connection connection) {\n\n            if (AbstractDoneToken.isDone(message)) {\n                return POST_LOGIN;\n            }\n\n            return LOGIN_FAILED;\n        }\n\n        @Override\n        MessageDecoder decoder(Client client) {\n\n            return (header, byteBuf) -> {\n\n                Assert.isTrue(header.getType() == Type.TABULAR_RESULT, () -> \"Expected tabular message, header type is: \" + header.getType());\n                Assert.isTrue(header.is(StatusBit.EOM), \"Login response packet must not be chunked\");\n\n                Tabular tabular = Tabular.decode(byteBuf, client.isColumnEncryptionSupported());\n                return tabular.getTokens();\n            };\n        }\n    },\n\n    /**\n     * State after successful login.\n     */\n    POST_LOGIN {\n        @Override\n        public boolean canAdvance(Message message) {\n            return false;\n        }\n\n        @Override\n        public ConnectionState next(Message message, Connection connection) {\n            return null;\n        }\n\n        @Override\n        MessageDecoder decoder(Client client) {\n\n            Tabular.TabularDecoder decoder = Tabular.createDecoder(client.isColumnEncryptionSupported());\n\n            return new MessageDecoder() {\n\n                @Override\n                public List<? extends Message> apply(Header header, ByteBuf byteBuf) {\n\n                    Assert.isTrue(header.getType() == Type.TABULAR_RESULT, () -> \"Expected tabular message, header type is: \" + header.getType());\n\n                    return decoder.decode(byteBuf);\n                }\n\n                @Override\n                public boolean decode(Header header, ByteBuf buffer, SynchronousSink<Message> sink) {\n                    return decoder.decode(buffer, sink);\n                }\n            };\n        }\n    },\n\n    /**\n     * State after failed login.\n     */\n    LOGIN_FAILED {\n        @Override\n        public boolean canAdvance(Message message) {\n            return false;\n        }\n\n        @Override\n        public ConnectionState next(Message message, Connection connection) {\n            return null;\n        }\n\n        @Override\n        MessageDecoder decoder(Client client) {\n            return null;\n        }\n    };\n\n    /**\n     * Check whether the state can advance from the given {@link Message} into a differen {@link ConnectionState}.\n     *\n     * @param message the message to inspect.\n     * @return {@code true} if the state can advance.\n     */\n    public abstract boolean canAdvance(Message message);\n\n    /**\n     * Return the next {@link ConnectionState} using the given {@link Message} and {@link Connection transport connection}.\n     *\n     * @param message    the message that triggered connection state change.\n     * @param connection the transport connection.\n     * @return the next {@link ConnectionState}.\n     */\n    public abstract ConnectionState next(Message message, Connection connection);\n\n    /**\n     * Returns the {@link MessageDecoder} that is applicable for the current {@link ConnectionState}.\n     * Message decoding is not supported in all states.\n     *\n     * @param client the client instance.\n     * @return the {@link MessageDecoder}.\n     */\n    abstract MessageDecoder decoder(Client client);\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/DisabledSslTunnel.java",
    "content": "/*\n * Copyright 2020-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.handler.ssl.SslContext;\nimport io.r2dbc.mssql.client.ssl.SslConfiguration;\n\nenum DisabledSslTunnel implements SslConfiguration {\n\n    INSTANCE;\n\n    @Override\n    public boolean isSslEnabled() {\n        return false;\n    }\n\n    @Override\n    public SslContext getSslContext() {\n        throw new IllegalStateException(\"SSL tunnel is disabled\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/EnvironmentChangeEvent.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.r2dbc.mssql.message.token.EnvChangeToken;\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Environment change event based on a {@link EnvChangeToken}.\n *\n * @author Mark Paluch\n */\npublic class EnvironmentChangeEvent {\n\n    private final EnvChangeToken token;\n\n    /**\n     * Create a new {@link EnvironmentChangeEvent}.\n     *\n     * @param token the environment change token.\n     */\n    public EnvironmentChangeEvent(EnvChangeToken token) {\n        this.token = Assert.requireNonNull(token, \"EnvChangeToken must not be null\");\n    }\n\n    /**\n     * @return the environment change token.\n     */\n    public EnvChangeToken getToken() {\n        return this.token;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/EnvironmentChangeListener.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\n/**\n * Listener interface for {@link EnvironmentChangeEvent}s. This interface is intended for objects that want to be\n * notified about environment changes such as a changed database or packet size.\n *\n * @author Mark Paluch\n */\n@FunctionalInterface\npublic interface EnvironmentChangeListener {\n\n    /**\n     * Event listener callback for environment change events.\n     *\n     * @param event environment change event\n     */\n    void onEnvironmentChange(EnvironmentChangeEvent event);\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/MessageDecoder.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.header.Header;\nimport reactor.core.publisher.SynchronousSink;\n\nimport java.util.List;\nimport java.util.function.BiFunction;\n\n/**\n * Decoder interface that accepts a {@link Header} and {@link ByteBuf data buffer} to attempt to decode {@link Message}s.\n *\n * @author Mark Paluch\n * @see StreamDecoder\n */\ninterface MessageDecoder extends BiFunction<Header, ByteBuf, List<? extends Message>> {\n\n    /**\n     * Apply the decoder function {@link #decode(Header, ByteBuf, SynchronousSink)} and notify {@link SynchronousSink} about every decoded {@link Message}.\n     *\n     * @param header\n     * @param buffer\n     * @param sink\n     * @return\n     */\n    default boolean decode(Header header, ByteBuf buffer, SynchronousSink<Message> sink) {\n\n        List<? extends Message> messages = apply(header, buffer);\n\n        if (messages.isEmpty()) {\n            return false;\n        }\n\n        messages.forEach(sink::next);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ReactorNettyClient.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.logging.LoggingHandler;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslHandler;\nimport io.netty.util.internal.logging.InternalLogger;\nimport io.netty.util.internal.logging.InternalLoggerFactory;\nimport io.r2dbc.mssql.client.ssl.SslConfiguration;\nimport io.r2dbc.mssql.client.ssl.TdsSslHandler;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.header.PacketIdProvider;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.tds.Redirect;\nimport io.r2dbc.mssql.message.token.*;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.R2dbcException;\nimport io.r2dbc.spi.R2dbcNonTransientResourceException;\nimport org.reactivestreams.Publisher;\nimport org.reactivestreams.Subscriber;\nimport org.reactivestreams.Subscription;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.*;\nimport reactor.netty.Connection;\nimport reactor.netty.NettyOutbound;\nimport reactor.netty.resources.ConnectionProvider;\nimport reactor.netty.tcp.SslProvider;\nimport reactor.netty.tcp.TcpClient;\nimport reactor.netty.tcp.TcpSslContextSpec;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\nimport reactor.util.concurrent.Queues;\nimport reactor.util.context.Context;\nimport reactor.util.context.ContextView;\n\nimport javax.annotation.Nullable;\nimport java.security.GeneralSecurityException;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Optional;\nimport java.util.Queue;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicIntegerFieldUpdater;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.function.Supplier;\n\n/**\n * An implementation of a TDS client based on the Reactor Netty project.\n *\n * @see TcpClient\n */\npublic final class ReactorNettyClient implements Client {\n\n    private static final Logger logger = Loggers.getLogger(ReactorNettyClient.class);\n\n    private static final boolean DEBUG_ENABLED = logger.isDebugEnabled();\n\n    private static final Supplier<MssqlConnectionClosedException> UNEXPECTED = () -> new MssqlConnectionClosedException(\"Connection unexpectedly closed\");\n\n    private static final Supplier<MssqlConnectionClosedException> EXPECTED = () -> new MssqlConnectionClosedException(\"Connection closed\");\n\n    private static final Supplier<MssqlConnectionClosedException> CLOSED = () -> new MssqlConnectionClosedException(\"Cannot exchange messages because the connection is closed\");\n\n    private final ConnectionContext context;\n\n    private final ByteBufAllocator byteBufAllocator;\n\n    private final Connection connection;\n\n    private final TdsEncoder tdsEncoder;\n\n    private final Consumer<EnvChangeToken> handleEnvChange;\n\n    private final Consumer<FeatureExtAckToken> featureAckChange = (token) -> {\n\n        for (FeatureExtAckToken.FeatureToken featureToken : token.getFeatureTokens()) {\n\n            if (featureToken instanceof FeatureExtAckToken.ColumnEncryption) {\n                this.encryptionSupported = true;\n            }\n        }\n    };\n\n    private final AtomicBoolean isClosed = new AtomicBoolean(false);\n\n    private final AtomicLong attentionPropagation = new AtomicLong();\n\n    private final AtomicLong outstandingRequests = new AtomicLong();\n\n    private final Sinks.Many<ClientMessage> requestSink = Sinks.many().unicast().onBackpressureBuffer();\n\n    private final Sinks.Many<Message> responseProcessor = Sinks.many().multicast().onBackpressureBuffer(512, false);\n\n    private final TransactionListener transactionListener = new TransactionListener();\n\n    private final CollationListener collationListener = new CollationListener();\n\n    private final RedirectListener redirectListener = new RedirectListener();\n\n    private final RequestQueue requestQueue;\n\n    // May change during initialization. Values remain the same after connection initialization.\n\n    private ConnectionState state = ConnectionState.PRELOGIN;\n\n    private MessageDecoder decodeFunction = ConnectionState.PRELOGIN.decoder(this);\n\n    private boolean encryptionSupported = false;\n\n    private volatile Optional<Collation> databaseCollation = Optional.empty();\n\n    private Optional<String> databaseVersion = Optional.empty();\n\n    private volatile Optional<Redirect> redirect = Optional.empty();\n\n    // May change during driver interaction, may be read on other threads.\n\n    private volatile TransactionDescriptor transactionDescriptor = TransactionDescriptor.empty();\n\n    private volatile TransactionStatus transactionStatus = TransactionStatus.AUTO_COMMIT;\n\n    /**\n     * Creates a new frame processor connected to a given TCP connection.\n     *\n     * @param connection        the TCP connection\n     * @param connectionContext the connection context\n     */\n    private ReactorNettyClient(Connection connection, TdsEncoder tdsEncoder, ConnectionContext connectionContext) {\n        Assert.requireNonNull(connection, \"Connection must not be null\");\n        Assert.state(this.responseProcessor.asFlux() instanceof Subscriber, () -> \"Response processor \" + this.responseProcessor + \" is not a Subscriber. Cannot proceed.\");\n\n        this.context = connectionContext;\n\n        StreamDecoder decoder = new StreamDecoder();\n\n        this.handleEnvChange = (token) -> {\n\n            EnvironmentChangeEvent event = new EnvironmentChangeEvent(token);\n\n            try {\n                tdsEncoder.onEnvironmentChange(event);\n                this.transactionListener.onEnvironmentChange(event);\n                this.collationListener.onEnvironmentChange(event);\n                this.redirectListener.onEnvironmentChange(event);\n            } catch (Exception e) {\n                logger.warn(this.context.getMessage(\"Failed onEnvironmentChange() in {}\"), \"\", e);\n            }\n        };\n\n        this.byteBufAllocator = connection.outbound().alloc();\n        this.connection = connection;\n        this.tdsEncoder = tdsEncoder;\n        this.requestQueue = new RequestQueue(this.context);\n\n        Consumer<Message> handleStateChange =\n            (message) -> {\n\n                if (message.getClass() == LoginAckToken.class) {\n                    LoginAckToken loginAckToken = (LoginAckToken) message;\n                    this.databaseVersion = Optional.of(loginAckToken.getVersion().toString());\n                }\n\n                ConnectionState connectionState = this.state;\n\n                if (connectionState.canAdvance(message)) {\n\n                    ConnectionState nextState = connectionState.next(message, connection);\n\n                    this.state = nextState;\n                    this.decodeFunction = nextState.decoder(this);\n                }\n            };\n\n        AtomicReference<Subscription> subscriptionRef = new AtomicReference<>();\n        SynchronousSink<Message> sink = new SynchronousSink<Message>() {\n\n            @Override\n            public void complete() {\n                throw new UnsupportedOperationException();\n            }\n\n            @Deprecated\n            @Override\n            public Context currentContext() {\n                return Context.empty();\n            }\n\n            @Override\n            public ContextView contextView() {\n                return Context.empty();\n            }\n\n            @Override\n            public void error(Throwable e) {\n\n                Throwable errorToUse = e;\n                if (!(errorToUse instanceof R2dbcException)) {\n                    errorToUse = new MssqlConnectionException(errorToUse);\n                }\n\n                ReactorNettyClient.this.responseProcessor.emitError(errorToUse, Sinks.EmitFailureHandler.FAIL_FAST);\n            }\n\n            @Override\n            public void next(Message message) {\n\n                if (DEBUG_ENABLED) {\n                    onInfoToken(message);\n                }\n\n                handleStateChange.accept(message);\n\n                if (message.getClass() == EnvChangeToken.class) {\n                    ReactorNettyClient.this.handleEnvChange.accept((EnvChangeToken) message);\n                }\n\n                if (message.getClass() == FeatureExtAckToken.class) {\n                    ReactorNettyClient.this.featureAckChange.accept((FeatureExtAckToken) message);\n                }\n\n                Subscription subscription = subscriptionRef.get();\n                if (AbstractDoneToken.isAttentionAck(message)) {\n\n                    long current;\n                    do {\n                        current = ReactorNettyClient.this.attentionPropagation.get();\n\n                        if (current == 0) {\n                            if (DEBUG_ENABLED) {\n                                logger.debug(ReactorNettyClient.this.context.getMessage(\"Swallowing attention acknowledged, no pending requests: {}. \"), message);\n                            }\n\n                            // update demand for dropped next signal\n                            if (subscription != null) {\n                                subscription.request(1);\n                            }\n                            return;\n                        }\n\n                    } while (!ReactorNettyClient.this.attentionPropagation.compareAndSet(current, current - 1));\n                }\n\n                long attentionPropagation = ReactorNettyClient.this.attentionPropagation.get();\n\n                if (attentionPropagation > 0 && !AbstractDoneToken.isAttentionAck(message)) {\n                    if (DEBUG_ENABLED) {\n                        logger.debug(ReactorNettyClient.this.context.getMessage(\"Discard message {}. Draining frames until attention acknowledgement.\"), message);\n                    }\n                    // update demand for dropped next signal\n                    if (subscription != null) {\n                        subscription.request(1);\n                    }\n                    return;\n                }\n\n                ReactorNettyClient.this.responseProcessor.emitNext(message, Sinks.EmitFailureHandler.FAIL_FAST);\n            }\n        };\n\n        connection.inbound().receiveObject() //\n            .concatMapIterable(it -> {\n\n                if (it instanceof ByteBuf) {\n\n                    ByteBuf buffer = (ByteBuf) it;\n                    return decoder.decode(buffer, this.decodeFunction);\n                }\n\n                if (it instanceof Message) {\n                    return Collections.singleton((Message) it);\n                }\n\n                throw ProtocolException.unsupported(String.format(\"Unexpected protocol message: [%s]\", it));\n            })\n            .onErrorResume(this::resumeError)\n            .subscribe(new CoreSubscriber<Message>() {\n\n                @Override\n                public void onSubscribe(Subscription s) {\n                    subscriptionRef.set(s);\n\n                    ((Subscriber<?>) ReactorNettyClient.this.responseProcessor.asFlux()).onSubscribe(s);\n                }\n\n                @Override\n                public void onNext(Message message) {\n                    sink.next(message);\n                }\n\n                @Override\n                public void onError(Throwable t) {\n                    sink.error(t);\n                }\n\n                @Override\n                public void onComplete() {\n                    handleClose();\n                }\n            });\n\n        this.requestSink\n            .asFlux()\n            .concatMap(\n                message -> {\n\n                    Object encoded = encodeForSend(message);\n\n                    if (encoded instanceof Publisher) {\n                        return connection.outbound().sendObject((Publisher) encoded);\n                    }\n\n                    return connection.outbound().sendObject(encoded);\n                })\n            .onErrorResume(this::resumeError)\n            .doAfterTerminate(this::handleClose)\n            .subscribe();\n    }\n\n    private Object encodeForSend(ClientMessage message) {\n\n        if (DEBUG_ENABLED) {\n            logger.debug(this.context.getMessage(\"Request: {}\"), message);\n        }\n\n        return message.encode(this.connection.outbound().alloc(), this.tdsEncoder.getPacketSize());\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private <T> Mono<T> resumeError(Throwable throwable) {\n\n        logger.error(this.context.getMessage(\"Error: {}\"), throwable.getMessage(), throwable);\n\n        this.requestSink.emitComplete((signalType, emitResult) -> {\n\n            if (emitResult.isFailure()) {\n                logger.error(this.context.getMessage(\"Error: {}\"), emitResult);\n            }\n\n            return false;\n        });\n\n        handleConnectionError(throwable);\n        return (Mono<T>) close();\n    }\n\n    private void onInfoToken(Message message) {\n        logger.debug(this.context.getMessage(\"Response: {}\"), message);\n\n        if (message instanceof AbstractInfoToken) {\n            AbstractInfoToken token = (AbstractInfoToken) message;\n            if (token.getClassification() == AbstractInfoToken.Classification.INFORMATIONAL) {\n                logger.debug(this.context.getMessage(\"Info: Code [{}] Severity [{}]: {}\"), token.getNumber(), token.getClassification(),\n                    token.getMessage());\n            } else {\n                logger.debug(this.context.getMessage(\"Warning: Code [{}] Severity [{}]: {}\"), token.getNumber(), token.getClassification(),\n                    token.getMessage());\n            }\n        }\n    }\n\n    /**\n     * Creates a new frame processor connected to a given host.\n     *\n     * @param host the host to connect to\n     * @param port the port to connect to\n     */\n    public static Mono<ReactorNettyClient> connect(String host, int port) {\n\n        Assert.requireNonNull(host, \"host must not be null\");\n\n        return connect(host, port, Duration.ofSeconds(30));\n    }\n\n    /**\n     * Creates a new frame processor connected to a given host.\n     *\n     * @param host           the host to connect to\n     * @param port           the port to connect to\n     * @param connectTimeout the connect timeout\n     */\n    public static Mono<ReactorNettyClient> connect(String host, int port, Duration connectTimeout) {\n\n        Assert.requireNonNull(connectTimeout, \"connect timeout must not be null\");\n        Assert.requireNonNull(host, \"host must not be null\");\n\n        return connect(new ClientConfiguration() {\n\n            @Override\n            public String getHost() {\n                return host;\n            }\n\n            @Override\n            public int getPort() {\n                return port;\n            }\n\n            @Override\n            public Duration getConnectTimeout() {\n                return connectTimeout;\n            }\n\n            @Override\n            public boolean isTcpKeepAlive() {\n                return false;\n            }\n\n            @Override\n            public boolean isTcpNoDelay() {\n                return true;\n            }\n\n            @Override\n            public ConnectionProvider getConnectionProvider() {\n                return ConnectionProvider.newConnection();\n            }\n\n            @Override\n            public boolean isSslEnabled() {\n                return false;\n            }\n\n            @Override\n            public SslContext getSslContext() {\n                return SslProvider.builder()\n                    .sslContext(TcpSslContextSpec.forClient())\n                    .build().getSslContext();\n            }\n        }, null, null);\n    }\n\n    /**\n     * Creates a new frame processor connected to {@link ClientConfiguration}.\n     *\n     * @param configuration   the client configuration\n     * @param applicationName\n     * @param connectionId\n     */\n    public static Mono<ReactorNettyClient> connect(ClientConfiguration configuration, @Nullable String applicationName, @Nullable UUID connectionId) {\n\n        Assert.requireNonNull(configuration, \"configuration must not be null\");\n\n        ConnectionContext connectionContext = new ConnectionContext(applicationName, connectionId);\n        logger.debug(connectionContext.getMessage(\"connect()\"));\n\n        PacketIdProvider packetIdProvider = PacketIdProvider.atomic();\n\n        TdsEncoder tdsEncoder = new TdsEncoder(packetIdProvider);\n\n        Mono<? extends Connection> connection = TcpClient.create(configuration.getConnectionProvider())\n            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Math.toIntExact(configuration.getConnectTimeout().toMillis()))\n            .option(ChannelOption.SO_KEEPALIVE, configuration.isTcpKeepAlive())\n            .option(ChannelOption.TCP_NODELAY, configuration.isTcpNoDelay())\n            .host(configuration.getHost())\n            .port(configuration.getPort())\n            .connect()\n            .doOnNext(it -> {\n\n                SslConfiguration tunnel = configuration.getSslTunnelConfiguration();\n                ChannelPipeline pipeline = it.channel().pipeline();\n\n                if (tunnel.isSslEnabled()) {\n                    logger.debug(connectionContext.getMessage(\"Enabling SSL tunnel\"));\n                    try {\n                        pipeline.addFirst(\"sslTunnel\", createSslTunnelHandler(it.channel().alloc(), tunnel));\n                    } catch (GeneralSecurityException e) {\n                        it.channel().close();\n                        throw new IllegalStateException(\"Cannot configure SSL tunnel\", e);\n                    }\n                    pipeline.addAfter(\"sslTunnel\", tdsEncoder.getClass().getName(), tdsEncoder);\n                } else {\n                    pipeline.addFirst(tdsEncoder.getClass().getName(), tdsEncoder);\n                }\n\n                TdsSslHandler handler = new TdsSslHandler(packetIdProvider, configuration, connectionContext.withChannelId(it.channel().toString()));\n                pipeline.addAfter(tdsEncoder.getClass().getName(), handler.getClass().getName(), handler);\n\n                InternalLogger logger = InternalLoggerFactory.getInstance(ReactorNettyClient.class);\n                if (logger.isTraceEnabled()) {\n                    pipeline.addBefore(tdsEncoder.getClass().getName(), LoggingHandler.class.getSimpleName(),\n                        new LoggingHandler(ReactorNettyClient.class, LogLevel.TRACE));\n                }\n            });\n\n        return connection.map(it -> new ReactorNettyClient(it, tdsEncoder, connectionContext.withChannelId(it.channel().toString())));\n    }\n\n    private static SslHandler createSslTunnelHandler(ByteBufAllocator allocator, SslConfiguration tunnel) throws GeneralSecurityException {\n        return new SslHandler(tunnel.getSslContext().newEngine(allocator));\n    }\n\n    @Override\n    public Mono<Void> attention() {\n        return Mono.defer(() -> Mono.fromFuture(send(Mono.just(Attention.create(1, getTransactionDescriptor()))).toFuture()));\n    }\n\n    @Override\n    public Mono<Void> close() {\n\n        logger.debug(this.context.getMessage(\"close()\"));\n\n        return Mono.defer(() -> {\n\n            logger.debug(this.context.getMessage(\"close(subscribed)\"));\n\n            if (this.isClosed.compareAndSet(false, true)) {\n                this.connection.dispose();\n                return this.connection.onDispose();\n            }\n\n            return Mono.empty();\n        });\n    }\n\n    @Override\n    public ByteBufAllocator getByteBufAllocator() {\n        return this.byteBufAllocator;\n    }\n\n    @Override\n    public ConnectionContext getContext() {\n        return this.context;\n    }\n\n    @Override\n    public Optional<Collation> getDatabaseCollation() {\n        return this.databaseCollation;\n    }\n\n    @Override\n    public Optional<String> getDatabaseVersion() {\n        return this.databaseVersion;\n    }\n\n    @Override\n    public Optional<Redirect> getRedirect() {\n        return this.redirect;\n    }\n\n    @Override\n    public TransactionDescriptor getTransactionDescriptor() {\n        return this.transactionDescriptor;\n    }\n\n    @Override\n    public TransactionStatus getTransactionStatus() {\n        return this.transactionStatus;\n    }\n\n    @Override\n    public boolean isColumnEncryptionSupported() {\n        return this.encryptionSupported;\n    }\n\n    @Override\n    public boolean isConnected() {\n\n        if (this.isClosed.get()) {\n            return false;\n        }\n\n        Channel channel = this.connection.channel();\n        return channel.isOpen();\n    }\n\n    @Override\n    public Flux<Message> exchange(Publisher<? extends ClientMessage> requests, Predicate<Message> takeUntil) {\n\n        Assert.requireNonNull(takeUntil, \"takeUntil must not be null\");\n        Assert.requireNonNull(requests, \"Requests must not be null\");\n\n        if (DEBUG_ENABLED) {\n            logger.debug(this.context.getMessage(\"exchange()\"));\n        }\n\n        ExchangeRequest exchangeRequest = new ExchangeRequest();\n\n        Flux<Message> handle = Mono.<Flux<Message>>create(sink -> {\n\n            if (DEBUG_ENABLED) {\n                logger.debug(this.context.getMessage(\"exchange(subscribed)\"));\n            }\n\n            if (!isConnected()) {\n                sink.error(CLOSED.get());\n            }\n\n            Flux<Message> requestMessages = this.responseProcessor.asFlux()\n                .doOnSubscribe(ignore -> {\n                    this.outstandingRequests.incrementAndGet();\n                    Flux.from(requests).subscribe(t -> {\n\n                        if (!isConnected()) {\n                            sink.error(CLOSED.get());\n                            return;\n                        }\n\n                        this.requestSink.emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST);\n                    }, e -> this.requestSink.emitError(e, Sinks.EmitFailureHandler.FAIL_FAST), () -> {\n\n                        if (!isConnected()) {\n                            sink.error(CLOSED.get());\n                        }\n                    });\n                });\n\n            try {\n                exchangeRequest.submit(this.requestQueue, sink, requestMessages);\n            } catch (Exception e) {\n                sink.error(e);\n            }\n\n        }).flatMapMany(Function.identity()).handle((message, sink) -> {\n\n            sink.next(message);\n\n            if (takeUntil.test(message)) {\n                exchangeRequest.complete();\n                sink.complete();\n            }\n        });\n\n        return handle.doAfterTerminate(this.requestQueue).doFinally(it -> this.outstandingRequests.decrementAndGet()).doOnCancel(() -> {\n\n            if (!exchangeRequest.isComplete()) {\n                logger.error(\"Exchange cancelled while exchange is active. This is likely a bug leading to unpredictable outcome.\");\n            }\n        });\n    }\n\n    private Mono<Void> send(Publisher<? extends ClientMessage> requests) {\n        return Flux.from(requests).concatMap(message -> {\n            NettyOutbound nettyOutbound = this.connection.outbound().sendObject(encodeForSend(message));\n\n            if (message instanceof Attention && this.outstandingRequests.longValue() != 0) {\n                return Mono.from(nettyOutbound).doOnSuccess(v -> this.attentionPropagation.incrementAndGet());\n            }\n\n            return nettyOutbound;\n        }).then();\n    }\n\n    private void handleClose() {\n        if (this.isClosed.compareAndSet(false, true)) {\n            logger.warn(ReactorNettyClient.this.context.getMessage(\"Connection has been closed by peer\"));\n            drainError(UNEXPECTED);\n        } else {\n            drainError(EXPECTED);\n        }\n    }\n\n    private void handleConnectionError(Throwable error) {\n        drainError(() -> new MssqlConnectionException(error));\n    }\n\n    private void drainError(Supplier<? extends Throwable> supplier) {\n\n        Sinkable receiver;\n        while ((receiver = this.requestQueue.poll()) != null) {\n            receiver.onError(supplier.get());\n        }\n\n        this.responseProcessor.emitError(supplier.get(), Sinks.EmitFailureHandler.FAIL_FAST);\n    }\n\n    /**\n     * Request queue to collect incoming exchange requests.\n     * <p>Submission conditionally queues requests if an ongoing exchange was active by the time of subscription.\n     * Drains queued commands on exchange completion if there are queued commands or disable active flag.\n     */\n    static class RequestQueue implements Runnable {\n\n        private final Queue<Sinkable> requestQueue = Queues.<Sinkable>small().get();\n\n        private final AtomicBoolean active = new AtomicBoolean();\n\n        private final ConnectionContext context;\n\n        RequestQueue(ConnectionContext context) {\n            this.context = context;\n        }\n\n        @Nullable\n        public Sinkable poll() {\n            return this.requestQueue.poll();\n        }\n\n        @Override\n        public void run() {\n\n            Sinkable nextCommand = this.requestQueue.poll();\n\n            if (nextCommand != null) {\n\n                if (DEBUG_ENABLED) {\n                    logger.debug(this.context.getMessage(\"Initiating queued exchange\"));\n                }\n\n                nextCommand.onSuccess();\n                return;\n            }\n\n            if (DEBUG_ENABLED) {\n                logger.debug(this.context.getMessage(\"Conversation complete\"));\n            }\n\n            this.active.compareAndSet(true, false);\n        }\n\n        /**\n         * Submit a {@code exchangeRequest}. Requests are either executed directly (without an active exchange) or queued (if another exchange is currently active).\n         *\n         * @param exchangeRequest\n         */\n        void submit(Sinkable exchangeRequest) {\n\n            if (this.active.compareAndSet(false, true)) {\n\n                if (DEBUG_ENABLED) {\n                    logger.debug(this.context.getMessage(\"Initiating exchange\"));\n                }\n\n                exchangeRequest.onSuccess();\n            } else {\n\n                if (DEBUG_ENABLED) {\n                    logger.debug(this.context.getMessage(\"Queueing exchange\"));\n                }\n\n                if (!this.requestQueue.offer(exchangeRequest)) {\n                    throw new IllegalStateException(\"Request queue is full\");\n                }\n\n                drainRequestQueue();\n            }\n        }\n\n        void drainRequestQueue() {\n\n            if (this.active.compareAndSet(false, true)) {\n\n                Sinkable runnable = this.requestQueue.poll();\n\n                if (runnable != null) {\n                    runnable.onSuccess();\n                } else {\n                    this.active.compareAndSet(true, false);\n                }\n            }\n        }\n\n    }\n\n    /**\n     * Ensure a command request is submitted and subscribed to only once.\n     */\n    static class ExchangeRequest {\n\n        private static final AtomicIntegerFieldUpdater<ExchangeRequest> COMPLETED = AtomicIntegerFieldUpdater.newUpdater(ExchangeRequest.class, \"completed\");\n\n        private static final AtomicIntegerFieldUpdater<ExchangeRequest> SUBMITTED = AtomicIntegerFieldUpdater.newUpdater(ExchangeRequest.class, \"submitted\");\n\n        // access via COMPLETED\n        private volatile int completed = 0;\n\n        // access via SUBMITTED\n        private volatile int submitted = 0;\n\n        public void complete() {\n            COMPLETED.set(this, 1);\n        }\n\n        public boolean isComplete() {\n            return COMPLETED.get(this) == 1;\n        }\n\n        void submit(RequestQueue queue, MonoSink<Flux<Message>> sink, Flux<Message> requestMessages) {\n\n            if (!SUBMITTED.compareAndSet(this, 0, 1)) {\n                throw new IllegalStateException(\"Client exchange can be subscribed only once\");\n            }\n\n            queue.submit(new Sinkable() {\n\n                @Override\n                public void onSuccess() {\n                    sink.success(requestMessages);\n                }\n\n                @Override\n                public void onError(Throwable throwable) {\n                    sink.error(throwable);\n                }\n            });\n        }\n\n    }\n\n    class TransactionListener implements EnvironmentChangeListener {\n\n        @Override\n        public void onEnvironmentChange(EnvironmentChangeEvent event) {\n\n            EnvChangeToken token = event.getToken();\n\n            if (token.getChangeType() == EnvChangeToken.EnvChangeType.BeginTx\n                || token.getChangeType() == EnvChangeToken.EnvChangeType.EnlistDTC) {\n\n                byte[] descriptor = token.getNewValue();\n\n                if (descriptor.length != TransactionDescriptor.LENGTH) {\n                    throw ProtocolException.invalidTds(\"Transaction descriptor length mismatch\");\n                }\n\n                if (DEBUG_ENABLED) {\n\n                    String op;\n                    if (token.getChangeType() == EnvChangeToken.EnvChangeType.BeginTx) {\n                        op = \"started\";\n                    } else {\n                        op = \"enlisted\";\n                    }\n\n                    logger.debug(String.format(ReactorNettyClient.this.context.getMessage(\"Transaction %s\"), op));\n                }\n\n                updateStatus(TransactionStatus.STARTED, TransactionDescriptor.from(descriptor));\n            }\n\n            if (token.getChangeType() == EnvChangeToken.EnvChangeType.CommitTx) {\n\n                if (DEBUG_ENABLED) {\n                    logger.debug(ReactorNettyClient.this.context.getMessage(\"Transaction committed\"));\n                }\n\n                updateStatus(TransactionStatus.EXPLICIT, TransactionDescriptor.empty());\n            }\n\n            if (token.getChangeType() == EnvChangeToken.EnvChangeType.RollbackTx) {\n\n                if (DEBUG_ENABLED) {\n                    logger.debug(ReactorNettyClient.this.context.getMessage(\"Transaction rolled back\"));\n                }\n\n                updateStatus(TransactionStatus.EXPLICIT, TransactionDescriptor.empty());\n            }\n        }\n\n        private void updateStatus(TransactionStatus status, TransactionDescriptor descriptor) {\n            ReactorNettyClient.this.transactionStatus = status;\n            ReactorNettyClient.this.transactionDescriptor = descriptor;\n        }\n\n    }\n\n    class CollationListener implements EnvironmentChangeListener {\n\n        @Override\n        public void onEnvironmentChange(EnvironmentChangeEvent event) {\n\n            if (event.getToken().getChangeType() == EnvChangeToken.EnvChangeType.SQLCollation) {\n\n                Collation collation = Collation.decode(Unpooled.wrappedBuffer(event.getToken().getNewValue()));\n                ReactorNettyClient.this.databaseCollation = Optional.of(collation);\n            }\n        }\n\n    }\n\n    class RedirectListener implements EnvironmentChangeListener {\n\n        @Override\n        public void onEnvironmentChange(EnvironmentChangeEvent event) {\n\n            if (event.getToken().getChangeType() == EnvChangeToken.EnvChangeType.Routing) {\n\n                Redirect redirect = Redirect.decode(Unpooled.wrappedBuffer(event.getToken().getNewValue()));\n                ReactorNettyClient.this.redirect = Optional.of(redirect);\n            }\n        }\n\n    }\n\n    interface Sinkable {\n\n        void onSuccess();\n\n        void onError(Throwable throwable);\n\n    }\n\n    static class MssqlConnectionClosedException extends R2dbcNonTransientResourceException {\n\n        public MssqlConnectionClosedException(String reason) {\n            super(reason);\n        }\n\n    }\n\n    static class MssqlConnectionException extends R2dbcNonTransientResourceException {\n\n        public MssqlConnectionException(Throwable cause) {\n            super(cause);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/StreamDecoder.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.SynchronousSink;\nimport reactor.util.annotation.Nullable;\nimport reactor.util.context.Context;\nimport reactor.util.context.ContextView;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A TDS decoder that reads {@link ByteBuf}s and returns a {@link Flux} of decoded {@link Message}s.\n * <p/>\n * TDS messages consist of a header ({@link Header#LENGTH 8 byte length}) and a body. Messages can be either self-contained ({@link Status.StatusBit#EOM}) or chunked.  This decoder attempts to\n * decode messages from a {@link ByteBuf stream} by emitting zero, one or many {@link Message}s. Data buffers are aggregated and de-chunked until reaching a message boundary, then adaptive decoding\n * attempts to decode the aggregated and de-chunked body as far as possible. Remaining (non-decodable) data buffers are aggregated until the next attempt.\n * <p/>\n * This decoder is stateful and should be used in a try-to-decode fashion.\n *\n * @author Mark Paluch\n * @see Message\n * @see Header\n */\nfinal class StreamDecoder {\n\n    private DecoderState state;\n\n    /**\n     * Decode a {@link ByteBuf} into a {@link Flux} of {@link Message}s. If the {@link ByteBuf} does not end on a\n     * {@link Message} boundary, the {@link ByteBuf} will be retained until the concatenated contents of all retained\n     * {@link ByteBuf}s is a {@link Message} boundary.\n     *\n     * @param in the {@link ByteBuf} to decode\n     * @return a {@link Flux} of {@link Message}s\n     */\n    public List<Message> decode(ByteBuf in, MessageDecoder messageDecoder) {\n\n        Assert.requireNonNull(in, \"in must not be null\");\n        Assert.requireNonNull(messageDecoder, \"MessageDecoder must not be null\");\n\n        ListSink<Message> result = new ListSink<>();\n\n        decode(in, messageDecoder, result);\n\n        return result;\n    }\n\n    /**\n     * Decode a {@link ByteBuf} into a stream of {@link Message}s notifying {@link SynchronousSink}. If the {@link ByteBuf} does not end on a\n     * {@link Message} boundary, the {@link ByteBuf} will be retained until the concatenated contents of all retained\n     * {@link ByteBuf}s is a {@link Message} boundary.\n     *\n     * @param in the {@link ByteBuf} to decode\n     * @return a {@link Flux} of {@link Message}s\n     */\n    public void decode(ByteBuf in, MessageDecoder messageDecoder, SynchronousSink<Message> sink) {\n\n        Assert.requireNonNull(in, \"in must not be null\");\n        Assert.requireNonNull(messageDecoder, \"MessageDecoder must not be null\");\n\n        DecoderState decoderState = this.state;\n        this.state = null;\n\n        DecoderState state = decoderState == null ? DecoderState.initial(in) : decoderState.andChunk(in);\n\n        do {\n            state = withState(messageDecoder, sink, state);\n        } while (state != null);\n    }\n\n    @Nullable\n    private DecoderState withState(MessageDecoder messageDecoder, SynchronousSink<Message> sink, DecoderState state) {\n\n        if (state.header == null) {\n\n            if (!Header.canDecode(state.remainder)) {\n                return retain(state);\n            }\n\n            state = state.readHeader();\n        }\n\n        try {\n\n            Header header = state.getRequiredHeader();\n\n            if (!state.canReadChunk()) {\n                return retain(state);\n            }\n\n            state = state.readChunk();\n\n            int readerIndex = state.aggregatedBodyReaderIndex();\n\n            boolean hasMessages = messageDecoder.decode(header, state.aggregatedBody, sink);\n\n            if (hasMessages) {\n\n                if (state.hasRawRemainder()) {\n                    return state;\n                }\n\n                if (state.hasAggregatedBodyRemainder()) {\n                    return retain(state);\n                }\n            } else {\n                state.aggregatedBodyReaderIndex(readerIndex);\n                return retain(state);\n            }\n\n            state.release();\n            return null;\n        } catch (Exception e) {\n            sink.error(e);\n        }\n\n        return state;\n    }\n\n    @Nullable\n    private DecoderState retain(DecoderState state) {\n        this.state = state;\n        return null;\n    }\n\n    @Nullable\n    DecoderState getDecoderState() {\n        return this.state;\n    }\n\n    /**\n     * The current decoding state. Encapsulates the raw transport stream buffers (\"remainder\") and the aggregated (de-chunked) body along an optional {@link Header}.\n     */\n    static class DecoderState {\n\n        ByteBuf remainder;\n\n        ByteBuf aggregatedBody;\n\n        @Nullable\n        Header header;\n\n        private DecoderState(ByteBuf remainder, ByteBuf aggregatedBody, @Nullable Header header) {\n\n            this.remainder = remainder;\n            this.header = header;\n            this.aggregatedBody = aggregatedBody;\n        }\n\n        /**\n         * Create a new, initial {@link DecoderState}.\n         *\n         * @param initialBuffer the data buffer.\n         * @return the initial {@link DecoderState}.\n         */\n        static DecoderState initial(ByteBuf initialBuffer) {\n\n            ByteBuf composite = initialBuffer.alloc().buffer();\n            composite.writeBytes(initialBuffer);\n\n            ByteBuf aggregatedBody = initialBuffer.alloc().buffer();\n\n            return new DecoderState(composite, aggregatedBody, null);\n        }\n\n        /**\n         * Create a new {@link DecoderState} by appending a new raw remaining {@link ByteBuf data buffer}.\n         *\n         * @param in\n         * @return\n         */\n        DecoderState andChunk(ByteBuf in) {\n\n            this.remainder.writeBytes(in);\n            //this.remainder.addComponent(true, in.retain());\n            return newState(this.remainder, this.aggregatedBody, this.header);\n        }\n\n        DecoderState newState(ByteBuf remainder, ByteBuf aggregatedBody, @Nullable Header header) {\n\n            this.remainder = remainder;\n            this.aggregatedBody = aggregatedBody;\n            this.header = header;\n\n            return this;\n        }\n\n        boolean canReadChunk() {\n\n            int requiredChunkLength = getChunkLength();\n            return this.remainder.readableBytes() >= requiredChunkLength;\n        }\n\n        /**\n         * @return {@code true} if the remaining raw bytes (raw transport buffer) are not yet fully consumed.\n         */\n        boolean hasRawRemainder() {\n            return this.remainder.readableBytes() != 0;\n        }\n\n        /**\n         * @return {@code true} if the remaining aggregated body bytes (aggregation of body buffers without header) are not yet fully consumed.\n         */\n        boolean hasAggregatedBodyRemainder() {\n            return this.aggregatedBody.readableBytes() != 0;\n        }\n\n        /**\n         * @return the aggregated body reader index.\n         */\n        int aggregatedBodyReaderIndex() {\n            return this.aggregatedBody.readerIndex();\n        }\n\n        /**\n         * Reset the aggregated body reader index.\n         *\n         * @param index the reader index.\n         */\n        void aggregatedBodyReaderIndex(int index) {\n            this.aggregatedBody.readerIndex(index);\n        }\n\n        /**\n         * @return the required {@link Header}.\n         */\n        Header getRequiredHeader() {\n\n            if (this.header == null) {\n                throw new IllegalStateException(\"DecoderState has no header\");\n            }\n\n            return this.header;\n        }\n\n        // ----------------------------------------\n        // State-changing methods.\n        // ----------------------------------------\n\n        /**\n         * Create a new {@link DecoderState} with a decoded {@link Header}.\n         *\n         * @return the new {@link DecoderState}.\n         */\n        DecoderState readHeader() {\n            return newState(this.remainder, this.aggregatedBody, Header.decode(this.remainder));\n        }\n\n        /**\n         * Read the body chunk and create a new {@link DecoderState}.\n         * Body is read from the remainder by copying the contents to decouple the remainder from dechunked data. Otherwise we would probably overwrite remainder data with dechunking.\n         * Retains a new header if we were able to decode the header but not the rest of the chunk. Not retaining the header causes the header to be decoded on the next pass and that causes\n         * protocol out of sync.\n         * Drop the header if we were able to dechunk the remainder.\n         *\n         * @return the new {@link DecoderState}.\n         */\n        DecoderState readChunk() {\n\n            boolean hasNewHeader;\n\n            do {\n                hasNewHeader = false;\n                this.aggregatedBody.writeBytes(this.remainder, getChunkLength());\n\n                if (Header.canDecode(this.remainder)) {\n                    hasNewHeader = true;\n                    this.header = Header.decode(this.remainder);\n                }\n\n            } while (canReadChunk());\n\n            if (hasNewHeader) {\n                return newState(this.remainder, this.aggregatedBody, this.header);\n            }\n\n            return newState(this.remainder, this.aggregatedBody, null);\n        }\n\n        /**\n         * Retain this {@link DecoderState} (i.e. increment ref count).\n         *\n         * @return {@code this} {@link DecoderState}.\n         */\n        DecoderState retain() {\n\n            //this.remainder.consolidate();\n            this.remainder.retain();\n\n            //this.aggregatedBody.consolidate();\n            this.aggregatedBody.retain();\n\n            return this;\n        }\n\n        /**\n         * Release this {@link DecoderState} (i.e. decrement ref count).\n         */\n        void release() {\n\n            this.remainder.release();\n            this.aggregatedBody.release();\n        }\n\n        int getChunkLength() {\n            return getRequiredHeader().getLength() - Header.LENGTH;\n        }\n\n    }\n\n    static class ListSink<T> extends ArrayList<T> implements SynchronousSink<T> {\n\n        public ListSink() {\n            super(2);\n        }\n\n        @Override\n        public void complete() {\n            throw new UnsupportedOperationException();\n        }\n\n        @Deprecated\n        @Override\n        public Context currentContext() {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override\n        public ContextView contextView() {\n            throw new UnsupportedOperationException();\n        }\n\n        @Override\n        public void error(Throwable e) {\n            throw new RuntimeException(e);\n        }\n\n        @Override\n        public void next(T message) {\n            add(message);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/TdsEncoder.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.CompositeByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.util.concurrent.PromiseCombiner;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.PacketIdProvider;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.tds.ContextualTdsFragment;\nimport io.r2dbc.mssql.message.tds.FirstTdsFragment;\nimport io.r2dbc.mssql.message.tds.LastTdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsPacket;\nimport io.r2dbc.mssql.message.token.EnvChangeToken;\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Encoder for TDS packets. This encoder can apply various strategies regarding TDS header handling:\n * <ul>\n * <li>Pass-thru {@link ByteBuf} messages (typically used for SSL traffic)</li>\n * <li>Prefix {@link ByteBuf} messages with TDS {@link Header} (typically used for SSL Handshake during PRELOGIN)</li>\n * <li>Apply {@link HeaderOptions} state for subsequent messages (typically used to set a header context until receiving\n * {@link LastTdsFragment} or {@link ResetHeader}) when initiated by a written {@link HeaderOptions} or\n * {@link FirstTdsFragment}.</li>\n * <li>Reset {@link HeaderOptions} when a {@link ResetHeader#INSTANCE ResetHeader} is written.</li>\n * </ul>\n *\n * @author Mark Paluch\n * @see FirstTdsFragment\n * @see LastTdsFragment\n * @see TdsPacket\n * @see HeaderOptions\n * @see TdsFragment\n */\npublic final class TdsEncoder extends ChannelOutboundHandlerAdapter implements EnvironmentChangeListener {\n\n    /**\n     * Initial (default) packet size for TDS packets.\n     */\n    public static final int INITIAL_PACKET_SIZE = 8000;\n\n    private CompositeByteBuf lastChunkRemainder;\n\n    private final PacketIdProvider packetIdProvider;\n\n    private int packetSize;\n\n    private HeaderOptions headerOptions;\n\n    /**\n     * Creates a new {@link TdsEncoder} using the default {@link #INITIAL_PACKET_SIZE packet size.}.\n     *\n     * @param packetIdProvider provider for the {@literal packetId}.\n     */\n    public TdsEncoder(PacketIdProvider packetIdProvider) {\n        this(packetIdProvider, INITIAL_PACKET_SIZE);\n    }\n\n    /**\n     * Creates a new {@link TdsEncoder} using the given {@code packetSize}\n     *\n     * @param packetIdProvider provider for the {@literal packetId}.\n     * @throws IllegalArgumentException when {@link PacketIdProvider} is {@code null}.\n     */\n    public TdsEncoder(PacketIdProvider packetIdProvider, int packetSize) {\n\n        this.packetIdProvider = Assert.requireNonNull(packetIdProvider, \"PacketId Provider must not be null\");\n        this.packetSize = packetSize;\n    }\n\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {\n\n        if (msg == ResetHeader.INSTANCE) {\n\n            this.headerOptions = null;\n            ctx.write(Unpooled.EMPTY_BUFFER, promise);\n            return;\n        }\n\n        if (msg instanceof HeaderOptions) {\n\n            this.headerOptions = (HeaderOptions) msg;\n            ctx.write(Unpooled.EMPTY_BUFFER, promise);\n            return;\n        }\n\n        // Expect ByteBuf to be self-contained messages that do not require further chunking (for now).\n        if (msg instanceof ByteBuf) {\n\n            if (this.headerOptions == null) {\n                ctx.write(msg, promise);\n                return;\n            }\n\n            ByteBuf message = (ByteBuf) msg;\n\n            if (message.readableBytes() <= 0) {\n                ctx.write(msg, promise);\n                return;\n            }\n\n            doWriteFragment(ctx, promise, message, this.headerOptions, true);\n            return;\n        }\n\n        // Write entire TDSPacket\n        if (msg instanceof TdsPacket) {\n\n            TdsPacket packet = (TdsPacket) msg;\n            ByteBuf message = packet.encode(ctx.alloc(), this.packetIdProvider);\n\n            Assert.state(message.readableBytes() <= this.packetSize, \"Packet size exceeded\");\n\n            ctx.write(message, promise);\n            return;\n        }\n\n        // Write message use HeaderOptions for subsequent packets and apply HeaderOptions\n        if (msg instanceof FirstTdsFragment) {\n\n            FirstTdsFragment fragment = (FirstTdsFragment) msg;\n\n            this.headerOptions = fragment.getHeaderOptions();\n\n            doWriteFragment(ctx, promise, fragment.getByteBuf(), this.headerOptions, false);\n            return;\n        }\n\n        // Write message use HeaderOptions for subsequent packets and apply HeaderOptions\n        if (msg instanceof ContextualTdsFragment) {\n\n            ContextualTdsFragment fragment = (ContextualTdsFragment) msg;\n\n            doWriteFragment(ctx, promise, fragment.getByteBuf(), fragment.getHeaderOptions(), true);\n            return;\n        }\n\n        // Write message, apply HeaderOptions and clear HeaderOptions\n        if (msg instanceof LastTdsFragment) {\n\n            Assert.state(this.headerOptions != null, \"HeaderOptions must not be null!\");\n\n            TdsFragment fragment = (TdsFragment) msg;\n\n            doWriteFragment(ctx, promise, fragment.getByteBuf(), this.headerOptions, true);\n\n            this.headerOptions = null;\n            return;\n        }\n\n        // Write message and apply HeaderOptions\n        if (msg instanceof TdsFragment) {\n\n            Assert.state(this.headerOptions != null, \"HeaderOptions must not be null!\");\n\n            TdsFragment fragment = (TdsFragment) msg;\n\n            doWriteFragment(ctx, promise, fragment.getByteBuf(), this.headerOptions, false);\n            return;\n        }\n\n        throw new IllegalArgumentException(String.format(\"Unsupported message type: %s\", msg));\n    }\n\n    @Override\n    public void onEnvironmentChange(EnvironmentChangeEvent event) {\n\n        EnvChangeToken token = event.getToken();\n        if (token.getChangeType() == EnvChangeToken.EnvChangeType.Packetsize) {\n            setPacketSize(Integer.parseInt(token.getNewValueString()));\n        }\n    }\n\n    public void setPacketSize(int packetSize) {\n        this.packetSize = packetSize;\n    }\n\n    public int getPacketSize() {\n        return this.packetSize;\n    }\n\n    private static HeaderOptions getLastHeader(HeaderOptions headerOptions) {\n        return headerOptions.and(Status.StatusBit.EOM);\n    }\n\n    private static HeaderOptions getChunkedHeaderOptions(HeaderOptions headerOptions) {\n        return headerOptions.not(Status.StatusBit.EOM);\n    }\n\n    private void doWriteFragment(ChannelHandlerContext ctx, ChannelPromise promise, ByteBuf body,\n                                 HeaderOptions headerOptions, boolean lastLogicalPacket) {\n\n        if (requiresChunking(body.readableBytes())) {\n            writeChunkedMessage(ctx, promise, body, headerOptions, lastLogicalPacket);\n        } else {\n            writeSingleMessage(ctx, promise, body, headerOptions, lastLogicalPacket);\n        }\n\n        body.release();\n    }\n\n    private void writeSingleMessage(ChannelHandlerContext ctx, ChannelPromise promise, ByteBuf body, HeaderOptions headerOptions, boolean lastLogicalPacket) {\n\n        if (lastLogicalPacket || getBytesToWrite(body.readableBytes()) == getPacketSize()) {\n\n            HeaderOptions optionsToUse = lastLogicalPacket ? getLastHeader(headerOptions) : headerOptions;\n\n            int messageLength = getBytesToWrite(body.readableBytes());\n            ByteBuf buffer = ctx.alloc().buffer(messageLength);\n            Header.encode(buffer, optionsToUse, messageLength, this.packetIdProvider);\n\n            if (this.lastChunkRemainder != null) {\n\n                buffer.writeBytes(this.lastChunkRemainder);\n\n                this.lastChunkRemainder.release();\n                this.lastChunkRemainder = null;\n            }\n\n            buffer.writeBytes(body);\n\n            ctx.write(buffer, promise);\n        } else {\n\n            // Prevent partial packets/buffer underrun if not the last packet.\n            if (this.lastChunkRemainder == null) {\n                this.lastChunkRemainder = body.alloc().compositeBuffer();\n            }\n\n            this.lastChunkRemainder.addComponent(true, body.retain());\n\n            ctx.write(Unpooled.EMPTY_BUFFER, promise);\n        }\n    }\n\n    private void writeChunkedMessage(ChannelHandlerContext ctx, ChannelPromise promise, ByteBuf body, HeaderOptions headerOptions, boolean lastLogicalPacket) {\n\n        PromiseCombiner combiner = new PromiseCombiner(ctx.executor());\n\n        try {\n            while (body.readableBytes() > 0) {\n\n                if (this.lastChunkRemainder != null) {\n\n                    ByteBuf chunk = body.alloc().buffer(estimateChunkSize(getBytesToWrite(body.readableBytes())));\n\n                    int combinedSize = this.lastChunkRemainder.readableBytes() + body.readableBytes();\n                    HeaderOptions optionsToUse = isLastTransportPacket(combinedSize, lastLogicalPacket) ? getLastHeader(headerOptions) : getChunkedHeaderOptions(headerOptions);\n                    Header.encode(chunk, optionsToUse, this.packetSize, this.packetIdProvider);\n\n                    int actualBodyReadableBytes = this.packetSize - Header.LENGTH - this.lastChunkRemainder.readableBytes();\n                    chunk.writeBytes(this.lastChunkRemainder);\n                    chunk.writeBytes(body, actualBodyReadableBytes);\n\n                    this.lastChunkRemainder.release();\n                    this.lastChunkRemainder = null;\n\n                    combiner.add(ctx.write(chunk, ctx.newPromise()));\n                } else {\n\n                    if (!lastLogicalPacket && !requiresChunking(body.readableBytes())) {\n\n                        // Prevent partial packets/buffer underrun if not the last packet.\n                        this.lastChunkRemainder = body.alloc().compositeBuffer();\n                        this.lastChunkRemainder.addComponent(true, body.retain());\n                        break;\n                    }\n\n                    ByteBuf chunk = body.alloc().buffer(estimateChunkSize(getBytesToWrite(body.readableBytes())));\n                    HeaderOptions optionsToUse = isLastTransportPacket(body.readableBytes(), lastLogicalPacket) ? getLastHeader(headerOptions) : getChunkedHeaderOptions(headerOptions);\n\n                    int byteCount = getEffectiveChunkSizeWithoutHeader(body.readableBytes());\n                    Header.encode(chunk, optionsToUse, Header.LENGTH + byteCount, this.packetIdProvider);\n\n                    chunk.writeBytes(body, byteCount);\n                    combiner.add(ctx.write(chunk, ctx.newPromise()));\n                }\n            }\n\n            combiner.finish(promise);\n        } catch (RuntimeException e) {\n            promise.tryFailure(e);\n            throw e;\n        }\n    }\n\n    int estimateChunkSize(int readableBytes) {\n        return Math.min(readableBytes + Header.LENGTH, this.packetSize);\n    }\n\n    private boolean requiresChunking(int readableBytes) {\n        return getBytesToWrite(readableBytes) > this.packetSize;\n    }\n\n    private int getBytesToWrite(int readableBytes) {\n        int bytesToWrite = Header.LENGTH;\n        bytesToWrite += this.lastChunkRemainder != null ? this.lastChunkRemainder.readableBytes() : 0;\n        bytesToWrite += readableBytes;\n        return bytesToWrite;\n    }\n\n    private int getEffectiveChunkSizeWithoutHeader(int readableBytes) {\n        return Math.min(readableBytes, this.packetSize - Header.LENGTH);\n    }\n\n    private boolean isLastTransportPacket(int readableBytes, boolean lastLogicalPacket) {\n\n        if (requiresChunking(readableBytes)) {\n            return false;\n        }\n\n        return lastLogicalPacket;\n    }\n\n    /**\n     * Marker message to reset {@link HeaderOptions}.\n     */\n    public enum ResetHeader {\n        INSTANCE\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/TransactionStatus.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\n/**\n * Transactional state.\n *\n * @author Mark Paluch\n */\npublic enum TransactionStatus {\n\n    /**\n     * Default (initial) state on startup using implicit transactions.\n     */\n    AUTO_COMMIT,\n\n    /**\n     * State after committing or rolling back a transaction.\n     */\n    EXPLICIT,\n\n    /**\n     * Started transaction.\n     */\n    STARTED\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/package-info.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The infrastructure for exchanging messages with the server.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.client;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/ContextProxy.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.ssl.SslHandler;\n\n/**\n * Empty proxy handler as {@link ChannelHandlerContext} target for {@link SslHandler}. TDS requires wrapping of the SSL\n * handshake during prelogin and we need to wrap/reuse the SSL handler to prepend TDS prelogin headers during the\n * handshake.\n *\n * @author Mark Paluch\n */\nclass ContextProxy extends ChannelDuplexHandler {\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/ExpectedHostnameX509TrustManager.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\nimport reactor.util.annotation.Nullable;\n\nimport javax.net.ssl.X509TrustManager;\nimport java.net.IDN;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.List;\nimport java.util.function.Predicate;\n\n/**\n * {@link X509TrustManager} implementation using {@link HostNamePredicate} to verify {@link X509Certificate}s.\n *\n * @author Mark Paluch\n */\npublic final class ExpectedHostnameX509TrustManager implements X509TrustManager {\n\n    private static final Logger logger = Loggers.getLogger(TdsSslHandler.class);\n\n    private final X509TrustManager defaultTrustManager;\n\n    private final String expectedHostName;\n\n    private final Predicate<String> matcher;\n\n    public ExpectedHostnameX509TrustManager(X509TrustManager tm, String expectedHostName) {\n\n        this.defaultTrustManager = tm;\n        this.expectedHostName = expectedHostName;\n        this.matcher = HostNamePredicate.of(expectedHostName);\n    }\n\n    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"Forwarding ClientTrusted\");\n        }\n\n        this.defaultTrustManager.checkClientTrusted(chain, authType);\n    }\n\n    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"Forwarding ServerTrusted\");\n        }\n\n        this.defaultTrustManager.checkServerTrusted(chain, authType);\n\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"ServerTrusted succeeded proceeding with server name validation\");\n        }\n\n        validateServerNameInCertificate(chain[0]);\n    }\n\n    public X509Certificate[] getAcceptedIssuers() {\n        return this.defaultTrustManager.getAcceptedIssuers();\n    }\n\n    private void validateServerNameInCertificate(X509Certificate cert) throws CertificateException {\n\n        String subjectCN = X509CertificateUtil.getHostName(cert);\n\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Expecting server name: [%s]\", this.expectedHostName));\n            logger.debug(String.format(\"Name in certificate: [%s]\", subjectCN));\n        }\n\n        boolean isServerNameValidated;\n\n        // the name in cert is in RFC2253 format parse it to get the actual subject name\n\n        isServerNameValidated = validateServerName(subjectCN);\n\n        if (!isServerNameValidated) {\n\n            List<String> subjectAlternativeNames = X509CertificateUtil.getSubjectAlternativeNames(cert);\n\n            if (logger.isDebugEnabled()) {\n                logger.debug(String.format(\"Expecting server name validation failed. Checking alternative names (SAN) [%s]\", subjectAlternativeNames));\n            }\n\n            for (String subjectAlternativeName : subjectAlternativeNames) {\n\n                isServerNameValidated = validateServerName(subjectAlternativeName);\n\n                if (isServerNameValidated) {\n                    break;\n                }\n            }\n        }\n\n        if (!isServerNameValidated) {\n            throw new CertificateException(String.format(\"Cannot validate certificate: %s\", subjectCN));\n        }\n    }\n\n    private boolean validateServerName(@Nullable String nameInCert) {\n\n        // Failed to get the common name from DN or empty CN\n        if (null == nameInCert) {\n            return false;\n        }\n\n        if (nameInCert.startsWith(\"xn--\")) {\n            nameInCert = IDN.toUnicode(nameInCert);\n        }\n\n        boolean matches = this.matcher.test(nameInCert);\n\n        if (matches) {\n            logSuccessMessage(nameInCert);\n        } else {\n            logFailMessage(nameInCert);\n        }\n\n        return matches;\n    }\n\n    private void logFailMessage(String nameInCert) {\n\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"The name in certificate [%s] does not match with the server name [%s].\", nameInCert, this.expectedHostName));\n        }\n    }\n\n    private void logSuccessMessage(String nameInCert) {\n\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"The name in certificate [%s] validated against server name [%s].\", nameInCert, this.expectedHostName));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/HostNamePredicate.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.Locale;\nimport java.util.function.Predicate;\nimport java.util.regex.Pattern;\n\n/**\n * Hostname matcher. Accepts either a simple, fully-qualified or hostname with wildcards to match against SSL certificate hostnames.\n *\n * <ul>\n * <li>{@code foo} matches {@code foo} but not {@code bar}</li>\n * <li>{@code foo.bar} matches {@code foo.bar} but not {@code foo.baz}</li>\n * <li>{@code *.bar} matches {@code foo.bar} but not {@code foo.baz}</li>\n * <li>{@code f**.bar} matches {@code foo.bar} but not {@code boo.bar}</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nclass HostNamePredicate implements Predicate<String> {\n\n    private final Pattern hostnamePattern;\n\n    private HostNamePredicate(Pattern hostnamePattern) {\n        this.hostnamePattern = hostnamePattern;\n    }\n\n    /**\n     * Create a new {@link HostNamePredicate} instance.\n     *\n     * @param expectedHostname expected host name.\n     * @return the {@link HostNamePredicate}.\n     * @throws IllegalArgumentException if {@code expectedHostname} is {@code null}.\n     */\n    public static HostNamePredicate of(String expectedHostname) {\n\n        Assert.notNull(expectedHostname, \"Expected hostname must not be null\");\n\n        StringBuilder builder = new StringBuilder();\n\n        // canonicalize to lower-case\n        String[] segments = expectedHostname.toLowerCase(Locale.ENGLISH).split(\"\\\\.\");\n\n        for (String segment : segments) {\n\n            if (builder.length() != 0) {\n                builder.append(\"\\\\.\");\n            }\n\n            StringBuilder rewrittenSegment = new StringBuilder(segment.length());\n            StringBuilder part = new StringBuilder(segment.length());\n\n            for (char c : segment.toCharArray()) {\n\n                if (c == '*') {\n\n                    if (part.length() != 0) {\n                        rewrittenSegment.append(Pattern.quote(part.toString()));\n                        part = new StringBuilder(segment.length());\n                    }\n                    rewrittenSegment.append(\"([^\\\\.]*)\");\n                } else {\n                    part.append(c);\n                }\n            }\n\n            if (part.length() != 0) {\n                rewrittenSegment.append(Pattern.quote(part.toString()));\n            }\n\n            builder.append(rewrittenSegment);\n        }\n\n        return new HostNamePredicate(Pattern.compile(builder.toString()));\n    }\n\n    @Override\n    public boolean test(String s) {\n        return this.hostnamePattern.matcher(s).matches();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/SslConfiguration.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport io.netty.handler.ssl.SslContext;\n\nimport java.security.GeneralSecurityException;\n\n/**\n * SSL Configuration for SQL Server connections.\n * <p>Microsoft SQL server supports various SSL setups:\n *\n * <ul>\n * <li>Not enabled</li>\n * <li>Supported</li>\n * <li>Enabled/required</li>\n * </ul>\n * <p>\n * Supported mode uses SSL during login to encrypt login credentials. SSL is disabled after login.\n * The client supports login-time SSL even when {@link #isSslEnabled()} is {@code false}. This mode does not validate certificates.\n * <p>Enabling {@link #isSslEnabled() SSL} enables also SSL certificate validation.\n *\n * @author Mark Paluch\n */\npublic interface SslConfiguration {\n\n    /**\n     * @return {@code true} if SSL is enabled. Enabling SSL enables certificate validation. {@code false} to disable SSL.\n     */\n    boolean isSslEnabled();\n\n    /**\n     * Return the {@link SslContext} if {@link #isSslEnabled() SSL is enabled}.\n     *\n     * @return the {@link SslContext}.\n     * @throws GeneralSecurityException if setting up the SSL provider fails.\n     * @throws IllegalStateException    if the SSL configuration is not enabled\n     * @since 0.8.3\n     */\n    SslContext getSslContext() throws GeneralSecurityException;\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/SslEventHandler.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.ssl.SslHandshakeCompletionEvent;\n\n/**\n * Event handler for SSL negotiation events.\n *\n * @author Mark Paluch\n */\nclass SslEventHandler extends ChannelDuplexHandler {\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n\n        if (evt == SslHandshakeCompletionEvent.SUCCESS) {\n            ctx.channel().pipeline().fireUserEventTriggered(SslState.NEGOTIATED);\n        }\n\n        if (evt == SslState.NEGOTIATED) {\n            ctx.fireChannelRead(SslState.NEGOTIATED);\n        }\n\n        super.userEventTriggered(ctx, evt);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/SslState.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.token.Login7;\n\n/**\n * Represents the SSL state aspect of a connection.\n *\n * @author Mark Paluch\n */\npublic enum SslState implements Message {\n\n    /**\n     * SSL not enabled (default).\n     */\n    OFF,\n\n    /**\n     * SSL handshake negotiated.\n     */\n    NEGOTIATED,\n\n    /**\n     * SSL requested during the {@link Login7} message only.\n     */\n    LOGIN_ONLY,\n\n    /**\n     * SSL requested for the entire connection.\n     */\n    CONNECTION,\n\n    /**\n     * SSL disabled once it was used for the login message.\n     */\n    AFTER_LOGIN_ONLY\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/TdsSslHandler.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.CompositeByteBuf;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.ssl.SslHandler;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.client.ConnectionState;\nimport io.r2dbc.mssql.client.TdsEncoder;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.PacketIdProvider;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.ContextualTdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\nimport reactor.util.annotation.Nullable;\n\nimport javax.net.ssl.SSLEngine;\nimport java.security.GeneralSecurityException;\n\n/**\n * SSL handling for TDS connections.\n * <p>\n * This handler wraps or passes thru read and write data depending on the {@link SslState}. Because TDS requires header\n * wrapping, we're not mounting the {@link SslHandler} directly into the pipeline but delegating read and write events\n * to it.<p>\n * This {@link ChannelHandler} supports also full SSL mode and requires to be reordered once the handshake is done therefor it's marked as {@code @Sharable}.\n *\n * @author Mark Paluch\n * @see SslHandler\n * @see ConnectionState\n */\n@ChannelHandler.Sharable\npublic final class TdsSslHandler extends ChannelDuplexHandler {\n\n    private static final Logger LOGGER = Loggers.getLogger(TdsSslHandler.class);\n\n    public static final boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();\n\n    private final ConnectionContext connectionContext;\n\n    private final PacketIdProvider packetIdProvider;\n\n    private final SslConfiguration sslConfiguration;\n\n    private volatile SslHandler sslHandler;\n\n    private ChannelHandlerContext context;\n\n    private ByteBuf outputBuffer;\n\n    private SslState state = SslState.OFF;\n\n    private boolean handshakeDone;\n\n    @Nullable\n    private Chunk chunk;\n\n    /**\n     * Creates a new {@link TdsSslHandler}.\n     *\n     * @param packetIdProvider the {@link PacketIdProvider} to create {@link Header}s to wrap the SSL handshake.\n     * @param sslConfiguration SSL config.\n     * @param context          Value object capturing diagnostic connection context.\n     */\n    public TdsSslHandler(PacketIdProvider packetIdProvider, SslConfiguration sslConfiguration, ConnectionContext context) {\n\n        Assert.requireNonNull(packetIdProvider, \"PacketIdProvider must not be null\");\n        Assert.requireNonNull(sslConfiguration, \"SslConfiguration must not be null\");\n        Assert.requireNonNull(context, \"ConnectionContext must not be null\");\n\n        this.packetIdProvider = packetIdProvider;\n        this.sslConfiguration = sslConfiguration;\n        this.connectionContext = context;\n    }\n\n    void setSslHandler(SslHandler sslHandler) {\n        this.sslHandler = sslHandler;\n    }\n\n    void setState(SslState state) {\n        this.state = state;\n    }\n\n    /**\n     * Create the {@link SslHandler}.\n     *\n     * @param sslConfiguration the SSL configuration.\n     * @return the configured {@link SslHandler}.\n     * @throws GeneralSecurityException thrown on security API errors.\n     */\n    private static SslHandler createSslHandler(SslConfiguration sslConfiguration, ByteBufAllocator allocator) throws GeneralSecurityException {\n\n        SSLEngine sslEngine = sslConfiguration.getSslContext()\n            .newEngine(allocator);\n\n        return new SslHandler(sslEngine);\n    }\n\n    /**\n     * Lazily register {@link SslHandler} if needed.\n     *\n     * @param ctx the {@link ChannelHandlerContext} for which the event is made.\n     * @param evt the user event.\n     * @throws Exception thrown if an error occurs\n     */\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n\n        if (evt == SslState.LOGIN_ONLY || evt == SslState.CONNECTION) {\n\n            this.state = (SslState) evt;\n            this.sslHandler = createSslHandler(this.sslConfiguration, ctx.alloc());\n\n            LOGGER.debug(this.connectionContext.getMessage(\"Registering Context Proxy and SSL Event Handlers to propagate SSL events to channelRead()\"));\n            ctx.pipeline().addAfter(getClass().getName(), ContextProxy.class.getName(), new ContextProxy());\n            ctx.pipeline().addAfter(ContextProxy.class.getName(), SslEventHandler.class.getName(), new SslEventHandler());\n\n            this.context = ctx.channel().pipeline().context(ContextProxy.class.getName());\n\n            ctx.write(HeaderOptions.create(Type.PRE_LOGIN, Status.empty()));\n\n            this.sslHandler.handlerAdded(this.context);\n        }\n\n        if (evt == SslState.NEGOTIATED) {\n\n            LOGGER.debug(this.connectionContext.getMessage(\"SSL Handshake done\"));\n\n            ctx.write(TdsEncoder.ResetHeader.INSTANCE, ctx.voidPromise());\n            this.handshakeDone = true;\n\n            // Reorder handlers:\n            // 1. Apply TLS first\n            // 2. Logging next\n            // 3. TDS\n            if (this.state == SslState.CONNECTION) {\n\n                LOGGER.debug(this.connectionContext.getMessage(\"Reordering handlers for full SSL usage\"));\n\n                ctx.pipeline().remove(this);\n                ctx.pipeline().addFirst(this);\n            }\n        }\n\n        super.userEventTriggered(ctx, evt);\n    }\n\n    @Override\n    public void handlerAdded(ChannelHandlerContext ctx) {\n        this.outputBuffer = ctx.alloc().buffer();\n    }\n\n    @Override\n    public void handlerRemoved(ChannelHandlerContext ctx) {\n\n        if (this.outputBuffer != null) {\n            this.outputBuffer.release();\n            this.outputBuffer = null;\n        }\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n\n        if (this.sslHandler != null) {\n            this.sslHandler.channelInactive(ctx);\n        }\n\n        Chunk chunk = this.chunk;\n        if (chunk != null) {\n            chunk.fullMessage.release();\n            chunk.aggregator.release();\n            this.chunk = null;\n        }\n    }\n\n    /**\n     * Write data either directly (SSL disabled), to an intermediate buffer for {@link Header} wrapping, or via\n     * {@link SslHandler} for entire packet encryption without prepending a header. Note that {@link SslState#LOGIN_ONLY}\n     * is swapped to {@link SslState#AFTER_LOGIN_ONLY} once login payload is written. We don't check actually whether the\n     * payload is a login packet but rely on higher level layers to send the appropriate data.\n     *\n     * @param ctx     the {@link ChannelHandlerContext} for which the write operation is made\n     * @param msg     the message to write\n     * @param promise the {@link ChannelPromise} to notify once the operation completes\n     * @throws Exception thrown if an error occurs\n     */\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n\n        if (this.handshakeDone && (this.state == SslState.NEGOTIATED || this.state == SslState.LOGIN_ONLY || this.state == SslState.CONNECTION)) {\n\n            msg = unwrap(ctx.alloc(), msg);\n\n            this.sslHandler.write(ctx, msg, promise);\n            this.sslHandler.flush(ctx);\n\n            if (this.state == SslState.LOGIN_ONLY) {\n                this.state = SslState.AFTER_LOGIN_ONLY;\n            }\n\n            return;\n        }\n\n        if (requiresWrapping()) {\n\n            if (DEBUG_ENABLED) {\n                LOGGER.debug(this.connectionContext.getMessage(\"Write wrapping: Append to output buffer\"));\n            }\n\n            ByteBuf sslPayload = (ByteBuf) msg;\n\n            this.outputBuffer.writeBytes(sslPayload);\n\n            sslPayload.release();\n        } else {\n            super.write(ctx, msg, promise);\n        }\n    }\n\n    private Object unwrap(ByteBufAllocator allocator, Object msg) {\n\n        if (msg instanceof ContextualTdsFragment) {\n\n            ContextualTdsFragment tdsFragment = (ContextualTdsFragment) msg;\n\n            HeaderOptions headerOptions = tdsFragment.getHeaderOptions();\n            Status eom = headerOptions.getStatus().and(Status.StatusBit.EOM);\n            Header header = new Header(headerOptions.getType(), eom, Header.LENGTH + tdsFragment.getByteBuf().readableBytes(),\n                0, this.packetIdProvider.nextPacketId(), 0);\n\n            ByteBuf buffer = allocator.buffer(header.getLength());\n            header.encode(buffer);\n            buffer.writeBytes(tdsFragment.getByteBuf());\n            tdsFragment.getByteBuf().release();\n\n            // unwrap ByteBuffer so we can write it using SSL.\n            return buffer;\n        }\n\n        if (msg instanceof TdsFragment) {\n            // unwrap ByteBuffer so we can write it using SSL.\n            return ((TdsFragment) msg).getByteBuf();\n        }\n\n        return msg;\n    }\n\n    /**\n     * Wrap SSL handshake in prelogin {@link Header}s. Delaying write to flush instead of writing each packet in a single\n     * header.\n     *\n     * @param ctx the {@link ChannelHandlerContext} for which the flush operation is made.\n     * @throws Exception thrown if an error occurs.\n     */\n    @Override\n    public void flush(ChannelHandlerContext ctx) throws Exception {\n\n        if (requiresWrapping()) {\n\n            if (DEBUG_ENABLED) {\n                LOGGER.debug(this.connectionContext.getMessage(\"Write wrapping: Flushing output buffer and enable auto-read\"));\n            }\n\n            ByteBuf message = this.outputBuffer;\n            this.outputBuffer = ctx.alloc().buffer();\n\n            ctx.writeAndFlush(message);\n            ctx.channel().config().setAutoRead(true);\n        } else {\n            super.flush(ctx);\n        }\n    }\n\n    /**\n     * SSL quirk: Make sure to flush the output buffer during SSL handshake. The SSL handshake can end in the read\n     * underrun that wants to read from the transport. We need to make sure that write requests (that don't get flushed\n     * explicitly) are sent so we can expect eventually a read.\n     *\n     * @param ctx the {@link ChannelHandlerContext} for which the read complete operation is made.\n     * @throws Exception thrown if an error occurs.\n     */\n    @Override\n    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {\n\n        if (isInHandshake() && this.outputBuffer.readableBytes() > 0) {\n            flush(ctx);\n        }\n\n        super.channelReadComplete(ctx);\n    }\n\n    /**\n     * Route read events to the {@link SslHandler}. Strip off {@link Header} during handshake.\n     *\n     * @param ctx the {@link ChannelHandlerContext} for which the read operation is made.\n     * @param msg the message to read.\n     * @throws Exception thrown if an error occurs.\n     */\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n\n        if (isInHandshake()) {\n            ByteBuf buffer = (ByteBuf) msg;\n\n            Chunk chunk = this.chunk;\n            if (chunk != null || Header.canDecode(buffer)) {\n\n                Header header;\n                if (chunk == null) {\n\n                    header = Header.decode(buffer);\n\n                    // sub-chunk read\n                    if (!Chunk.isCompletePacketAvailable(header, buffer)) {\n\n                        ByteBuf defragmented = buffer.alloc().buffer(header.getLength());\n                        defragmented.writeBytes(buffer);\n                        buffer.release();\n\n                        this.chunk = new Chunk(header, defragmented, buffer.alloc().compositeBuffer());\n                        ctx.read();\n                        return;\n                    }\n                } else {\n\n                    chunk.defragment(buffer);\n\n                    if (!chunk.isCompleteHandshakeAvailable()) {\n                        return;\n                    }\n\n                    buffer = chunk.fullMessage;\n                    header = chunk.header;\n                    this.chunk.aggregator.release();\n                    this.chunk = null;\n                }\n\n                if (header.getType() == Type.PRE_LOGIN) {\n                    this.sslHandler.channelRead(this.context, buffer);\n                }\n\n                if (header.is(Status.StatusBit.IGNORE)) {\n                    return;\n                }\n            }\n            return;\n        }\n\n        if (this.handshakeDone && this.state == SslState.CONNECTION) {\n            this.sslHandler.channelRead(ctx, msg);\n            return;\n        }\n\n        super.channelRead(ctx, msg);\n    }\n\n    private boolean isInHandshake() {\n        return requiresWrapping() && !this.handshakeDone;\n    }\n\n    private boolean requiresWrapping() {\n        return (this.state == SslState.LOGIN_ONLY || this.state == SslState.CONNECTION);\n    }\n\n    /**\n     * Chunk remainder for incomplete packet reads.\n     */\n    static class Chunk {\n\n        Header header;\n\n        final ByteBuf fullMessage;\n\n        final CompositeByteBuf aggregator;\n\n        int decoded = 0;\n\n        Chunk(Header header, ByteBuf fullMessage, CompositeByteBuf aggregator) {\n            this.header = header;\n            this.fullMessage = fullMessage;\n            this.aggregator = aggregator;\n        }\n\n        /**\n         * Gradually defragment chunks into a complete message. Retain {@code chunk} across defragmenation attempts.\n         *\n         * @param chunk\n         */\n        void defragment(ByteBuf chunk) {\n\n            this.aggregator.addComponent(true, chunk);\n\n            while (this.aggregator.isReadable()) {\n\n                int remainder = getRemainingLength();\n\n                if (this.aggregator.readableBytes() >= remainder) {\n                    this.fullMessage.writeBytes(this.aggregator, remainder);\n                } else {\n                    break;\n                }\n\n                if (Header.canDecode(this.aggregator)) {\n                    updateHeader(Header.decode(this.aggregator));\n                } else {\n                    break;\n                }\n\n                if (isCompleteHandshakeAvailable()) {\n                    break;\n                }\n            }\n        }\n\n        void updateHeader(Header header) {\n\n            this.decoded = this.decoded + (this.header.getLength() - Header.LENGTH);\n            this.header = header;\n        }\n\n        /**\n         * Check if the full handshake arrived.\n         *\n         * @return\n         */\n        boolean isCompleteHandshakeAvailable() {\n            return this.header.is(Status.StatusBit.EOM) && getRemainingLength() <= 0;\n        }\n\n        int getRemainingLength() {\n            return this.header.getLength() - ((this.fullMessage.readableBytes() - this.decoded) + Header.LENGTH);\n        }\n\n        /**\n         * Check if the full packet arrived. Since we've already read the header, we need to add {@link Header#LENGTH} to the calculation.\n         *\n         * @param header TDS header.\n         * @param buffer body buffer.\n         * @return\n         */\n        static boolean isCompletePacketAvailable(Header header, ByteBuf buffer) {\n            return (buffer.readableBytes() + Header.LENGTH) >= header.getLength();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/TrustAllTrustManager.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport io.r2dbc.mssql.message.token.Login7;\n\nimport javax.net.ssl.X509TrustManager;\nimport java.security.cert.X509Certificate;\n\n/**\n * Accepts all {@link X509Certificate}s. Used when SSL is not enabled on the client side to allow SSL during {@link Login7} exchange.\n *\n * @author Mark Paluch\n */\npublic enum TrustAllTrustManager implements X509TrustManager {\n\n    INSTANCE;\n\n    public void checkClientTrusted(X509Certificate[] chain, String authType) {\n    }\n\n    public void checkServerTrusted(X509Certificate[] chain, String authType) {\n    }\n\n    public X509Certificate[] getAcceptedIssuers() {\n        return new X509Certificate[0];\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/X509CertificateUtil.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport reactor.util.annotation.Nullable;\n\nimport java.net.IDN;\nimport java.security.cert.CertificateParsingException;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Locale;\n\n/**\n * Miscellaneous {@link X509Certificate} utility methods.\n *\n * @author Mark Paluch\n */\nfinal class X509CertificateUtil {\n\n    /**\n     * Extract the host name from the given {@link X509Certificate}.\n     *\n     * @param cert the certificate.\n     * @return the extracted host name.\n     */\n    @Nullable\n    static String getHostName(X509Certificate cert) {\n        return extractCommonName(cert.getSubjectX500Principal().getName(\"canonical\"));\n    }\n\n    /**\n     * Extract common name from RFC 2253 format.\n     * Returns the common name if successful, {@code null} if failed to find the common name.\n     *\n     * @param distinguishedName the DN\n     * @return the extracted host name.\n     */\n    @Nullable\n    private static String extractCommonName(String distinguishedName) {\n\n        int index;\n        // canonical name converts entire name to lowercase\n        index = distinguishedName.indexOf(\"cn=\");\n        if (index == -1) {\n            return null;\n        }\n\n        distinguishedName = distinguishedName.substring(index + 3);\n        // Parse until a comma or end is reached\n        // Note the parser will handle gracefully (essentially will return empty string) , inside the quotes (e.g\n        // cn=\"Foo, bar\") however\n        // RFC 952 says that the hostName cant have commas however the parser should not (and will not) crash if it\n        // sees a , within quotes.\n        for (index = 0; index < distinguishedName.length(); index++) {\n            if (distinguishedName.charAt(index) == ',') {\n                break;\n            }\n        }\n\n        String commonName = distinguishedName.substring(0, index);\n        // strip any quotes\n        if (commonName.length() > 1 && ('\\\"' == commonName.charAt(0))) {\n            if ('\\\"' == commonName.charAt(commonName.length() - 1)) {\n                commonName = commonName.substring(1, commonName.length() - 1);\n            } else {\n                // Be safe the name is not ended in \" return null so the common Name wont match\n                commonName = null;\n            }\n        }\n\n        return commonName;\n    }\n\n    /**\n     * Extract SubjectAlternativeNames from the given {@link X509Certificate} and return these as {@link IDN}-unicode {@link List} of {@link String hostnames}.\n     *\n     * @param cert the certificate.\n     * @return {@link IDN}-unicode {@link List} of {@link String hostnames}.\n     * @throws CertificateParsingException if the extension cannot be decoded.\n     */\n    static List<String> getSubjectAlternativeNames(X509Certificate cert) throws CertificateParsingException {\n\n        List<String> san = new ArrayList<>();\n        Collection<List<?>> sanCollection = cert.getSubjectAlternativeNames();\n\n        if (sanCollection == null) {\n            return san;\n        }\n\n        // find a subjectAlternateName entry corresponding to DNS Name\n        for (List<?> sanEntry : sanCollection) {\n\n            if (sanEntry == null || sanEntry.size() < 2) {\n                continue;\n            }\n            Object key = sanEntry.get(0);\n            Object value = sanEntry.get(1);\n\n            // Documentation(http://download.oracle.com/javase/6/docs/api/java/security/cert/X509Certificate.html):\n            // \"Note that the Collection returned may contain\n            // more than one name of the same type.\"\n            // So, more than one entry of dnsNameType can be present.\n            // Java docs guarantee that the first entry in the list will be an integer.\n            // 2 is the sequence no of a dnsName\n            if (key instanceof Integer && ((Integer) key == 2) && value instanceof String) {\n\n                // As per RFC2459, the DNSName will be in the\n                // \"preferred name syntax\" as specified by RFC\n                // 1034 and the name can be in upper or lower case.\n                // And no significance is attached to case.\n                // Java docs guarantee that the second entry in the list\n                // will be a string for dnsName\n                String dnsNameInSANCert = (String) value;\n\n                // Use English locale to avoid Turkish i issues.\n                dnsNameInSANCert = dnsNameInSANCert.toLowerCase(Locale.ENGLISH);\n                san.add(IDN.toUnicode(dnsNameInSANCert));\n            }\n        }\n\n        return san;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/client/ssl/package-info.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * SSL support classes.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.client.ssl;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/AbstractCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Abstract codec class that provides a basis for all concrete\n * implementations of a  {@link Codec}.\n *\n * @param <T> the type that is handled by this {@link Codec}.\n */\nabstract class AbstractCodec<T> implements Codec<T> {\n\n    private final Class<T> type;\n\n    /**\n     * Creates a new {@link AbstractCodec}.\n     *\n     * @param type the type handled by this codec.\n     */\n    AbstractCodec(Class<T> type) {\n        this.type = Assert.requireNonNull(type, \"Type must not be null\");\n    }\n\n    @Override\n    public boolean canEncode(Object value) {\n\n        Assert.requireNonNull(value, \"Value must not be null\");\n\n        return this.type.isInstance(value);\n    }\n\n    @Override\n    public final Encoded encode(ByteBufAllocator allocator, RpcParameterContext context, T value) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n        Assert.requireNonNull(context, \"RpcParameterContext must not be null\");\n        Assert.requireNonNull(value, \"Value must not be null\");\n\n        return doEncode(allocator, context, value);\n    }\n\n    @Override\n    public final boolean canEncodeNull(Class<?> type) {\n\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        return this.type.isAssignableFrom(type);\n    }\n\n    @Override\n    public final Encoded encodeNull(ByteBufAllocator allocator) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n\n        return doEncodeNull(allocator);\n    }\n\n    @Override\n    public final boolean canDecode(Decodable decodable, Class<?> type) {\n\n        Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        return type.isAssignableFrom(this.type) &&\n            doCanDecode(decodable.getType());\n    }\n\n    @Nullable\n    public T decode(@Nullable ByteBuf buffer, Decodable decodable, Class<? extends T> type) {\n\n        Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        if (buffer == null) {\n            return null;\n        }\n\n        Length length = Length.decode(buffer, decodable.getType());\n        return doDecode(buffer, length, decodable.getType(), type);\n    }\n\n    @Override\n    public Class<T> getType() {\n        return this.type;\n    }\n\n    /**\n     * @param allocator the allocator to allocate encoding buffers.\n     * @param context   parameter context.\n     * @param value     the  {@code value}.\n     * @return the encoded value.\n     */\n    abstract Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, T value);\n\n    /**\n     * Encode a {@code null} value.\n     *\n     * @param allocator the allocator to allocate encoding buffers.\n     * @return the encoded {@code null} value.\n     */\n    abstract Encoded doEncodeNull(ByteBufAllocator allocator);\n\n    /**\n     * Determine whether this {@link Codec} is capable of decoding column values based on the given {@link TypeInformation}.\n     *\n     * @param typeInformation the column type.\n     * @return {@code true} if this codec is able to decode values of {@link TypeInformation}.\n     */\n    abstract boolean doCanDecode(TypeInformation typeInformation);\n\n    /**\n     * Decode the {@link ByteBuf data} into the {@link Class value type}.\n     *\n     * @param buffer    the data buffer.\n     * @param length    length of the column data.\n     * @param type      the type descriptor.\n     * @param valueType the desired value type.\n     * @return the decoded value. Can be {@code null} if the column value is {@code null}.\n     */\n    @Nullable\n    abstract T doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends T> valueType);\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/AbstractNumericCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.util.EnumSet;\nimport java.util.Set;\nimport java.util.function.Function;\n\n/**\n * Abstract codec class that provides a basis for concrete\n * implementations of a {@link Codec} for integer numeric data types.\n *\n * @author Mark Paluch\n */\nabstract class AbstractNumericCodec<T> extends AbstractCodec<T> {\n\n    /**\n     * Length in bytes required to represent {@literal BIGINT}.\n     */\n    static final int SIZE_BIGINT = 8;\n\n    /**\n     * Length in bytes required to represent {@literal INT}.\n     */\n    static final int SIZE_INT = 4;\n\n    /**\n     * Length in bytes required to represent {@literal SMALLINT}.\n     */\n    static final int SIZE_SMALL_INT = 2;\n\n    /**\n     * Length in bytes required to represent {@literal TINYINT}.\n     */\n    static final int SIZE_TINY_INT = 1;\n\n    private static final Set<SqlServerType> SUPPORTED_TYPES = EnumSet.of(SqlServerType.BIT, SqlServerType.TINYINT, SqlServerType.SMALLINT, SqlServerType.INTEGER, SqlServerType.BIGINT,\n        SqlServerType.DECIMAL, SqlServerType.NUMERIC);\n\n    private final LongToObjectFunction<T> converter;\n\n    AbstractNumericCodec(Class<T> type, LongToObjectFunction<T> converter) {\n        super(type);\n        this.converter = converter;\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return SUPPORTED_TYPES.contains(serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return SUPPORTED_TYPES.contains(typeInformation.getServerType());\n    }\n\n    @Override\n    T doDecode(ByteBuf buffer, Length length, TypeInformation typeInformation, Class<? extends T> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        // TODO how to deal with precission loss?\n        if (typeInformation.getServerType() == SqlServerType.DECIMAL || typeInformation.getServerType() == SqlServerType.NUMERIC) {\n            return this.converter.apply(decodeDecimal(buffer, length.getLength(), typeInformation.getScale()).longValue());\n        }\n\n        switch (length.getLength()) {\n            case SIZE_BIGINT:\n                return this.converter.apply(Decode.bigint(buffer));\n            case SIZE_INT:\n                return this.converter.apply(Decode.asInt(buffer));\n            case SIZE_SMALL_INT:\n                return this.converter.apply(Decode.smallInt(buffer));\n            case SIZE_TINY_INT:\n                return this.converter.apply(Decode.tinyInt(buffer));\n            default:\n                throw ProtocolException.invalidTds(String.format(\"Unexpected value length: %d\", length.getLength()));\n        }\n    }\n\n    static BigDecimal decodeDecimal(ByteBuf buffer, int length, int scale) {\n\n        byte signByte = buffer.readByte();\n        int sign = (0 == signByte) ? -1 : 1;\n        byte[] magnitude = new byte[length - 1];\n\n        // read magnitude LE\n        for (int i = 0; i < magnitude.length; i++) {\n            magnitude[magnitude.length - 1 - i] = buffer.readByte();\n        }\n\n        return new BigDecimal(new BigInteger(sign, magnitude), scale);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeNull(allocator, serverType);\n    }\n\n    /**\n     * Represents a function that produces a object-valued result.\n     *\n     * @param <T> the type of the input to the function\n     * @see Function\n     */\n    @FunctionalInterface\n    interface LongToObjectFunction<T> {\n\n        /**\n         * Applies this function to the given argument.\n         *\n         * @param value the function argument\n         * @return the function result\n         */\n        T apply(long value);\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/BigIntegerCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\n\n/**\n * Codec for numeric values that are represented as {@link BigInteger}.\n *\n * <ul>\n * <li>Server types: Integer numbers</li>\n * <li>Java type: {@link BigInteger}</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class BigIntegerCodec extends AbstractNumericCodec<BigInteger> {\n\n    /**\n     * Singleton instance.\n     */\n    static final BigIntegerCodec INSTANCE = new BigIntegerCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeNull(alloc, SqlServerType.BIGINT));\n\n    private BigIntegerCodec() {\n        super(BigInteger.class, BigInteger::valueOf);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, BigInteger value) {\n        return DecimalCodec.INSTANCE.encode(allocator, context, new BigDecimal(value));\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.BIGINT);\n    }\n\n    @Override\n    BigInteger doDecode(ByteBuf buffer, Length length, TypeInformation typeInformation, Class<? extends BigInteger> valueType) {\n\n        if (typeInformation.getServerType() == SqlServerType.NUMERIC || typeInformation.getServerType() == SqlServerType.DECIMAL) {\n            BigDecimal decimal = DecimalCodec.INSTANCE.doDecode(buffer, length, typeInformation, BigDecimal.class);\n\n            if (decimal == null) {\n                return null;\n            }\n\n            return decimal.toBigInteger();\n        }\n\n        return super.doDecode(buffer, length, typeInformation, valueType);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/BinaryCodec.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.*;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Blob;\nimport reactor.core.publisher.Mono;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.ByteBuffer;\nimport java.util.EnumSet;\nimport java.util.Set;\nimport java.util.function.IntFunction;\nimport java.util.function.Supplier;\n\n/**\n * Codec for binary values that are represented as {@code byte[]} or {@link ByteBuffer}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#BINARY},  {@link SqlServerType#VARBINARY}, {@link SqlServerType#VARBINARYMAX}, and {@link SqlServerType#IMAGE}.</li>\n * <li>Java type: {@code byte[]}, {@link ByteBuffer}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nclass BinaryCodec implements Codec<Object> {\n\n    /**\n     * Singleton instance.\n     */\n    public static final BinaryCodec INSTANCE = new BinaryCodec();\n\n    private static final byte[] NULL = ByteArray.fromBuffer((alloc) ->\n    {\n        ByteBuf buffer = alloc.buffer(4);\n        Encode.uShort(buffer, SqlServerType.VARBINARY.getMaxLength());\n        Encode.uShort(buffer, Length.USHORT_NULL);\n\n        return buffer;\n    });\n\n    private static final Set<SqlServerType> SUPPORTED_TYPES = EnumSet.of(SqlServerType.BINARY, SqlServerType.VARBINARY,\n            SqlServerType.VARBINARYMAX, SqlServerType.IMAGE);\n\n    private BinaryCodec() {\n    }\n\n    @Override\n    public boolean canEncode(Object value) {\n\n        Assert.requireNonNull(value, \"Value must not be null\");\n\n        return value instanceof byte[] || value instanceof ByteBuffer;\n    }\n\n    @Override\n    public Encoded encode(ByteBufAllocator allocator, RpcParameterContext context, Object value) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n        Assert.requireNonNull(context, \"RpcParameterContext must not be null\");\n        Assert.requireNonNull(value, \"Value must not be null\");\n\n        int length;\n        if (value instanceof byte[]) {\n\n            byte[] bytes = (byte[]) value;\n\n            if (exceedsBigVarbinary(bytes.length)) {\n                return BlobCodec.INSTANCE.encode(allocator, context, Blob.from(Mono.just(ByteBuffer.wrap(bytes))));\n            }\n            length = bytes.length;\n        } else {\n\n            ByteBuffer bytes = (ByteBuffer) value;\n\n            if (exceedsBigVarbinary(bytes.remaining())) {\n                return BlobCodec.INSTANCE.encode(allocator, context, Blob.from(Mono.just(bytes)));\n            }\n\n            length = bytes.remaining();\n        }\n\n\n        IntFunction<ByteBuf> encoder = actualLength -> {\n            ByteBuf buffer;\n\n            if (value instanceof byte[]) {\n\n                byte[] bytes = (byte[]) value;\n\n                buffer = RpcEncoding.prepareBuffer(allocator, TdsDataType.BIGVARBINARY.getLengthStrategy(), SqlServerType.VARBINARY.getMaxLength(), actualLength);\n                buffer.writeBytes(bytes);\n            } else {\n\n                ByteBuffer bytes = (ByteBuffer) value;\n\n                buffer = RpcEncoding.prepareBuffer(allocator, TdsDataType.BIGVARBINARY.getLengthStrategy(), SqlServerType.VARBINARY.getMaxLength(), actualLength);\n                buffer.writeBytes(bytes.asReadOnlyBuffer());\n            }\n            return buffer;\n        };\n\n        return new VarbinaryEncoded(TdsDataType.BIGVARBINARY, Encoded.ofLengthAware(length, encoder));\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return SUPPORTED_TYPES.contains(serverType);\n    }\n\n    @Override\n    public boolean canEncodeNull(Class<?> type) {\n\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        // Accept subtypes of ByteBuffer\n        return type.isAssignableFrom(byte[].class) || ByteBuffer.class.isAssignableFrom(type);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public Class<Object> getType() {\n        return (Class) ByteBuffer.class;\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator) {\n        return new VarbinaryEncoded(TdsDataType.BIGVARBINARY, () -> Unpooled.wrappedBuffer(NULL));\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return new VarbinaryEncoded(TdsDataType.BIGVARBINARY, () -> Unpooled.wrappedBuffer(NULL));\n    }\n\n    @Override\n    public boolean canDecode(Decodable decodable, Class<?> type) {\n\n        Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        return SUPPORTED_TYPES.contains(decodable.getType().getServerType()) && canEncodeNull(type);\n    }\n\n    @Nullable\n    public Object decode(@Nullable ByteBuf buffer, Decodable decodable, Class<? extends Object> type) {\n\n        Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        if (buffer == null) {\n            return null;\n        }\n\n        Length length;\n\n        if (decodable.getType().getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n\n            PlpLength plpLength = PlpLength.decode(buffer, decodable.getType());\n            length = Length.of(Math.toIntExact(plpLength.getLength()), plpLength.isNull());\n        } else {\n            length = Length.decode(buffer, decodable.getType());\n        }\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        return doDecode(buffer, length, decodable.getType(), type);\n    }\n\n    Object doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends Object> valueType) {\n\n        byte[] bytes = new byte[length.getLength()];\n\n        if (type.getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n\n            int index = 0;\n            while (buffer.isReadable()) {\n\n                Length chunkLength = Length.decode(buffer, type);\n                buffer.readBytes(bytes, index, chunkLength.getLength());\n                index += chunkLength.getLength();\n            }\n        } else {\n            buffer.readBytes(bytes);\n        }\n\n        // accept Object.class and ByteBuffer subclasses\n        if (valueType.isAssignableFrom(ByteBuffer.class) || ByteBuffer.class.isAssignableFrom(valueType)) {\n            return ByteBuffer.wrap(bytes);\n        }\n\n        return bytes;\n    }\n\n    static class VarbinaryEncoded extends RpcEncoding.HintedEncoded {\n\n        private static final String FORMAL_TYPE = SqlServerType.VARBINARY + \"(\" + TypeUtils.SHORT_VARTYPE_MAX_BYTES + \")\";\n\n        VarbinaryEncoded(TdsDataType dataType, Supplier<ByteBuf> value) {\n            super(dataType, SqlServerType.VARBINARY, value);\n        }\n\n        @Override\n        public String getFormalType() {\n            return FORMAL_TYPE;\n        }\n\n    }\n\n    private static boolean exceedsBigVarbinary(int length) {\n        return length > TypeUtils.SHORT_VARTYPE_MAX_BYTES;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/BlobCodec.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.type.*;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.ReferenceCountUtil;\nimport io.r2dbc.spi.Blob;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.ByteBuffer;\nimport java.util.*;\n\n/**\n * Codec for binary values that are represented as {@link Blob}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#BINARY},  {@link SqlServerType#VARBINARY}, {@link SqlServerType#VARBINARYMAX}, and {@link SqlServerType#IMAGE}.</li>\n * <li>Java type: {@link Blob}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n * @author Tomasz Marciniak\n */\npublic class BlobCodec extends AbstractCodec<Blob> {\n\n    /**\n     * Singleton instance.\n     */\n    public static final BlobCodec INSTANCE = new BlobCodec();\n\n    private static final Set<SqlServerType> SUPPORTED_TYPES = EnumSet.of(SqlServerType.BINARY, SqlServerType.VARBINARY, SqlServerType.VARBINARYMAX, SqlServerType.IMAGE);\n\n    private BlobCodec() {\n        super(Blob.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Blob value) {\n        return new PlpEncoded(SqlServerType.VARBINARYMAX, allocator, Flux.from(value.stream()).map(Unpooled::wrappedBuffer), () -> Mono.from(value.discard()).toFuture());\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return SUPPORTED_TYPES.contains(serverType);\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return BinaryCodec.INSTANCE.encodeNull(allocator);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return BinaryCodec.INSTANCE.encodeNull(allocator, serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return SUPPORTED_TYPES.contains(typeInformation.getServerType());\n    }\n\n    @Nullable\n    public Blob decode(@Nullable ByteBuf buffer, Decodable decodable, Class<? extends Blob> type) {\n\n        Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        if (buffer == null) {\n            return null;\n        }\n\n        Length length;\n\n        if (decodable.getType().getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n\n            PlpLength plpLength = PlpLength.decode(buffer, decodable.getType());\n            length = Length.of(Math.toIntExact(plpLength.getLength()), plpLength.isNull());\n        } else {\n            length = Length.decode(buffer, decodable.getType());\n        }\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        return doDecode(buffer, length, decodable.getType(), type);\n    }\n\n    @Override\n    Blob doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends Blob> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        if (type.getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n\n            List<ByteBuf> chunks = new ArrayList<>();\n            while (buffer.isReadable()) {\n\n                Length chunkLength = Length.decode(buffer, type);\n                chunks.add(buffer.readRetainedSlice(chunkLength.getLength()));\n            }\n\n            return new ScalarBlob(chunks);\n        }\n\n        return new ScalarBlob(Collections.singletonList(buffer.readRetainedSlice(length.getLength())));\n    }\n\n    /**\n     * Scalar {@link Blob} backed by an already received and de-chunked {@link List} of {@link ByteBuf}.\n     */\n    static class ScalarBlob implements Blob {\n\n        final List<ByteBuf> buffers;\n\n        ScalarBlob(List<ByteBuf> buffers) {\n            this.buffers = buffers;\n            this.buffers.forEach(byteBuf -> byteBuf.touch(\"ScalarBlob\"));\n        }\n\n        @Override\n        public Publisher<ByteBuffer> stream() {\n\n            return Flux.fromIterable(this.buffers).map(it -> {\n\n                if (!it.isReadable()) {\n                    it.release();\n                    return ByteBuffer.wrap(new byte[0]);\n                }\n\n                ByteBuffer result = ByteBuffer.allocate(it.readableBytes());\n                it.readBytes(result);\n                it.release();\n\n                result.flip();\n                return result;\n            }).doOnDiscard(ByteBuf.class, ReferenceCountUtil::maybeRelease).doOnCancel(() -> {\n                for (ByteBuf buffer : this.buffers) {\n                    ReferenceCountUtil.maybeRelease(buffer);\n                }\n            });\n        }\n\n        @Override\n        public Publisher<Void> discard() {\n\n            return Mono.fromRunnable(() -> {\n\n                for (ByteBuf buffer : this.buffers) {\n                    ReferenceCountUtil.maybeRelease(buffer);\n                }\n            });\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/BooleanCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.SqlServerType;\n\n/**\n * Codec for numeric values that are represented as {@link Boolean}.\n *\n * <ul>\n * <li>Server types: Integer numbers</li>\n * <li>Java type: {@link Boolean}</li>\n * <li>Downcast: {@code true} if the value  is not zero</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class BooleanCodec extends AbstractNumericCodec<Boolean> {\n\n    /**\n     * Singleton instance.\n     */\n    static final BooleanCodec INSTANCE = new BooleanCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeNull(alloc, SqlServerType.TINYINT));\n\n    private BooleanCodec() {\n        super(Boolean.class, value -> value != 0);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Boolean value) {\n        return RpcEncoding.encodeFixed(allocator, SqlServerType.TINYINT, value, (buffer, b) -> Encode.asByte(buffer, b ? 1 : 0));\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.TINYINT);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/ByteArray.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.ByteBufUtil;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.ReferenceCountUtil;\n\nimport java.util.function.Function;\n\n/**\n * Utility to create byte arrays.\n *\n * @author Mark Paluch\n * @author Tomasz Marciniak\n */\nabstract class ByteArray {\n\n    /**\n     * Create a {@code byte[]} from a {@link Function encode function} accepting {@link ByteBufAllocator} returning {@link Encoded}.\n     *\n     * @param encodeFunction encode function.\n     * @return the {@code byte[]} containing the encoded buffer.\n     */\n    static byte[] fromEncoded(Function<ByteBufAllocator, Encoded> encodeFunction) {\n\n        Assert.notNull(encodeFunction, \"Encode Function must not be null\");\n\n        Encoded encoded = encodeFunction.apply(ByteBufAllocator.DEFAULT);\n\n        ByteBuf buffer = encoded.getValue();\n        try {\n            return ByteBufUtil.getBytes(buffer);\n        } finally {\n            encoded.dispose();\n            ReferenceCountUtil.maybeRelease(buffer);\n        }\n    }\n\n    /**\n     * Create a {@code byte[]} from a {@link Function encode function} accepting {@link ByteBufAllocator} returning {@link ByteBuf}.\n     *\n     * @param encodeFunction encode function.\n     * @return the {@code byte[]} containing the encoded buffer.\n     */\n    static byte[] fromBuffer(Function<ByteBufAllocator, ByteBuf> encodeFunction) {\n\n        ByteBuf buffer = encodeFunction.apply(ByteBufAllocator.DEFAULT);\n\n        try {\n            return ByteBufUtil.getBytes(buffer);\n        } finally {\n            buffer.release();\n        }\n    }\n\n    // Utility constructor\n    private ByteArray() {\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/ByteCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.SqlServerType;\n\n/**\n * Codec for numeric values that are represented as {@link Byte}.\n *\n * <ul>\n * <li>Server types: Integer numbers</li>\n * <li>Java type: {@link Byte}</li>\n * <li>Downcast: to {@link Byte}</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class ByteCodec extends AbstractNumericCodec<Byte> {\n\n    /**\n     * Singleton instance.\n     */\n    static final ByteCodec INSTANCE = new ByteCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeNull(alloc, SqlServerType.TINYINT));\n\n    private ByteCodec() {\n        super(Byte.class, value -> (byte) value);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Byte value) {\n        return RpcEncoding.encodeFixed(allocator, SqlServerType.TINYINT, value, Encode::asByte);\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.TINYINT);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/CharacterEncoder.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.codec.RpcParameterContext.CharacterValueContext;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.type.*;\nimport io.r2dbc.spi.Clob;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.CharBuffer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\nimport static io.r2dbc.mssql.message.type.SqlServerType.Category.NCHARACTER;\n\n/**\n * Basic {@link CharSequence} encoding utilities.\n *\n * @author Mark Paluch\n */\nclass CharacterEncoder {\n\n    private static final byte[] NULL = ByteArray.fromBuffer(alloc -> {\n\n        ByteBuf buffer = alloc.buffer(8);\n\n        Encode.uShort(buffer, TypeUtils.SHORT_VARTYPE_MAX_BYTES);\n        Collation.RAW.encode(buffer);\n        Encode.uShort(buffer, -1);\n\n        return buffer;\n    });\n\n    /**\n     * Encode a {@code VARCHAR NULL}.\n     *\n     * @return the {@link Encoded} {@code VARCHAR NULL}.\n     */\n    static Encoded encodeNull(SqlServerType serverType) {\n\n        if (isNational(serverType)) {\n            return new VarcharEncoded(TdsDataType.NVARCHAR, () -> Unpooled.wrappedBuffer(NULL));\n        }\n\n        return new NvarcharEncoded(TdsDataType.NVARCHAR, () -> Unpooled.wrappedBuffer(NULL));\n    }\n\n    /**\n     * Encode a {@link CharSequence} to {@code VARCHAR} or {@code NVARCHAR} depending on {@code sendStringParametersAsUnicode}.\n     *\n     * @return the {@link Encoded} {@link CharSequence}.\n     */\n    static Encoded encodeBigVarchar(ByteBufAllocator allocator, RpcDirection direction, @Nullable SqlServerType serverType, Collation collation, boolean sendStringParametersAsUnicode,\n                                    @Nullable CharSequence value) {\n\n        int initialCapacity = (value != null ? value.length() * 2 : 0) + 7;\n        Function<Boolean, ByteBuf> encoder = unicode -> {\n\n\n            ByteBuf buffer = allocator.buffer(initialCapacity);\n            encodeBigVarchar(buffer, direction, collation, unicode, value);\n            return buffer;\n        };\n\n        if (isNational(serverType) || sendStringParametersAsUnicode) {\n            return new NvarcharEncoded(TdsDataType.NVARCHAR, () -> encoder.apply(true));\n        }\n\n        return new VarcharEncoded(TdsDataType.BIGVARCHAR, Encoded.ofLengthAware(initialCapacity, i -> encoder.apply(false)));\n    }\n\n    /**\n     * Encode a {@link CharSequence} to {@code VARCHAR} or {@code NVARCHAR} depending on {@code sendStringParametersAsUnicode}. Uses either {@code (N)VARCHAR} or {@code (N)VARCHAR(MAX)}, depending\n     * on the string size.\n     */\n    static void encodeBigVarchar(ByteBuf buffer, RpcDirection direction, Collation collation, boolean sendStringParametersAsUnicode, @Nullable CharSequence value) {\n\n        ByteBuf characterData = encodeCharSequence(buffer.alloc(), collation, sendStringParametersAsUnicode, value);\n        int valueLength = characterData.readableBytes();\n        boolean isShortValue = valueLength <= TypeUtils.SHORT_VARTYPE_MAX_BYTES;\n        boolean isNull = value == null;\n\n        // Textual RPC requires a collation. If none is provided, as is the case when\n        // the SSType is non-textual, then use the database collation by default.\n\n        // Use PLP encoding on Yukon and later with long values and OUT parameters\n        boolean usePLP = (!isShortValue || direction == RpcDirection.OUT);\n        if (usePLP) {\n\n            // Send v*max length indicator 0xFFFF.\n            Encode.uShort(buffer, (short) 0xFFFF);\n\n            // Send collation if requested.\n            collation.encode(buffer);\n\n            // Handle null here and return, we're done here if it's null.\n            if (isNull) {\n                // Null header for v*max types is 0xFFFFFFFFFFFFFFFF.\n                Encode.uLongLong(buffer, 0xFFFFFFFFFFFFFFFFL);\n            } else if (Length.UNKNOWN_STREAM_LENGTH == valueLength) {\n                // Append v*max length.\n                // UNKNOWN_PLP_LEN is 0xFFFFFFFFFFFFFFFE\n                Encode.uLongLong(buffer, 0xFFFFFFFFFFFFFFFEL);\n\n                // NOTE: Don't send the first chunk length, this will be calculated by caller.\n            } else {\n                // For v*max types with known length, length is <totallength8><chunklength4>\n                // We're sending same total length as chunk length (as we're sending 1 chunk).\n                Encode.uLongLong(buffer, valueLength);\n            }\n\n            // Send the data.\n            if (!isNull) {\n                if (valueLength > 0) {\n                    Encode.asInt(buffer, valueLength);\n                    buffer.writeBytes(characterData);\n                    characterData.release();\n                }\n            }\n\n            // Send the terminator PLP chunk.\n            Encode.asInt(buffer, 0);\n\n        } else {\n\n            // Write maximum length of data\n            Encode.uShort(buffer, TypeUtils.SHORT_VARTYPE_MAX_BYTES);\n\n            collation.encode(buffer);\n\n            // Write actual length of data\n            Encode.uShort(buffer, valueLength);\n\n            // If length is zero, we're done.\n            if (0 != valueLength) {\n                buffer.writeBytes(characterData);\n                characterData.release();\n            }\n        }\n    }\n\n    private static ByteBuf encodeCharSequence(ByteBufAllocator alloc, Collation collation, boolean sendStringParametersAsUnicode, @Nullable CharSequence value) {\n\n        if (value == null || value.length() == 0) {\n            return Unpooled.EMPTY_BUFFER;\n        }\n\n        if (sendStringParametersAsUnicode) {\n            ByteBuf buffer = alloc.buffer(value.length() * 2);\n            Encode.rpcString(buffer, value);\n            return buffer;\n        }\n\n        ByteBuf buffer = alloc.buffer((int) (value.length() * 1.5));\n        Encode.rpcString(buffer, value, collation.getCharset());\n        return buffer;\n    }\n\n    static Encoded encodePlp(ByteBufAllocator allocator, @Nullable SqlServerType serverType, CharacterValueContext valueContext, CharSequence value) {\n\n        Flux<ByteBuf> binaryStream = Flux.just(value).map(it -> {\n            return encodeCharSequence(allocator, isNational(serverType), valueContext, it);\n        });\n\n        return new PlpEncodedCharacters(getPlpType(serverType, valueContext), valueContext.getCollation(), allocator, binaryStream, () -> {\n        });\n    }\n\n    private static boolean isNational(@Nullable SqlServerType serverType) {\n        return serverType != null && serverType.getCategory() == NCHARACTER;\n    }\n\n    static Encoded encodePlp(ByteBufAllocator allocator, @Nullable SqlServerType serverType, CharacterValueContext valueContext, Clob value) {\n\n        Flux<ByteBuf> binaryStream = Flux.from(value.stream()).map(it -> {\n            return encodeCharSequence(allocator, isNational(serverType), valueContext, it);\n        });\n\n        return new PlpEncodedCharacters(getPlpType(serverType, valueContext), valueContext.getCollation(), allocator, binaryStream, () -> Mono.from(value.discard()).toFuture());\n    }\n\n    private static SqlServerType getPlpType(@Nullable SqlServerType serverType, CharacterValueContext valueContext) {\n        return isNational(serverType) || valueContext.isSendStringParametersAsUnicode() ? SqlServerType.NVARCHARMAX : SqlServerType.VARCHARMAX;\n    }\n\n    private static ByteBuf encodeCharSequence(ByteBufAllocator allocator, boolean isNational, CharacterValueContext valueContext, CharSequence it) {\n        return ByteBufUtil.encodeString(allocator, CharBuffer.wrap(it), isNational || valueContext.isSendStringParametersAsUnicode() ? ServerCharset.UNICODE.charset() :\n                valueContext.getCollation().getCharset());\n    }\n\n    private static class NvarcharEncoded extends RpcEncoding.HintedEncoded {\n\n        private static final String FORMAL_TYPE = SqlServerType.NVARCHAR + \"(\" + (TypeUtils.SHORT_VARTYPE_MAX_BYTES / 2) + \")\";\n\n        NvarcharEncoded(TdsDataType dataType, Supplier<ByteBuf> value) {\n            super(dataType, SqlServerType.NVARCHAR, value);\n        }\n\n        @Override\n        public String getFormalType() {\n            return FORMAL_TYPE;\n        }\n\n    }\n\n    private static class VarcharEncoded extends RpcEncoding.HintedEncoded {\n\n        private static final String FORMAL_TYPE = SqlServerType.VARCHAR + \"(\" + TypeUtils.SHORT_VARTYPE_MAX_BYTES + \")\";\n\n        VarcharEncoded(TdsDataType dataType, Supplier<ByteBuf> value) {\n            super(dataType, SqlServerType.NVARCHAR, value);\n        }\n\n        @Override\n        public String getFormalType() {\n            return FORMAL_TYPE;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/ClobCodec.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.CompositeByteBuf;\nimport io.r2dbc.mssql.codec.RpcParameterContext.CharacterValueContext;\nimport io.r2dbc.mssql.message.type.*;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.ReferenceCountUtil;\nimport io.r2dbc.spi.Clob;\nimport io.r2dbc.spi.R2dbcNonTransientException;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.CharsetDecoder;\nimport java.nio.charset.CoderResult;\nimport java.nio.charset.CodingErrorAction;\nimport java.nio.charset.MalformedInputException;\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Codec for character values that are represented as {@link Clob}.\n * <p>\n * BlobCodec\n * <li>Server types: {@link SqlServerType#CHAR}, {@link SqlServerType#NCHAR}, {@link SqlServerType#VARCHAR}, {@link SqlServerType#NVARCHAR}, {@link SqlServerType#VARCHARMAX},\n * {@link SqlServerType#NVARCHARMAX}, {@link SqlServerType#TEXT} and {@link SqlServerType#NTEXT}.</li>\n * <li>Java type: {@link Clob}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\npublic class ClobCodec extends AbstractCodec<Clob> {\n\n    /**\n     * Singleton instance.\n     */\n    public static final ClobCodec INSTANCE = new ClobCodec();\n\n    private static final Set<SqlServerType> SUPPORTED_TYPES = EnumSet.of(SqlServerType.CHAR, SqlServerType.NCHAR,\n        SqlServerType.VARCHAR, SqlServerType.NVARCHAR,\n        SqlServerType.VARCHARMAX, SqlServerType.NVARCHARMAX,\n        SqlServerType.TEXT, SqlServerType.NTEXT);\n\n    private ClobCodec() {\n        super(Clob.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Clob value) {\n        return CharacterEncoder.encodePlp(allocator, context.getServerType(), context.getRequiredValueContext(CharacterValueContext.class), value);\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return SUPPORTED_TYPES.contains(serverType);\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return StringCodec.INSTANCE.doEncodeNull(allocator);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return StringCodec.INSTANCE.encodeNull(allocator, serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return SUPPORTED_TYPES.contains(typeInformation.getServerType());\n    }\n\n    @Nullable\n    public Clob decode(@Nullable ByteBuf buffer, Decodable decodable, Class<? extends Clob> type) {\n\n        Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        if (buffer == null) {\n            return null;\n        }\n\n        Length length;\n\n        if (decodable.getType().getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n\n            PlpLength plpLength = buffer.isReadable() ? PlpLength.decode(buffer, decodable.getType()) : PlpLength.nullLength();\n            length = Length.of(Math.toIntExact(plpLength.getLength()), plpLength.isNull());\n        } else {\n            length = Length.decode(buffer, decodable.getType());\n        }\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        return doDecode(buffer, length, decodable.getType(), type);\n    }\n\n    @Override\n    Clob doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends Clob> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        if (type.getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n\n            int startIndex = buffer.readerIndex();\n            while (buffer.isReadable()) {\n\n                Length chunkLength = Length.decode(buffer, type);\n                buffer.skipBytes(chunkLength.getLength());\n            }\n\n            int endIndex = buffer.readerIndex();\n            buffer.readerIndex(startIndex);\n            return new ScalarClob(type, length, buffer.readRetainedSlice(endIndex - startIndex));\n        }\n\n        return new ScalarClob(type, length, buffer.readRetainedSlice(length.getLength()));\n    }\n\n    /**\n     * Scalar {@link Clob} backed by an already received and de-chunked {@link List} of {@link ByteBuf}.\n     */\n    static class ScalarClob implements Clob {\n\n        private final TypeInformation type;\n\n        private final Length valueLength;\n\n        private final ByteBuf buffer;\n\n        private final CompositeByteBuf remainder;\n\n        ScalarClob(TypeInformation type, Length valueLength, ByteBuf buffer) {\n            this.type = type;\n            this.valueLength = valueLength;\n            this.buffer = buffer.touch(\"ScalarClob\");\n            this.remainder = buffer.alloc().compositeBuffer();\n        }\n\n        @Override\n        public Publisher<CharSequence> stream() {\n\n            CharsetDecoder decoder = this.type.getCharset().newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT);\n\n            AtomicReference<CoderResult> result = new AtomicReference<>();\n            AtomicInteger counter = new AtomicInteger();\n            return createBufferStream(this.buffer, this.valueLength, this.type).<CharSequence>handle((buffer, sink) -> {\n\n                if (!buffer.isReadable()) {\n                    // ensure release if not consumed\n                    buffer.release();\n                    return;\n                }\n\n                this.remainder.addComponent(true, buffer);\n                ByteBuffer byteBuffer = this.remainder.nioBuffer();\n\n                int size = byteBuffer.remaining();\n                CharBuffer outBuffer = CharBuffer.allocate(byteBuffer.remaining());\n\n                CoderResult decode;\n                synchronized (decoder) {\n                    decode = decoder.decode(byteBuffer, outBuffer, false);\n                }\n\n                result.set(decode);\n                int consumed = size - byteBuffer.remaining();\n\n                if (consumed > 0) {\n                    this.remainder.skipBytes(consumed);\n                } else {\n                    sink.error(new MalformedInputException(consumed));\n                    return;\n                }\n\n                if (counter.incrementAndGet() % 16 == 0) {\n                    this.remainder.discardSomeReadBytes();\n                }\n\n                outBuffer.flip();\n                sink.next(outBuffer.toString());\n            }).doOnComplete(() -> {\n\n                CoderResult coderResult = result.get();\n\n                if (coderResult != null && coderResult.isError()) {\n\n                    if (coderResult.isMalformed()) {\n                        throw new ClobDecodeException(\"Cannot decode CLOB data. Malformed character input\");\n                    }\n                    if (coderResult.isUnmappable()) {\n                        throw new ClobDecodeException(\"Cannot decode CLOB data. Unmappable characters\");\n                    }\n                }\n\n                if (this.remainder.isReadable()) {\n                    throw new ClobDecodeException(\"Cannot decode CLOB data. Buffer has remainder: \" + ByteBufUtil.hexDump(this.remainder));\n                }\n\n            })\n                    .doFinally(s -> ReferenceCountUtil.maybeRelease(this.remainder));\n        }\n\n        @Override\n        public Publisher<Void> discard() {\n            return Mono.fromRunnable(this::releaseBuffers);\n        }\n\n        private void releaseBuffers() {\n\n            ReferenceCountUtil.maybeSafeRelease(this.remainder);\n            ReferenceCountUtil.maybeSafeRelease(this.buffer);\n        }\n\n        private static Flux<ByteBuf> createBufferStream(ByteBuf plpStream, Length valueLength, TypeInformation type) {\n\n            return Flux.<ByteBuf>generate(sink -> {\n\n                try {\n                    if (!plpStream.isReadable()) {\n                        sink.complete();\n                        return;\n                    }\n\n                    Length length;\n                    if (type.getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n                        length = Length.decode(plpStream, type);\n                    } else {\n                        length = valueLength;\n                    }\n\n                    sink.next(plpStream.readRetainedSlice(length.getLength()));\n                } catch (Exception e) {\n                    sink.error(e);\n                }\n            })\n                .doFinally(s -> {\n                    ReferenceCountUtil.maybeSafeRelease(plpStream);\n                });\n        }\n\n    }\n\n    static class ClobDecodeException extends R2dbcNonTransientException {\n\n        public ClobDecodeException(String reason) {\n            super(reason);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/Codec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Codec to encode and decode values based on Server types and Java value types.<p/>\n * Codecs can decode one or more {@link SqlServerType server-specific data types} and represent them as a specific Java {@link Class type}. The type parameter of {@link Codec}\n * indicates the interchange type that is handled by this codec.\n * <p/>\n * Codecs that can decode various types (e.g. {@literal uniqueidentifier} and {@literal char}) use the most appropriate method to represent the value by casting or using the\n * {@link Object#toString() toString} method.\n *\n * @param <T> the type that is handled by this codec.\n * @see TypeInformation\n * @see SqlServerType\n */\ninterface Codec<T> {\n\n    /**\n     * Determine whether this {@link Codec} is capable of encoding the {@code value}.\n     *\n     * @param value the parameter value.\n     * @return {@code true} if this {@link Codec} is able to encode the {@code value}.\n     * @see #encodeNull\n     */\n    boolean canEncode(Object value);\n\n    /**\n     * Encode the {@code value} to be used as RPC parameter.\n     *\n     * @param allocator the allocator to allocate encoding buffers.\n     * @param context   parameter context.\n     * @param value     the {@code null} {@code value}.\n     * @return the encoded value.\n     */\n    Encoded encode(ByteBufAllocator allocator, RpcParameterContext context, T value);\n\n    /**\n     * Determine whether this {@link Codec} is capable of encoding a {@code null} value for the given {@link Class} type.\n     *\n     * @param type the desired value type.\n     * @return {@code true} if this {@link Codec} is able to encode {@code null} values for the given {@link Class} type.\n     * @see #encodeNull\n     */\n    boolean canEncodeNull(Class<?> type);\n\n    /**\n     * Determine whether this {@link Codec} is capable of encoding a {@code null} value for the given {@link SqlServerType} type.\n     *\n     * @param type the desired value type.\n     * @return {@code true} if this {@link Codec} is able to encode {@code null} values for the given {@link SqlServerType} type.\n     * @see #encodeNull\n     * @since 0.9\n     */\n    boolean canEncodeNull(SqlServerType serverType);\n\n    /**\n     * Encode a {@code null} value.\n     *\n     * @param allocator the allocator to allocate encoding buffers.\n     * @return the encoded {@code null} value.\n     */\n    Encoded encodeNull(ByteBufAllocator allocator);\n\n    /**\n     * Encode a {@code null} value.\n     *\n     * @param allocator the allocator to allocate encoding buffers.\n     * @return the encoded {@code null} value.\n     * @since 0.9\n     */\n    Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType);\n\n    /**\n     * Determine whether this {@link Codec} is capable of decoding a value for the given {@link Decodable} and whether it can represent the decoded value as the desired {@link Class type}.\n     * {@link Decodable} represents typically a column or RPC return value.\n     *\n     * @param decodable the decodable metadata.\n     * @param type      the desired value type.\n     * @return {@code true} if this codec is able to decode values of {@link TypeInformation}.\n     */\n    boolean canDecode(Decodable decodable, Class<?> type);\n\n    /**\n     * Decode the {@link ByteBuf data} and return it as the requested {@link Class type}.\n     *\n     * @param buffer    the data buffer.\n     * @param decodable the decodable descriptor.\n     * @param type      the desired value type.\n     * @return the decoded value. Can be {@code null} if the value is {@code null}.\n     */\n    @Nullable\n    T decode(@Nullable ByteBuf buffer, Decodable decodable, Class<? extends T> type);\n\n    /**\n     * Returns the Java {@link Class type} of this codec.\n     *\n     * @return the Java type.\n     */\n    Class<T> getType();\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/Codecs.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.util.ReferenceCounted;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.token.ReturnValue;\nimport io.r2dbc.mssql.message.token.RowToken;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Registry for {@link Codec}s to encode RPC parameters and decode tabular values.\n *\n * @see Codec\n * @see ReturnValue\n * @see Column\n * @see RowToken\n */\npublic interface Codecs {\n\n    /**\n     * Encode a non-{@code null} {@code value} as RPC parameter.\n     *\n     * @param allocator the allocator to allocate encoding buffers.\n     * @param context   parameter context.\n     * @param value     the {@code null} {@code value}.\n     * @return the encoded value. Must be {@link ReferenceCounted#release() released} after usage.\n     */\n    Encoded encode(ByteBufAllocator allocator, RpcParameterContext context, Object value);\n\n    /**\n     * Encode a {@code null} value for a specific {@link Class type}.\n     *\n     * @param allocator the allocator to allocate encoding buffers.\n     * @param type      the type to represent {@code null}.\n     * @return the encoded {@code null} value.\n     */\n    Encoded encodeNull(ByteBufAllocator allocator, Class<?> type);\n\n    /**\n     * Decode a data to a value.\n     *\n     * @param buffer    the {@link ByteBuf} to decode.\n     * @param decodable the decodable metadata.\n     * @param type      the type to decode to.\n     * @param <T>       the type of item being returned.\n     * @return the decoded value. Can be {@code null} if the column value is {@code null}.\n     */\n    @Nullable\n    <T> T decode(@Nullable ByteBuf buffer, Decodable decodable, Class<? extends T> type);\n\n    /**\n     * Returns the Java {@link Class type} to which this {@link TypeInformation type descriptor} decodes to. The resulting type is considered the native type for the {@link TypeInformation type\n     * descriptor}.\n     *\n     * @param type the type descriptor.\n     * @return the most appropriate Java {@link Class type}.\n     * @throws IllegalArgumentException if {@code type} is {@code null}\n     */\n    Class<?> getJavaType(TypeInformation type);\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/DecimalCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TdsDataType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.util.function.Supplier;\n\n/**\n * Codec for fixed floating-point values that are represented as {@link BigDecimal}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#NUMERIC} and  {@link SqlServerType#DECIMAL}</li>\n * <li>Java type: {@link BigDecimal}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class DecimalCodec extends AbstractNumericCodec<BigDecimal> {\n\n    private final static BigInteger MAX_VALUE = new BigInteger(\"99999999999999999999999999999999999999\");\n\n    static final DecimalCodec INSTANCE = new DecimalCodec();\n\n    private static final int MAX_PRECISION = 38;\n\n    private static final byte[] NULL = ByteArray.fromBuffer(alloc -> {\n\n        ByteBuf buffer = alloc.buffer(4);\n\n        Encode.asByte(buffer, 0x11);\n        Encode.asByte(buffer, SqlServerType.DECIMAL.getMaxLength());\n        Encode.asByte(buffer, 0); // scale\n        Encode.asByte(buffer, 0); // length\n\n        return buffer;\n    });\n\n    private DecimalCodec() {\n        super(BigDecimal.class, BigDecimal::valueOf);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, BigDecimal value) {\n\n        BigDecimal valueToUse;\n\n        // Handle negative scale as a special case for Java 1.5 and later\n        if (value.scale() < 0) {\n            valueToUse = value.setScale(0);\n        } else {\n            valueToUse = value;\n        }\n\n        if (exceedsMaxPrecisionOrScale(valueToUse)) {\n            throw new IllegalArgumentException(\"One or more values is out of range of values for the DECIMAL SQL type\");\n        }\n\n        return new DecimalEncoded(TdsDataType.DECIMALN, () -> {\n\n            ByteBuf buffer = RpcEncoding.prepareBuffer(allocator, TdsDataType.DECIMALN.getLengthStrategy(), 0x11, SqlServerType.DECIMAL.getMaxLength());\n\n            encodeBigDecimal(buffer, valueToUse);\n            return buffer;\n        }, MAX_PRECISION, valueToUse.scale());\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return new DecimalEncoded(TdsDataType.DECIMALN, () -> Unpooled.wrappedBuffer(NULL), MAX_PRECISION, 0);\n    }\n\n    @Override\n    BigDecimal doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends BigDecimal> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        if (type.getServerType() == SqlServerType.DECIMAL || type.getServerType() == SqlServerType.NUMERIC) {\n            return decodeDecimal(buffer, length.getLength(), type.getScale());\n        }\n\n        return super.doDecode(buffer, length, type, valueType);\n    }\n\n    private static void encodeBigDecimal(ByteBuf buffer, BigDecimal value) {\n\n        boolean isNegative = (value.signum() < 0);\n\n        BigInteger valueToUse = value.unscaledValue();\n\n        if (isNegative) {\n            valueToUse = valueToUse.negate();\n        }\n\n        byte[] unscaledBytes = valueToUse.toByteArray();\n\n        Encode.asByte(buffer, value.scale());\n        Encode.asByte(buffer, unscaledBytes.length + 1); // data length + sign\n        Encode.asByte(buffer, isNegative ? 0 : 1);   // 1 = +ve, 0 = -ve\n\n        for (int i = unscaledBytes.length - 1; i >= 0; i--) {\n            Encode.asByte(buffer, unscaledBytes[i]);\n        }\n    }\n\n    private static boolean exceedsMaxPrecisionOrScale(BigDecimal value) {\n\n        // Maximum scale allowed is same as maximum precision allowed.\n        if (value.scale() > MAX_PRECISION) {\n            return true;\n        }\n\n        // Convert to unscaled integer value, then compare with maxRPCDecimalValue.\n        // NOTE: Handle negative scale as a special case for Java 1.5 and later\n        BigInteger bi = value.unscaledValue();\n        if (value.signum() < 0) {\n            bi = bi.negate();\n        }\n        return bi.compareTo(MAX_VALUE) > 0;\n    }\n\n    static class DecimalEncoded extends RpcEncoding.HintedEncoded {\n\n        private final int length;\n\n        private final int scale;\n\n        DecimalEncoded(TdsDataType dataType, Supplier<ByteBuf> value, int length, int scale) {\n            super(dataType, SqlServerType.DECIMAL, value);\n            this.length = length;\n            this.scale = scale;\n        }\n\n        @Override\n        public String getFormalType() {\n            return super.getFormalType() + \"(\" + this.length + \",\" + this.scale + \")\";\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/Decodable.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\n/**\n * Interface declaring metadata to allow decoding of a related value.\n *\n * @author Mark Paluch\n */\npublic interface Decodable {\n\n    /**\n     * Returns the type that is associated with the decodable value.\n     *\n     * @return the type that is associated with the decodable value.\n     */\n    TypeInformation getType();\n\n    /**\n     * Returns the name of the decodable item. This is typically a parameter name or a column name.\n     *\n     * @return the name of the decodable.\n     */\n    String getName();\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/DefaultCodecs.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Parameter;\nimport io.r2dbc.spi.R2dbcType;\nimport io.r2dbc.spi.Type;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * The default {@link Codec} implementation.  Delegates to type-specific codec implementations.\n */\npublic final class DefaultCodecs implements Codecs {\n\n    private final Codec<?>[] codecs;\n\n    private final Map<SqlServerType, Codec<?>> codecPreferences = new HashMap<>();\n\n    private final Map<Class<?>, Codec<?>> codecNullCache = new ConcurrentHashMap<>();\n\n    /**\n     * Creates a new instance of {@link DefaultCodecs}.\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public DefaultCodecs() {\n\n        this.codecs = Arrays.asList(\n\n            // Prioritized Codecs\n            StringCodec.INSTANCE,\n            BinaryCodec.INSTANCE,\n\n            BooleanCodec.INSTANCE,\n            ByteCodec.INSTANCE,\n            ShortCodec.INSTANCE,\n            FloatCodec.INSTANCE,\n            DoubleCodec.INSTANCE,\n            IntegerCodec.INSTANCE,\n            LongCodec.INSTANCE,\n            BigIntegerCodec.INSTANCE,\n            LocalTimeCodec.INSTANCE,\n            LocalDateCodec.INSTANCE,\n            LocalDateTimeCodec.INSTANCE,\n            UuidCodec.INSTANCE,\n            DecimalCodec.INSTANCE,\n            MoneyCodec.INSTANCE,\n            TimestampCodec.INSTANCE,\n            OffsetDateTimeCodec.INSTANCE,\n            ZonedDateTimeCodec.INSTANCE,\n            BlobCodec.INSTANCE,\n            ClobCodec.INSTANCE\n        ).toArray(new Codec[0]);\n\n        this.codecPreferences.put(SqlServerType.BIT, BooleanCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.TINYINT, ByteCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.SMALLINT, ShortCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.INTEGER, IntegerCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.BIGINT, LongCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.REAL, FloatCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.FLOAT, DoubleCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.GUID, UuidCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.NUMERIC, DecimalCodec.INSTANCE);\n        this.codecPreferences.put(SqlServerType.DECIMAL, DecimalCodec.INSTANCE);\n    }\n\n    @SuppressWarnings({\"unchecked\", \"rawtpes\"})\n    @Override\n    public Encoded encode(ByteBufAllocator allocator, RpcParameterContext context, Object value) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n        Assert.requireNonNull(context, \"RpcParameterContext must not be null\");\n        Assert.requireNonNull(value, \"Value must not be null\");\n\n        Object parameterValue = value;\n        SqlServerType serverType;\n\n        if (value instanceof Parameter) {\n\n            Parameter parameter = (Parameter) value;\n            parameterValue = parameter.getValue();\n\n            if (parameter.getType() instanceof Type.InferredType && parameterValue == null) {\n                return encodeNull(allocator, parameter.getType().getJavaType());\n            }\n\n            serverType = getServerType(parameter);\n\n        } else {\n            serverType = null;\n        }\n\n        if (serverType == null) {\n\n            for (Codec<?> codec : this.codecs) {\n                if (codec.canEncode(parameterValue)) {\n                    return ((Codec) codec).encode(allocator, context, parameterValue);\n                }\n            }\n        } else {\n\n            if (parameterValue == null) {\n\n                for (Codec<?> codec : this.codecs) {\n                    if (codec.canEncodeNull(serverType)) {\n                        return codec.encodeNull(allocator, serverType);\n                    }\n                }\n            } else {\n\n                for (Codec<?> codec : this.codecs) {\n                    if (codec.canEncode(parameterValue)) {\n                        return ((Codec) codec).encode(allocator, context.withServerType(serverType), parameterValue);\n                    }\n                }\n            }\n\n        }\n\n        throw new IllegalArgumentException(String.format(\"Cannot encode [%s] parameter of type [%s]\", parameterValue, value.getClass().getName()));\n    }\n\n    @Nullable\n    private SqlServerType getServerType(Parameter parameter) {\n\n        if (parameter.getType() instanceof Type.InferredType) {\n            return null;\n        }\n\n        if (parameter.getType() instanceof R2dbcType) {\n            return SqlServerType.of((R2dbcType) parameter.getType());\n        }\n\n        if (parameter.getType() instanceof SqlServerType) {\n            return (SqlServerType) parameter.getType();\n        }\n\n        return SqlServerType.of(parameter.getType().getName());\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, Class<?> type) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        Codec<?> codecToUse = this.codecNullCache.computeIfAbsent(type, key -> {\n\n            for (Codec<?> codec : this.codecs) {\n                if (codec.canEncodeNull(key)) {\n                    return codec;\n                }\n            }\n\n            throw new IllegalArgumentException(String.format(\"Cannot encode [null] parameter of type [%s]\", type.getName()));\n\n        });\n\n        return codecToUse.encodeNull(allocator);\n    }\n\n    @Override\n    public <T> T decode(@Nullable ByteBuf buffer, Decodable decodable, Class<? extends T> type) {\n\n        Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        if (buffer == null) {\n            return null;\n        }\n\n        Codec<T> codec = getDecodingCodec(decodable, type);\n        return doDecode(codec, buffer, decodable, type);\n    }\n\n    @Nullable\n    private <T> T doDecode(Codec<T> codec, @Nullable ByteBuf buffer, Decodable decodable, Class<? extends T> type) {\n        return codec.decode(buffer, decodable, type);\n    }\n\n    @Override\n    public Class<?> getJavaType(TypeInformation type) {\n\n        Assert.requireNonNull(type, \"Type must not be null\");\n        Codec<Object> decodingCodec = getDecodingCodec(new TypeInformationWrapper(type), Object.class);\n        return decodingCodec.getType();\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private <T> Codec<T> getDecodingCodec(Decodable decodable, Class<? extends T> requestedType) {\n\n        Codec<?> preferredCodec = this.codecPreferences.get(decodable.getType().getServerType());\n        if (preferredCodec != null && preferredCodec.canDecode(decodable, requestedType)) {\n            return (Codec<T>) preferredCodec;\n        }\n\n        for (Codec<?> codec : this.codecs) {\n            if (codec.canDecode(decodable, requestedType)) {\n                return (Codec<T>) codec;\n            }\n        }\n\n        throw new IllegalArgumentException(String.format(\"Cannot decode value of type [%s], name [%s] server type [%s]\", requestedType.getName(), decodable.getName(),\n            decodable.getType().getServerType()));\n    }\n\n    static class TypeInformationWrapper implements Decodable {\n\n        private final TypeInformation typeInformation;\n\n        TypeInformationWrapper(TypeInformation typeInformation) {\n            this.typeInformation = typeInformation;\n        }\n\n        @Override\n        public TypeInformation getType() {\n            return this.typeInformation;\n        }\n\n        @Override\n        public String getName() {\n            return this.typeInformation.getServerTypeName();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/DoubleCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\n/**\n * Codec for floating-point values that are represented as {@link Double}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#FLOAT} (8-byte) and  {@link SqlServerType#REAL} (4-byte)</li>\n * <li>Java type: {@link Double}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class DoubleCodec extends AbstractCodec<Double> {\n\n    public static final DoubleCodec INSTANCE = new DoubleCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeNull(alloc, SqlServerType.FLOAT));\n\n    private DoubleCodec() {\n        super(Double.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Double value) {\n        return RpcEncoding.encodeFixed(allocator, SqlServerType.FLOAT, value, Encode::asDouble);\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.FLOAT;\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.FLOAT);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeNull(allocator, serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.FLOAT || typeInformation.getServerType() == SqlServerType.REAL;\n    }\n\n    @Override\n    Double doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends Double> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        if (length.getLength() == 4) {\n            return (double) Decode.asFloat(buffer);\n        }\n\n        return Decode.asDouble(buffer);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/Encoded.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TdsDataType;\nimport io.r2dbc.mssql.util.ReferenceCountUtil;\nimport reactor.core.Disposable;\n\nimport java.util.function.IntFunction;\nimport java.util.function.Supplier;\n\n/**\n * Encoded value, either providing a singleton {@link ByteBuf} or a {@link Supplier} of buffers.\n *\n * @author Mark Paluch\n */\npublic class Encoded implements Disposable {\n\n    private final TdsDataType dataType;\n\n    private final Supplier<ByteBuf> encoder;\n\n    Encoded(TdsDataType dataType, Supplier<ByteBuf> encoder) {\n        this.dataType = dataType;\n        this.encoder = encoder;\n    }\n\n    public static Encoded of(TdsDataType dataType, ByteBuf value) {\n        return new Encoded(dataType, new DisposableSupplier(value));\n    }\n\n    public static Encoded of(TdsDataType dataType, Supplier<ByteBuf> value) {\n        return new Encoded(dataType, value);\n    }\n\n    public TdsDataType getDataType() {\n        return this.dataType;\n    }\n\n    public ByteBuf getValue() {\n        return this.encoder.get();\n    }\n\n    /**\n     * Returns the formal type such as {@literal INTEGER} or {@literal VARCHAR(255)}\n     *\n     * @return\n     */\n    public String getFormalType() {\n\n        for (SqlServerType serverType : SqlServerType.values()) {\n\n            for (TdsDataType tdsType : serverType.getFixedTypes()) {\n                if (tdsType == this.dataType) {\n                    return serverType.toString();\n                }\n            }\n        }\n\n        throw new IllegalStateException(String.format(\"Cannot determine a formal type for %s\", this.dataType));\n    }\n\n    /**\n     * Attempt to estimate the length of the buffer to apply allocation optimizations.\n     *\n     * @return the estimated length. Can be an approximation or zero, if the buffer size cannot be estimated.\n     */\n    public int estimateLength() {\n\n        if (this.encoder instanceof DisposableSupplier) {\n            return ((DisposableSupplier) this.encoder).get().readableBytes();\n        }\n\n        if (this.encoder instanceof LengthAwareSupplier) {\n            return ((LengthAwareSupplier) this.encoder).getLength();\n        }\n\n        return 0;\n    }\n\n    public static Supplier<ByteBuf> ofLengthAware(int length, IntFunction<ByteBuf> supplier) {\n        return new LengthAwareSupplier(length, supplier);\n    }\n\n    @Override\n    public void dispose() {\n\n        if (this.encoder instanceof DisposableSupplier) {\n            ((DisposableSupplier) this.encoder).dispose();\n        }\n    }\n\n    static class DisposableSupplier implements Supplier<ByteBuf>, Disposable {\n\n        private final ByteBuf buf;\n\n        DisposableSupplier(ByteBuf buf) {\n            this.buf = buf;\n        }\n\n        @Override\n        public ByteBuf get() {\n            return this.buf.asReadOnly();\n        }\n\n        @Override\n        public void dispose() {\n            ReferenceCountUtil.maybeSafeRelease(this.buf);\n        }\n\n        @Override\n        public boolean isDisposed() {\n            return this.buf.refCnt() == 0;\n        }\n    }\n\n\n    static class LengthAwareSupplier implements Supplier<ByteBuf> {\n\n        private final int length;\n\n        private final IntFunction<ByteBuf> delegate;\n\n        public LengthAwareSupplier(int length, IntFunction<ByteBuf> delegate) {\n            this.length = length;\n            this.delegate = delegate;\n        }\n\n        @Override\n        public ByteBuf get() {\n            return this.delegate.apply(this.length);\n        }\n\n        public int getLength() {\n            return this.length;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/FloatCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\n/**\n * Codec for floating-point values that are represented as {@link Float}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#FLOAT} (8-byte) and  {@link SqlServerType#REAL} (4-byte)</li>\n * <li>Java type: {@link Float}</li>\n * <li>Downcast: to {@link Float}</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class FloatCodec extends AbstractCodec<Float> {\n\n    static final FloatCodec INSTANCE = new FloatCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded(alloc -> RpcEncoding.encodeNull(alloc, SqlServerType.REAL));\n\n    private FloatCodec() {\n        super(Float.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Float value) {\n        return RpcEncoding.encodeFixed(allocator, SqlServerType.REAL, value, Encode::asFloat);\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.REAL;\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.REAL);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeNull(allocator, serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return DoubleCodec.INSTANCE.doCanDecode(typeInformation);\n    }\n\n    @Override\n    Float doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends Float> valueType) {\n\n        Double value = DoubleCodec.INSTANCE.doDecode(buffer, length, type, Double.class);\n        return value == null ? null\n            : value.floatValue();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/IntegerCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.SqlServerType;\n\n/**\n * Codec for numeric values that are represented as {@link Integer}.\n *\n * <ul>\n * <li>Server types: Integer numbers</li>\n * <li>Java type: {@link Integer}</li>\n * <li>Downcast: to {@link Integer}</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class IntegerCodec extends AbstractNumericCodec<Integer> {\n\n    /**\n     * Singleton instance.\n     */\n    static final IntegerCodec INSTANCE = new IntegerCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeNull(alloc, SqlServerType.INTEGER));\n\n    private IntegerCodec() {\n        super(Integer.class, value -> (int) value);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Integer value) {\n        return RpcEncoding.encodeFixed(allocator, SqlServerType.INTEGER, value, Encode::asInt);\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.INTEGER);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/LocalDateCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.*;\n\nimport java.time.LocalDate;\nimport java.time.temporal.ChronoUnit;\n\n/**\n * Codec for date types that are represented as {@link LocalDate}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#DATE}</li>\n * <li>Java type: {@link LocalDate}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class LocalDateCodec extends AbstractCodec<LocalDate> {\n\n    /**\n     * Singleton instance.\n     */\n    public static final LocalDateCodec INSTANCE = new LocalDateCodec();\n\n    /**\n     * Date base date: 0001-01-01.\n     */\n    private static final LocalDate DATE_ZERO = LocalDate.of(1, 1, 1);\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeTemporalNull(alloc, SqlServerType.DATE));\n\n    private LocalDateCodec() {\n        super(LocalDate.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, LocalDate value) {\n\n\n        return new RpcEncoding.HintedEncoded(TdsDataType.DATEN, SqlServerType.DATE, () -> {\n\n            ByteBuf buffer = allocator.buffer(4);\n            buffer.writeByte(TypeUtils.DAYS_INTO_CE_LENGTH);\n            encode(buffer, value);\n\n            return buffer;\n        });\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.DATE;\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.DATE);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeTemporalNull(allocator, serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.DATE;\n    }\n\n    @Override\n    LocalDate doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends LocalDate> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        int days = (buffer.readByte() & 0xFF) | (buffer.readByte() & 0xFF) << 8 | (buffer.readByte() & 0xFF) << 16;\n\n        return DATE_ZERO.plusDays(days);\n    }\n\n    /**\n     * Write the {@link LocalDate} value to the {@link ByteBuf data buffer}.\n     */\n    static void encode(ByteBuf buffer, LocalDate value) {\n\n        long days = ChronoUnit.DAYS.between(DATE_ZERO, value);\n\n        buffer.writeByte((byte) days & 0xFF);\n        buffer.writeByte((byte) (days >> 8) & 0xFF);\n        buffer.writeByte((byte) (days >> 16) & 0xFF);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/LocalDateTimeCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.message.type.TypeUtils;\n\nimport java.time.Duration;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Codec for temporal types that are represented as {@link LocalDateTime}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#SMALLDATETIME}, {@link SqlServerType#DATETIME}, and {@link SqlServerType#DATETIME2}</li>\n * <li>Java type: {@link LocalDateTime}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class LocalDateTimeCodec extends AbstractCodec<LocalDateTime> {\n\n    /**\n     * Singleton instance.\n     */\n    public static final LocalDateTimeCodec INSTANCE = new LocalDateTimeCodec();\n\n    /**\n     * Date-Time base date: 1900-01-01T00:00:00.0.\n     */\n    private static final LocalDateTime DATETIME_ZERO = LocalDateTime.of(1900, 1, 1, 0, 0, 0, 0);\n\n    private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1);\n\n    private static final long NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1);\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeTemporalNull(alloc, SqlServerType.DATETIME2, 7));\n\n    private LocalDateTimeCodec() {\n        super(LocalDateTime.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, LocalDateTime value) {\n\n        return RpcEncoding.encode(allocator, SqlServerType.DATETIME2, 8, value,\n            (buffer, localDateTime) -> {\n                encode(buffer, SqlServerType.DATETIME2, TypeUtils.MAX_FRACTIONAL_SECONDS_SCALE, localDateTime);\n            });\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.DATETIME2;\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.DATETIME2);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeTemporalNull(allocator, serverType, 7);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.SMALLDATETIME || typeInformation.getServerType() == SqlServerType.DATETIME || typeInformation.getServerType() == SqlServerType.DATETIME2;\n    }\n\n    @Override\n    LocalDateTime doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends LocalDateTime> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        if (type.getServerType() == SqlServerType.SMALLDATETIME) {\n\n            int daysSinceBaseDate = Decode.uShort(buffer);\n            int minutesSinceMidnight = Decode.uShort(buffer);\n\n            return DATETIME_ZERO.plusDays(daysSinceBaseDate).plusMinutes(minutesSinceMidnight);\n        }\n\n        if (type.getServerType() == SqlServerType.DATETIME) {\n\n            int daysSinceBaseDate = Decode.asInt(buffer);\n\n            long ticksSinceMidnight = (Decode.asInt(buffer) * 10 + 1) / 3;\n            int subSecondNanos = (int) ((ticksSinceMidnight * NANOS_PER_MILLISECOND) % NANOS_PER_SECOND);\n\n            return DATETIME_ZERO.plusDays(daysSinceBaseDate).plus(ticksSinceMidnight, ChronoUnit.MILLIS).withNano(subSecondNanos);\n        }\n\n        if (type.getServerType() == SqlServerType.DATETIME2) {\n\n            LocalTime localTime = LocalTimeCodec.INSTANCE.doDecode(buffer, length, type, LocalTime.class);\n            LocalDate localDate = LocalDateCodec.INSTANCE.doDecode(buffer, length, type, LocalDate.class);\n\n            return localTime.atDate(localDate);\n        }\n\n        throw new UnsupportedOperationException(String.format(\"Cannot decode value from server type [%s]\", type.getServerType()));\n    }\n\n    static void encode(ByteBuf buffer, SqlServerType type, int scale, LocalDateTime value) {\n\n        if (type == SqlServerType.SMALLDATETIME) {\n\n            LocalDateTime midnight = value.truncatedTo(ChronoUnit.DAYS);\n            int daysSinceBaseDate = Math.toIntExact(Duration.between(DATETIME_ZERO, midnight).toDays());\n            int minutesSinceMidnight = (int) Duration.between(midnight, value).toMinutes();\n\n            Encode.uShort(buffer, daysSinceBaseDate);\n            Encode.uShort(buffer, minutesSinceMidnight);\n\n            return;\n        }\n\n        if (type == SqlServerType.DATETIME) {\n\n            LocalDateTime midnight = value.truncatedTo(ChronoUnit.DAYS);\n            int daysSinceBaseDate = Math.toIntExact(Duration.between(DATETIME_ZERO, midnight).toDays());\n\n            Duration time = Duration.between(midnight, value);\n\n            long ticksSinceMidnight = (3 * time.toMillis() + 5) / 10;\n\n            Encode.asInt(buffer, daysSinceBaseDate);\n            Encode.asInt(buffer, (int) ticksSinceMidnight);\n\n            return;\n        }\n\n        if (type == SqlServerType.DATETIME2) {\n\n            LocalTimeCodec.doEncode(buffer, scale, value.toLocalTime());\n            LocalDateCodec.encode(buffer, value.toLocalDate());\n\n            return;\n        }\n\n        throw new UnsupportedOperationException(String.format(\"Cannot encode [%s] to server type [%s]\", value, type));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/LocalTimeCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.message.type.TypeUtils;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.time.LocalTime;\n\n/**\n * Codec for scaled time types that are represented as {@link LocalTime}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#TIME}</li>\n * <li>Java type: {@link LocalTime}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class LocalTimeCodec extends AbstractCodec<LocalTime> {\n\n    /**\n     * Singleton instance.\n     */\n    public static final LocalTimeCodec INSTANCE = new LocalTimeCodec();\n\n    /**\n     * Using known multipliers is faster than calculating these (10^n).\n     */\n    private static final int[] SCALED_MULTIPLIERS = new int[]{10000000, 1000000, 100000, 10000, 1000, 100, 10, 1};\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeTemporalNull(alloc, SqlServerType.TIME, 7));\n\n    private LocalTimeCodec() {\n        super(LocalTime.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, LocalTime value) {\n        return RpcEncoding.encode(allocator, SqlServerType.TIME, TypeUtils.getTimeValueLength(TypeUtils.MAX_FRACTIONAL_SECONDS_SCALE),\n            value, (buffer,\n                    localTime) -> doEncode(buffer,\n                TypeUtils.MAX_FRACTIONAL_SECONDS_SCALE, localTime));\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.TIME;\n    }\n\n    @Override\n    Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.TIME);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeTemporalNull(allocator, serverType, 7);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.TIME;\n    }\n\n    @Override\n    LocalTime doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends LocalTime> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        long hundredNanosSinceMidnight = 0;\n        int scale = type.getScale();\n\n        Assert.isTrue(scale >= 0 && scale <= TypeUtils.MAX_FRACTIONAL_SECONDS_SCALE, \"Invalid fractional scale\");\n\n        int valueLength = TypeUtils.getTimeValueLength(scale);\n        for (int i = 0; i < valueLength; i++) {\n            hundredNanosSinceMidnight |= (buffer.readByte() & 0xFFL) << (8 * i);\n        }\n        hundredNanosSinceMidnight *= SCALED_MULTIPLIERS[scale];\n\n        return LocalTime.ofNanoOfDay(hundredNanosSinceMidnight * 100);\n    }\n\n    static void doEncode(ByteBuf buffer, int scale, LocalTime value) {\n\n        int valueLength = TypeUtils.getTimeValueLength(scale);\n        doEncodeValue(buffer, valueLength, value);\n    }\n\n    private static void doEncodeValue(ByteBuf buffer, int valueLength, LocalTime value) {\n\n        long nanosSinceMidnight = value.toNanoOfDay();\n        nanosSinceMidnight /= SCALED_MULTIPLIERS[valueLength];\n\n        for (int i = 0; i < valueLength; i++) {\n            buffer.writeByte((byte) ((nanosSinceMidnight >> (8 * i)) & 0xFF));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/LongCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.SqlServerType;\n\n/**\n * Codec for numeric values that are represented as {@link Long}.\n *\n * <ul>\n * <li>Server types: Integer numbers</li>\n * <li>Java type: {@link Long}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class LongCodec extends AbstractNumericCodec<Long> {\n\n    /**\n     * Singleton instance.\n     */\n    static final LongCodec INSTANCE = new LongCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeNull(alloc, SqlServerType.BIGINT));\n\n    private LongCodec() {\n        super(Long.class, value -> value);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Long value) {\n        return RpcEncoding.encodeFixed(allocator, SqlServerType.BIGINT, value, Encode::bigint);\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.BIGINT);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/MoneyCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\n\n/**\n * Codec for fixed floating-point values that are represented as {@link BigDecimal}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#MONEY} (8-byte) and  {@link SqlServerType#SMALLMONEY} (4-byte)</li>\n * <li>Java type: {@link BigDecimal}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class MoneyCodec extends AbstractCodec<BigDecimal> {\n\n    /**\n     * Singleton instance.\n     */\n    static final MoneyCodec INSTANCE = new MoneyCodec();\n\n    /**\n     * Value length of {@link SqlServerType#MONEY}.\n     */\n    private static final int BIG_MONEY_LENGTH = 8;\n\n    /**\n     * Value length of {@link SqlServerType#SMALLMONEY}.\n     */\n    private static final int SMALL_MONEY_LENGTH = 4;\n\n    private static final byte[] NULL = ByteArray.fromEncoded(alloc -> RpcEncoding.encodeNull(alloc, SqlServerType.MONEY));\n\n    private MoneyCodec() {\n        super(BigDecimal.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, BigDecimal value) {\n        return RpcEncoding.encodeFixed(allocator, SqlServerType.MONEY, value, (buffer, bigDecimal) -> Encode.money(buffer, bigDecimal.unscaledValue()));\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.MONEY || serverType == SqlServerType.SMALLMONEY;\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.MONEY);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeNull(allocator, serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.MONEY;\n    }\n\n    @Override\n    BigDecimal doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends BigDecimal> valueType) {\n\n        BigInteger decoded = decode(buffer, length.getLength());\n\n        return new BigDecimal(decoded, 4);\n    }\n\n    private static BigInteger decode(ByteBuf buffer, int length) {\n\n        switch (length) {\n            case BIG_MONEY_LENGTH:\n\n                int intBitsHi = Decode.asInt(buffer);\n                int intBitsLo = Decode.asInt(buffer);\n\n                return BigInteger.valueOf(((long) intBitsHi << 32) | (intBitsLo & 0xFFFFFFFFL));\n\n            case SMALL_MONEY_LENGTH:\n                return BigInteger.valueOf(Decode.asInt(buffer));\n\n            default:\n                throw ProtocolException.invalidTds(String.format(\"Unexpected value length: %d\", length));\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/OffsetDateTimeCodec.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.*;\n\nimport java.time.LocalDate;\nimport java.time.LocalTime;\nimport java.time.OffsetDateTime;\nimport java.time.ZoneOffset;\n\n/**\n * Codec for temporal types that are represented as {@link OffsetDateTime}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#DATETIMEOFFSET}</li>\n * <li>Java type: {@link OffsetDateTime}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class OffsetDateTimeCodec extends AbstractCodec<OffsetDateTime> {\n\n    /**\n     * Singleton instance.\n     */\n    public static final OffsetDateTimeCodec INSTANCE = new OffsetDateTimeCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeTemporalNull(alloc, SqlServerType.DATETIMEOFFSET, 7));\n\n    private OffsetDateTimeCodec() {\n        super(OffsetDateTime.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, OffsetDateTime value) {\n\n\n        return new RpcEncoding.HintedEncoded(TdsDataType.DATETIMEOFFSETN, SqlServerType.DATETIMEOFFSET, () -> {\n\n            ByteBuf buffer = allocator.buffer(12);\n\n            Encode.asByte(buffer, 7); // scale\n            Encode.asByte(buffer, 0x0a); // length\n\n            doEncode(buffer, value.minusSeconds(value.getOffset().getTotalSeconds()));\n\n            return buffer;\n        });\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.DATETIMEOFFSET;\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.DATETIMEOFFSET);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeTemporalNull(allocator, serverType, 7);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.DATETIMEOFFSET;\n    }\n\n    @Override\n    OffsetDateTime doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends OffsetDateTime> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        LocalTime localTime = LocalTimeCodec.INSTANCE.doDecode(buffer, length, type, LocalTime.class);\n        LocalDate localDate = LocalDateCodec.INSTANCE.doDecode(buffer, length, type, LocalDate.class);\n\n        int localMinutesOffset = Decode.smallInt(buffer);\n        ZoneOffset offset = ZoneOffset.ofTotalSeconds(localMinutesOffset * 60);\n\n        return OffsetDateTime.of(localTime.atDate(localDate), offset).plusMinutes(localMinutesOffset);\n    }\n\n    static void doEncode(ByteBuf buffer, OffsetDateTime value) {\n\n        LocalTimeCodec.doEncode(buffer, TypeUtils.MAX_FRACTIONAL_SECONDS_SCALE, value.toLocalTime());\n        LocalDateCodec.encode(buffer, value.toLocalDate());\n\n        int localMinutesOffset = value.getOffset().getTotalSeconds() / 60;\n\n        Encode.smallInt(buffer, localMinutesOffset);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/PlpEncoded.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.CompositeByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.PlpLength;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport org.reactivestreams.Publisher;\nimport org.reactivestreams.Subscription;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.Disposable;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.FluxOperator;\nimport reactor.core.publisher.Operators;\nimport reactor.util.annotation.Nullable;\nimport reactor.util.context.Context;\n\nimport java.util.concurrent.atomic.AtomicIntegerFieldUpdater;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.AtomicLongFieldUpdater;\nimport java.util.function.IntSupplier;\n\n/**\n * Partial-length-prefixed extension to {@link Encoded}. Consumes a upstream {@link Publisher}.\n *\n * @author Mark Paluch\n */\npublic class PlpEncoded extends Encoded {\n\n    private final SqlServerType serverType;\n\n    private final ByteBufAllocator allocator;\n\n    private final Publisher<ByteBuf> dataStream;\n\n    private final Disposable disposable;\n\n    public PlpEncoded(SqlServerType dataType, ByteBufAllocator allocator, Publisher<ByteBuf> dataStream, Disposable disposable) {\n\n        super(dataType.getNullableType(), () -> Unpooled.EMPTY_BUFFER);\n\n        this.serverType = dataType;\n        this.allocator = allocator;\n        this.dataStream = dataStream;\n        this.disposable = disposable;\n    }\n\n    public void encodeHeader(ByteBuf byteBuf) {\n\n        // Send v*max length indicator 0xFFFF.\n        Encode.uShort(byteBuf, Length.USHORT_NULL);\n    }\n\n    @Override\n    public boolean isDisposed() {\n        return this.disposable.isDisposed();\n    }\n\n    @Override\n    public void dispose() {\n        this.disposable.dispose();\n    }\n\n    /**\n     * Transform the backing binary stream to a stream of binary chunks at the size provided by {@link IntSupplier chunk size supplier}.\n     *\n     * @param chunkSize expected chunk size.\n     * @return\n     */\n    public Flux<ByteBuf> chunked(IntSupplier chunkSize) {\n        return chunked(chunkSize, false);\n    }\n\n    /**\n     * Transform the backing binary stream to a stream of binary chunks at the size provided by {@link IntSupplier chunk size supplier}.\n     *\n     * @param chunkSize       expected chunk size.\n     * @param withSizeHeaders {@code true} to include PLP length headers (one unknown length and chunk length per chunk).\n     * @return\n     */\n    public Flux<ByteBuf> chunked(IntSupplier chunkSize, boolean withSizeHeaders) {\n        return new ChunkOperator(Flux.from(this.dataStream), this.allocator, chunkSize, withSizeHeaders);\n    }\n\n    @Override\n    public String getFormalType() {\n\n        switch (this.serverType) {\n            case VARBINARYMAX:\n                return \"VARBINARY(MAX)\";\n            case VARCHARMAX:\n                return \"VARCHAR(MAX)\";\n            case NVARCHARMAX:\n                return \"NVARCHAR(MAX)\";\n        }\n\n        throw new UnsupportedOperationException(\"Type \" + this.serverType + \" not supported\");\n    }\n\n    /**\n     * Operator for chunked encoding of {@link ByteBuf}. Chunk size is obtained from {@link IntSupplier} on\n     */\n    static class ChunkOperator extends FluxOperator<ByteBuf, ByteBuf> {\n\n        private final ByteBufAllocator allocator;\n\n        private final IntSupplier chunkSizeSupplier;\n\n        private final boolean withSizeHeaders;\n\n        ChunkOperator(Flux<ByteBuf> source, ByteBufAllocator allocator, IntSupplier chunkSizeSupplier, boolean withSizeHeaders) {\n\n            super(source);\n            this.allocator = allocator;\n            this.chunkSizeSupplier = chunkSizeSupplier;\n            this.withSizeHeaders = withSizeHeaders;\n        }\n\n        @Override\n        public void subscribe(CoreSubscriber<? super ByteBuf> actual) {\n            this.source.subscribe(new ChunkSubscriber(actual, this.allocator, this.chunkSizeSupplier, this.withSizeHeaders));\n        }\n\n    }\n\n    static class ChunkSubscriber extends AtomicLong implements CoreSubscriber<ByteBuf>, Subscription {\n\n        private static final int STATUS_WIP = 0;\n\n        private static final int STATUS_DONE = 1;\n\n        private final CoreSubscriber<? super ByteBuf> actual;\n\n        private final ByteBufAllocator allocator;\n\n        private final IntSupplier chunkSizeSupplier;\n\n        private final boolean withSizeHeaders;\n\n        private boolean first = true;\n\n        private volatile int nextChunkSize;\n\n        @Nullable\n        private volatile CompositeByteBuf aggregator;\n\n        volatile long requested;\n\n        @SuppressWarnings(\"rawtypes\")\n        static final AtomicLongFieldUpdater<ChunkSubscriber> REQUESTED =\n            AtomicLongFieldUpdater.newUpdater(ChunkSubscriber.class, \"requested\");\n\n        volatile int status;\n\n        @SuppressWarnings(\"rawtypes\")\n        static final AtomicIntegerFieldUpdater<ChunkSubscriber> STATUS =\n            AtomicIntegerFieldUpdater.newUpdater(ChunkSubscriber.class, \"status\");\n\n        private boolean doneUpstream;\n\n        private Subscription s;\n\n        ChunkSubscriber(CoreSubscriber<? super ByteBuf> actual, ByteBufAllocator allocator, IntSupplier chunkSizeSupplier, boolean withSizeHeaders) {\n\n            this.actual = actual;\n            this.allocator = allocator;\n            this.chunkSizeSupplier = chunkSizeSupplier;\n            this.withSizeHeaders = withSizeHeaders;\n        }\n\n        @Override\n        public Context currentContext() {\n            return this.actual.currentContext();\n        }\n\n        @Override\n        public void onSubscribe(Subscription s) {\n\n            if (Operators.validate(this.s, s)) {\n                this.s = s;\n                this.actual.onSubscribe(this);\n            }\n        }\n\n        @Override\n        public void onNext(ByteBuf byteBuf) {\n\n            byteBuf.touch(\"PlpEncoded.onNext(…)\");\n\n            if (STATUS.get(this) == STATUS_DONE) {\n\n                byteBuf.release();\n                Operators.onNextDropped(byteBuf, this.actual.currentContext());\n                return;\n            }\n\n            CompositeByteBuf aggregator = this.aggregator;\n            if (aggregator == null) {\n                this.aggregator = aggregator = this.allocator.compositeBuffer();\n            }\n\n            aggregator.addComponent(true, byteBuf);\n\n            drain();\n\n            if (!this.doneUpstream && REQUESTED.get(this) > 0) {\n                this.s.request(1);\n            }\n        }\n\n        private void drain() {\n\n            CompositeByteBuf aggregator = this.aggregator;\n\n            if (aggregator == null) {\n\n                if (this.doneUpstream && STATUS.compareAndSet(this, STATUS_WIP, STATUS_DONE)) {\n                    this.actual.onComplete();\n                }\n\n                return;\n            }\n\n            while (STATUS.get(this) == STATUS_WIP && aggregator.readableBytes() >= this.nextChunkSize && REQUESTED.get(this) > 0) {\n\n                long demand = REQUESTED.get(this);\n                if (demand > 0) {\n                    if (REQUESTED.compareAndSet(this, demand, demand - 1)) {\n                        emitNext(aggregator, this.nextChunkSize);\n                        this.nextChunkSize = this.chunkSizeSupplier.getAsInt();\n                    }\n                }\n            }\n\n            if (STATUS.get(this) == STATUS_WIP && this.doneUpstream && aggregator.isReadable() && REQUESTED.get(this) > 0) {\n\n                long demand = REQUESTED.get(this);\n                if (demand > 0) {\n                    if (REQUESTED.compareAndSet(this, demand, demand - 1)) {\n                        emitNext(aggregator, aggregator.readableBytes());\n                    }\n                }\n            }\n\n            if (this.doneUpstream && !aggregator.isReadable() && STATUS.compareAndSet(this, STATUS_WIP, STATUS_DONE)) {\n\n                aggregator.release();\n\n                this.aggregator = null;\n                this.actual.onComplete();\n            } else {\n                aggregator.discardReadComponents();\n            }\n        }\n\n        /**\n         * Emit a chunk from the buffer. At this point we need to make sure that buffers which get emitted do not hold a reference to the aggregator as they might cross thread boundaries.\n         *\n         * @param aggregator\n         * @param bytesToRead\n         */\n        private void emitNext(CompositeByteBuf aggregator, int bytesToRead) {\n\n            ByteBuf buffer = aggregator.alloc().buffer(bytesToRead);\n            buffer.writeBytes(aggregator, bytesToRead);\n\n            if (this.withSizeHeaders) {\n\n                CompositeByteBuf composite = this.allocator.compositeBuffer();\n\n                ByteBuf header = this.allocator.buffer();\n                if (this.first) {\n\n                    this.first = false;\n                    PlpLength.unknown().encode(header);\n                }\n\n                Length chunkLength = Length.of(bytesToRead);\n                chunkLength.encode(header, LengthStrategy.PARTLENTYPE);\n\n                composite.addComponent(true, header);\n                composite.addComponent(true, buffer);\n\n                buffer = composite;\n            }\n\n            this.actual.onNext(buffer);\n        }\n\n        @Override\n        public void onError(Throwable t) {\n\n            CompositeByteBuf aggregator = this.aggregator;\n\n            if (STATUS.compareAndSet(this, STATUS_WIP, STATUS_DONE)) {\n\n                this.doneUpstream = true;\n\n                this.actual.onError(t);\n\n                if (aggregator != null) {\n                    aggregator.release();\n                }\n\n                return;\n            }\n\n            Operators.onErrorDropped(t, this.actual.currentContext());\n        }\n\n        @Override\n        public void onComplete() {\n\n            this.doneUpstream = true;\n            drain();\n        }\n\n        @Override\n        public void request(long n) {\n\n            if (Operators.validate(n)) {\n\n                Operators.addCap(REQUESTED, this, n);\n                drain();\n\n                this.nextChunkSize = this.chunkSizeSupplier.getAsInt();\n\n                if (!this.doneUpstream && REQUESTED.get(this) > 0) {\n                    this.s.request(1);\n                }\n            }\n        }\n\n        @Override\n        public void cancel() {\n\n            if (!this.doneUpstream) {\n                this.doneUpstream = true;\n                this.s.cancel();\n            }\n\n            if (STATUS.compareAndSet(this, STATUS_WIP, STATUS_DONE)) {\n\n                CompositeByteBuf aggregator = this.aggregator;\n\n                if (aggregator != null) {\n                    aggregator.release();\n                }\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/PlpEncodedCharacters.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport org.reactivestreams.Publisher;\nimport reactor.core.Disposable;\n\n/**\n * Extension to {@link PlpEncoded} associated with a {@link Collation}.\n *\n * @author Mark Paluch\n */\nclass PlpEncodedCharacters extends PlpEncoded {\n\n    private final Collation collation;\n\n    PlpEncodedCharacters(SqlServerType dataType, Collation collation, ByteBufAllocator allocator, Publisher<ByteBuf> dataStream, Disposable disposable) {\n\n        super(dataType, allocator, dataStream, disposable);\n\n        this.collation = collation;\n    }\n\n    public void encodeHeader(ByteBuf byteBuf) {\n\n        super.encodeHeader(byteBuf);\n\n        this.collation.encode(byteBuf);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/RpcDirection.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\n/**\n * Direction for RPC parameters\n *\n * @author Mark Paluch\n */\npublic enum RpcDirection {\n\n    /**\n     * Input parameter that is expected by the server.\n     */\n    IN,\n\n    /**\n     * Output parameter that is returned by the server.\n     */\n    OUT\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/RpcEncoding.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TdsDataType;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.StringUtils;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.function.BiConsumer;\nimport java.util.function.BiFunction;\nimport java.util.function.Supplier;\n\n/**\n * Utility methods to encode RPC parameters.\n *\n * @author Mark Paluch\n */\npublic final class RpcEncoding {\n\n    private RpcEncoding() {\n    }\n\n    /**\n     * Encode a string parameter as {@literal NVARCHAR} or {@literal NTEXT} (depending on the size).\n     *\n     * @param buffer    the data buffer.\n     * @param name      optional parameter name.\n     * @param direction RPC parameter direction (in/out)\n     * @param collation parameter value encoding.\n     * @param value     the parameter value, can be {@code null}.\n     */\n    public static void encodeString(ByteBuf buffer, @Nullable String name, RpcDirection direction, Collation collation, @Nullable String value) {\n\n        encodeHeader(buffer, name, direction, TdsDataType.NVARCHAR);\n        CharacterEncoder.encodeBigVarchar(buffer, direction, collation, true, value);\n    }\n\n    /**\n     * Encode an integer parameter as {@literal INTn}.\n     *\n     * @param buffer    the data buffer.\n     * @param name      optional parameter name.\n     * @param direction RPC parameter direction (in/out)\n     * @param value     the parameter value, can be {@code null}.\n     */\n    public static void encodeInteger(ByteBuf buffer, @Nullable String name, RpcDirection direction, @Nullable Integer value) {\n\n        encodeHeader(buffer, name, direction, TdsDataType.INTN);\n\n        Encode.asByte(buffer, 4); // max-len\n\n        if (value == null) {\n            Encode.asByte(buffer, 0); // len of data bytes\n        } else {\n            Encode.asByte(buffer, 4); // len of data bytes\n            Encode.asInt(buffer, value);\n        }\n    }\n\n    /**\n     * Encode an RPC header that writes {@code name}, {@link RpcDirection}, and the {@link TdsDataType}.\n     *\n     * @param buffer    the data buffer.\n     * @param name      name of the parameter, can be {@code null}.\n     * @param direction the parameter direction.\n     * @param dataType  TDS data type.\n     */\n    public static void encodeHeader(ByteBuf buffer, @Nullable String name, RpcDirection direction, TdsDataType dataType) {\n\n        if (StringUtils.hasText(name)) {\n\n            Encode.asByte(buffer, name.length() + 1);\n\n            char at = '@';\n            writeChar(buffer, at);\n\n            for (int i = 0; i < name.length(); i++) {\n                char ch = name.charAt(i);\n                writeChar(buffer, ch);\n            }\n        } else {\n            Encode.asByte(buffer, 0);\n        }\n\n        Encode.asByte(buffer, direction == RpcDirection.OUT ? 1 : 0);\n        Encode.asByte(buffer, dataType.getValue());\n    }\n\n    private static void writeChar(ByteBuf buffer, char ch) {\n\n        buffer.writeByte((byte) (ch & 0xFF));\n        buffer.writeByte((byte) ((ch >> 8) & 0xFF));\n    }\n\n    /**\n     * Encode a RPC parameter that uses a fixed-length, nullable data type.\n     *\n     * @param allocator    the allocator to allocate encoding buffers.\n     * @param serverType   the server type. Used to derive the nullable {@link TdsDataType}.\n     * @param value        the value to encode.\n     * @param valueEncoder encoder function. Using a {@link BiFunction} to allow non-capturing lambdas.\n     * @param <T>\n     * @return\n     */\n    public static <T> Encoded encodeFixed(ByteBufAllocator allocator, SqlServerType serverType, T value, BiConsumer<ByteBuf, T> valueEncoder) {\n\n        Assert.notNull(serverType.getNullableType(), \"Server type provides no nullable type\");\n        LengthStrategy lengthStrategy = serverType.getNullableType().getLengthStrategy();\n\n        return new HintedEncoded(serverType.getNullableType(), serverType, () -> {\n\n            ByteBuf buffer = prepareBuffer(allocator, lengthStrategy, serverType.getMaxLength(), serverType.getMaxLength());\n\n            valueEncoder.accept(buffer, value);\n\n            return buffer;\n        });\n    }\n\n    /**\n     * Encode a RPC parameter that declares length and max-length attributes and apply a {@link SqlServerType} hint.\n     *\n     * @param allocator    the allocator to allocate encoding buffers.\n     * @param serverType   the server data type. Used to derive the nullable {@link TdsDataType}.\n     * @param length       actual data length.\n     * @param value        the value to encode.\n     * @param valueEncoder encoder function. Using a {@link BiFunction} to allow non-capturing lambdas.\n     * @param <T>\n     * @return\n     */\n    public static <T> Encoded encode(ByteBufAllocator allocator, SqlServerType serverType, int length, T value, BiConsumer<ByteBuf, T> valueEncoder) {\n\n        Assert.notNull(serverType.getNullableType(), \"Server type provides no nullable type\");\n\n        TdsDataType dataType = serverType.getNullableType();\n\n\n        return new HintedEncoded(dataType, serverType, () -> {\n\n            ByteBuf buffer = prepareBuffer(allocator, dataType.getLengthStrategy(), serverType.getMaxLength(), length);\n\n            valueEncoder.accept(buffer, value);\n\n            return buffer;\n        });\n    }\n\n    /**\n     * Encode a {@code null} RPC parameter that declares length and max-length attributes and apply a {@link SqlServerType} hint.\n     *\n     * @param allocator  the allocator to allocate encoding buffers.\n     * @param serverType the server data type. Used to derive the nullable {@link TdsDataType}.\n     * @return the encoded {@code null} value.\n     */\n    public static Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n\n        Assert.notNull(serverType.getNullableType(), \"Server type does not declare a nullable type\");\n\n        return new HintedEncoded(serverType.getNullableType(), serverType, () -> prepareBuffer(allocator, serverType.getNullableType().getLengthStrategy(), serverType.getMaxLength(), 0));\n    }\n\n    /**\n     * Wrap a binary encoded RPC parameter and apply a {@link SqlServerType} hint.\n     *\n     * @param buffer     the encoded buffer.\n     * @param serverType the server data type. Used to derive the nullable {@link TdsDataType}.\n     * @return the encoded {@code null} value.\n     */\n    public static Encoded wrap(byte[] buffer, SqlServerType serverType) {\n\n        Assert.isTrue(serverType.getMaxLength() > 0, \"Server type does not declare a max length\");\n        Assert.notNull(serverType.getNullableType(), \"Server type does not declare a nullable type\");\n\n        return new HintedEncoded(serverType.getNullableType(), serverType, () -> Unpooled.wrappedBuffer(buffer));\n    }\n\n    /**\n     * Encode a temporal typed {@code null} RPC parameter.\n     *\n     * @param allocator  the allocator to allocate encoding buffers.\n     * @param serverType the server data type. Used to derive the nullable {@link TdsDataType}.\n     * @return the encoded {@code null} value.\n     */\n    public static Encoded encodeTemporalNull(ByteBufAllocator allocator, SqlServerType serverType) {\n\n        Assert.notNull(serverType.getNullableType(), \"Server type does not declare a nullable type\");\n\n\n        return new HintedEncoded(serverType.getNullableType(), serverType, () -> {\n\n            ByteBuf buffer = allocator.buffer(1);\n\n            Encode.asByte(buffer, 0);\n\n            return buffer;\n        });\n    }\n\n    /**\n     * Encode a temporal scaled {@code null} RPC parameter.\n     *\n     * @param allocator  the allocator to allocate encoding buffers.\n     * @param serverType the server data type. Used to derive the nullable {@link TdsDataType}.\n     * @param scale      type scale.\n     * @return the encoded {@code null} value.\n     */\n    public static Encoded encodeTemporalNull(ByteBufAllocator allocator, SqlServerType serverType, int scale) {\n\n        Assert.notNull(serverType.getNullableType(), \"Server type does not declare a nullable type\");\n\n\n        return new HintedEncoded(serverType.getNullableType(), serverType, () -> {\n\n            ByteBuf buffer = allocator.buffer(1);\n\n            Encode.asByte(buffer, scale);\n            Encode.asByte(buffer, 0); // value length\n\n            return buffer;\n        });\n    }\n\n    static ByteBuf prepareBuffer(ByteBufAllocator allocator, LengthStrategy lengthStrategy, int maxLength, int length) {\n\n        ByteBuf buffer;\n        switch (lengthStrategy) {\n            case PARTLENTYPE:\n\n                buffer = allocator.buffer(8 + 8 + length);\n                buffer.writeLong(maxLength).writeLong(length);\n\n                return buffer;\n\n            case BYTELENTYPE:\n\n                buffer = allocator.buffer(1 + 1 + length);\n                Encode.asByte(buffer, maxLength);\n                Encode.asByte(buffer, length);\n                return buffer;\n\n            case FIXEDLENTYPE:\n\n                buffer = allocator.buffer();\n                return buffer;\n\n            case USHORTLENTYPE:\n\n                buffer = allocator.buffer(1 + 1 + length);\n                Encode.uShort(buffer, maxLength);\n                Encode.uShort(buffer, length);\n\n                return buffer;\n            default:\n                throw new UnsupportedOperationException(lengthStrategy.toString());\n        }\n    }\n\n    /**\n     * Extension to {@link Encoded} that applies a {@link SqlServerType} hint.\n     */\n    static class HintedEncoded extends Encoded {\n\n        private final SqlServerType sqlServerType;\n\n        public HintedEncoded(TdsDataType dataType, SqlServerType sqlServerType, Supplier<ByteBuf> value) {\n            super(dataType, value);\n            this.sqlServerType = sqlServerType;\n        }\n\n        @Override\n        public String getFormalType() {\n            return this.sqlServerType.toString();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/RpcParameterContext.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Parameter context for RPC parameters. Encapsulated {@link RpcDirection} and an optional {@link ValueContext}.\n *\n * @author Mark Paluch\n */\npublic final class RpcParameterContext {\n\n    private static final RpcParameterContext IN = new RpcParameterContext(RpcDirection.IN, null, null);\n\n    private static final RpcParameterContext OUT = new RpcParameterContext(RpcDirection.OUT, null, null);\n\n    private final RpcDirection direction;\n\n    @Nullable\n    private final ValueContext valueContext;\n\n    @Nullable\n    private final SqlServerType serverType;\n\n    private RpcParameterContext(RpcDirection direction, @Nullable ValueContext valueContext, @Nullable SqlServerType serverType) {\n        this.direction = direction;\n        this.serverType = serverType;\n        this.valueContext = valueContext;\n    }\n\n    /**\n     * Returns a context for in (client to server) parameters.\n     *\n     * @return a context for in (client to server) parameters.\n     * @see RpcDirection#IN\n     */\n    public static RpcParameterContext in() {\n        return IN;\n    }\n\n    /**\n     * Returns a context for in (client to server) parameters with the associated {@link ValueContext}.\n     *\n     * @param valueContext the value context.\n     * @return a context for in (client to server) parameters with the associated {@link ValueContext}.\n     * @see RpcDirection#IN\n     */\n    public static RpcParameterContext in(ValueContext valueContext) {\n        return new RpcParameterContext(RpcDirection.IN, Assert.requireNonNull(valueContext, \"ValueContext must not be null\"), null);\n    }\n\n    /**\n     * Returns a context for out (server to client) parameters.\n     *\n     * @return a context for out (server to client) parameters.\n     * @see RpcDirection#OUT\n     */\n    public static RpcParameterContext out() {\n        return OUT;\n    }\n\n    /**\n     * Returns a context for out (server to client) parameters with the associated {@link ValueContext}.\n     *\n     * @param valueContext the value context.\n     * @return a context for out (server to client) parameters with the associated {@link ValueContext}.\n     * @see RpcDirection#IN\n     */\n    public static RpcParameterContext out(ValueContext valueContext) {\n        return new RpcParameterContext(RpcDirection.OUT, Assert.requireNonNull(valueContext, \"ValueContext must not be null\"), null);\n    }\n\n    /**\n     * @return the RPC direction.\n     */\n    public RpcDirection getDirection() {\n        return this.direction;\n    }\n\n    /**\n     * @return {@code true} if this parameter is a in parameter.\n     */\n    public boolean isIn() {\n        return this.direction == RpcDirection.IN;\n    }\n\n    /**\n     * @return {@code true} if this parameter is a out parameter.\n     */\n    public boolean isOut() {\n        return this.direction == RpcDirection.OUT;\n    }\n\n    /**\n     * @return the value context, can be {@code null}.\n     */\n    @Nullable\n    public ValueContext getValueContext() {\n        return this.valueContext;\n    }\n\n    @Nullable\n    public SqlServerType getServerType() {\n        return this.serverType;\n    }\n\n    /**\n     * Return the required {@link ValueContext} or throw {@link IllegalStateException} if no {@link ValueContext} is set..\n     *\n     * @return the value context.\n     * @throws IllegalArgumentException if the {@link ValueContext} is not set.\n     */\n    public ValueContext getRequiredValueContext() {\n\n        ValueContext valueContext = getValueContext();\n\n        if (valueContext == null) {\n            throw new IllegalStateException(\"No ValueContext set\");\n        }\n\n        return valueContext;\n    }\n\n    /**\n     * Return the required {@link ValueContext} as typed {@code T} or throw {@link IllegalStateException} if no {@link ValueContext} is set.\n     *\n     * @return the value context.\n     * @throws IllegalArgumentException if the {@link ValueContext} is not set.\n     * @throws ClassCastException       if the {@link ValueContext} is not of type {@code contextType}.\n     */\n    public <T extends ValueContext> T getRequiredValueContext(Class<? extends T> contextType) {\n\n        ValueContext valueContext = getRequiredValueContext();\n\n        return contextType.cast(valueContext);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [direction=\").append(this.direction);\n        sb.append(\", valueContext=\").append(this.valueContext);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    public RpcParameterContext withServerType(SqlServerType serverType) {\n        Assert.requireNonNull(serverType, \"SqlServerType must not be null\");\n        return new RpcParameterContext(this.direction, this.valueContext, serverType);\n    }\n\n    /**\n     * Marker interface for additional contextual information that are used for value encoding.\n     */\n    public interface ValueContext {\n\n        static ValueContext character(Collation collation, boolean sendStringParametersAsUnicode) {\n            return new CharacterValueContext(collation, sendStringParametersAsUnicode);\n        }\n\n    }\n\n    /**\n     * Marker interface for additional contextual information.\n     */\n    public static class CharacterValueContext implements ValueContext {\n\n        private final Collation collation;\n\n        private final boolean sendStringParametersAsUnicode;\n\n        public CharacterValueContext(Collation collation, boolean sendStringParametersAsUnicode) {\n            this.collation = collation;\n            this.sendStringParametersAsUnicode = sendStringParametersAsUnicode;\n        }\n\n        public Collation getCollation() {\n            return this.collation;\n        }\n\n        public boolean isSendStringParametersAsUnicode() {\n            return this.sendStringParametersAsUnicode;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/ShortCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.SqlServerType;\n\n/**\n * Codec for numeric values that are represented as {@link Short}.\n *\n * <ul>\n * <li>Server types: Integer numbers</li>\n * <li>Java type: {@link Short}</li>\n * <li>Downcast: to {@link Short}</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class ShortCodec extends AbstractNumericCodec<Short> {\n\n    /**\n     * Singleton instance.\n     */\n    static final ShortCodec INSTANCE = new ShortCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeNull(alloc, SqlServerType.SMALLINT));\n\n    private ShortCodec() {\n        super(Short.class, value -> (short) value);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, Short value) {\n        return RpcEncoding.encodeFixed(allocator, SqlServerType.SMALLINT, value, Encode::smallInt);\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.SMALLINT);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/StringCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.CompositeByteBuf;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.PlpLength;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.message.type.TypeUtils;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.charset.Charset;\nimport java.util.EnumSet;\nimport java.util.Locale;\nimport java.util.Set;\nimport java.util.UUID;\n\n/**\n * Codec for character values that are represented as {@link String}.\n *\n * <ul>\n * <li>Server types: (N)(VAR)CHAR, (N)TEXT {@link SqlServerType#GUID}</li>\n * <li>Java type: {@link String}</li>\n * <li>Downcast: to {@link UUID#toString()}</li>\n * </ul>\n *\n * @author Mark Paluch\n * @author Anton Duyun\n */\nfinal class StringCodec extends AbstractCodec<String> {\n\n    /**\n     * Singleton instance.\n     */\n    static final StringCodec INSTANCE = new StringCodec();\n\n    private static final Set<SqlServerType> SUPPORTED_TYPES = EnumSet.of(SqlServerType.CHAR, SqlServerType.NCHAR,\n        SqlServerType.VARCHAR, SqlServerType.NVARCHAR,\n        SqlServerType.VARCHARMAX, SqlServerType.NVARCHARMAX,\n        SqlServerType.TEXT, SqlServerType.NTEXT,\n        SqlServerType.GUID);\n\n    private StringCodec() {\n        super(String.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, String value) {\n\n        RpcParameterContext.CharacterValueContext valueContext = context.getRequiredValueContext(RpcParameterContext.CharacterValueContext.class);\n\n        SqlServerType serverType = context.getServerType();\n\n        if (exceedsBigVarchar(context.getDirection(), value) || serverType == SqlServerType.VARCHARMAX || serverType == SqlServerType.NVARCHARMAX) {\n            return CharacterEncoder.encodePlp(allocator, serverType, valueContext, value);\n        }\n\n        return CharacterEncoder.encodeBigVarchar(allocator, context.getDirection(), serverType, valueContext.getCollation(), valueContext.isSendStringParametersAsUnicode(), value);\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.VARCHAR || serverType == SqlServerType.NVARCHAR;\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return encodeNull(allocator, SqlServerType.NVARCHAR);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return CharacterEncoder.encodeNull(serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return SUPPORTED_TYPES.contains(typeInformation.getServerType());\n    }\n\n    @Nullable\n    public String decode(@Nullable ByteBuf buffer, Decodable decodable, Class<? extends String> type) {\n\n        Assert.requireNonNull(decodable, \"Decodable must not be null\");\n        Assert.requireNonNull(type, \"Type must not be null\");\n\n        if (buffer == null) {\n            return null;\n        }\n\n        Length length;\n\n        if (decodable.getType().getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n            PlpLength plpLength = PlpLength.decode(buffer, decodable.getType());\n            length = Length.of(Math.toIntExact(plpLength.getLength()), plpLength.isNull());\n        } else {\n            length = Length.decode(buffer, decodable.getType());\n        }\n\n        return doDecode(buffer, length, decodable.getType(), type);\n    }\n\n    @Override\n    String doDecode(ByteBuf buffer, Length length, TypeInformation typeInformation, Class<? extends String> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        if (typeInformation.getServerType() == SqlServerType.GUID) {\n\n            UUID uuid = UuidCodec.INSTANCE.doDecode(buffer, length, typeInformation, UUID.class);\n            return uuid != null ? uuid.toString().toUpperCase(Locale.ENGLISH) : null;\n        }\n\n        Charset charset = typeInformation.getCharset();\n\n        if (typeInformation.getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n\n            CompositeByteBuf result = buffer.alloc().compositeBuffer();\n\n            try {\n                while (buffer.isReadable()) {\n\n                    Length chunkLength = Length.decode(buffer, typeInformation);\n                    result.addComponent(true, buffer.readRetainedSlice(chunkLength.getLength()));\n                }\n\n                return result.toString(charset);\n            } finally {\n                result.release();\n            }\n        }\n\n        String value = buffer.toString(buffer.readerIndex(), length.getLength(), charset);\n        buffer.skipBytes(length.getLength());\n\n        return valueType.cast(value);\n    }\n\n    static boolean exceedsBigVarchar(RpcDirection direction, String value) {\n\n        int valueLength = (value.length() * 2);\n        boolean isShortValue = valueLength <= TypeUtils.SHORT_VARTYPE_MAX_BYTES;\n\n        // Use PLP encoding on Yukon and later with long values and OUT parameters\n        return (!isShortValue || direction == RpcDirection.OUT);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/TimestampCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\n/**\n * Codec for binary timestamp values that are represented as {@code byte[]}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#TIMESTAMP} (8-byte)</li>\n * <li>Java type: {@code byte[]}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class TimestampCodec extends AbstractCodec<byte[]> {\n\n    static final TimestampCodec INSTANCE = new TimestampCodec();\n\n    private TimestampCodec() {\n        super(byte[].class);\n    }\n\n    @Override\n    public boolean canEncode(Object value) {\n        return false;\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, byte[] value) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return false;\n    }\n\n    @Override\n    protected Encoded doEncodeNull(ByteBufAllocator allocator) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.TIMESTAMP;\n    }\n\n    @Override\n    byte[] doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends byte[]> valueType) {\n\n        byte[] value = new byte[length.getLength()];\n\n        buffer.readBytes(value);\n\n        return value;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/UuidCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\nimport java.util.UUID;\n\n/**\n * @author Mark Paluch\n */\nfinal class UuidCodec extends AbstractCodec<UUID> {\n\n    /**\n     * Singleton instance.\n     */\n    static final UuidCodec INSTANCE = new UuidCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded(alloc -> RpcEncoding.encodeNull(alloc, SqlServerType.GUID));\n\n    private UuidCodec() {\n        super(UUID.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, UUID value) {\n\n        return RpcEncoding.encode(allocator, SqlServerType.GUID, 16, value, (buffer, uuid) -> {\n\n            long msb = value.getMostSignificantBits();\n            long lsb = value.getLeastSignificantBits();\n\n            buffer.writeBytes(swapForWrite(msb));\n            buffer.writeLong(lsb);\n        });\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.GUID;\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.GUID);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeNull(allocator, serverType);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.GUID;\n    }\n\n    @Override\n    UUID doDecode(ByteBuf buffer, Length length, TypeInformation typeInformation, Class<? extends UUID> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        byte[] bytes = new byte[8];\n        buffer.readBytes(bytes);\n\n        long msb = swapForRead(bytes);\n        long lsb = buffer.readLong();\n\n        return new UUID(msb, lsb);\n    }\n\n    /**\n     * Swap bytes. MSB is represented in order 3,2,1,0 and 5,4,7,6\n     *\n     * @param memory\n     * @return\n     */\n    private static long swapForRead(byte[] memory) {\n        return ((long) memory[3] & 0xff) << 56 |\n            ((long) memory[2] & 0xff) << 48 |\n            ((long) memory[1] & 0xff) << 40 |\n            ((long) memory[0] & 0xff) << 32 |\n            ((long) memory[5] & 0xff) << 24 |\n            ((long) memory[4] & 0xff) << 16 |\n            ((long) memory[7] & 0xff) << 8 |\n            (long) memory[6] & 0xff;\n    }\n\n    /**\n     * Swap bytes. MSB is represented in order 3,2,1,0 and 5,4,7,6\n     *\n     * @param msb\n     * @return\n     */\n    private byte[] swapForWrite(long msb) {\n\n        byte[] bytes = new byte[8];\n        bytes[3] = (byte) ((msb >> 56) & 0xff);\n        bytes[2] = (byte) ((msb >> 48) & 0xff);\n        bytes[1] = (byte) ((msb >> 40) & 0xff);\n        bytes[0] = (byte) ((msb >> 32) & 0xff);\n\n        bytes[5] = (byte) ((msb >> 24) & 0xff);\n        bytes[4] = (byte) ((msb >> 16) & 0xff);\n        bytes[7] = (byte) ((msb >> 8) & 0xff);\n        bytes[6] = (byte) ((msb) & 0xff);\n\n        return bytes;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/ZonedDateTimeCodec.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\nimport java.time.OffsetDateTime;\nimport java.time.ZonedDateTime;\n\n/**\n * Codec for temporal types that are represented as {@link ZonedDateTime}.\n *\n * <ul>\n * <li>Server types: {@link SqlServerType#DATETIMEOFFSET}</li>\n * <li>Java type: {@link ZonedDateTime}</li>\n * <li>Downcast: none</li>\n * </ul>\n *\n * @author Mark Paluch\n */\nfinal class ZonedDateTimeCodec extends AbstractCodec<ZonedDateTime> {\n\n    /**\n     * Singleton instance.\n     */\n    static final ZonedDateTimeCodec INSTANCE = new ZonedDateTimeCodec();\n\n    private static final byte[] NULL = ByteArray.fromEncoded((alloc) -> RpcEncoding.encodeTemporalNull(alloc, SqlServerType.DATETIMEOFFSET, 7));\n\n    private ZonedDateTimeCodec() {\n        super(ZonedDateTime.class);\n    }\n\n    @Override\n    Encoded doEncode(ByteBufAllocator allocator, RpcParameterContext context, ZonedDateTime value) {\n        return OffsetDateTimeCodec.INSTANCE.encode(allocator, context, value.toOffsetDateTime());\n    }\n\n    @Override\n    public boolean canEncodeNull(SqlServerType serverType) {\n        return serverType == SqlServerType.DATETIMEOFFSET;\n    }\n\n    @Override\n    public Encoded doEncodeNull(ByteBufAllocator allocator) {\n        return RpcEncoding.wrap(NULL, SqlServerType.DATETIMEOFFSET);\n    }\n\n    @Override\n    public Encoded encodeNull(ByteBufAllocator allocator, SqlServerType serverType) {\n        return RpcEncoding.encodeTemporalNull(allocator, serverType, 7);\n    }\n\n    @Override\n    boolean doCanDecode(TypeInformation typeInformation) {\n        return typeInformation.getServerType() == SqlServerType.DATETIMEOFFSET;\n    }\n\n    @Override\n    ZonedDateTime doDecode(ByteBuf buffer, Length length, TypeInformation type, Class<? extends ZonedDateTime> valueType) {\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        OffsetDateTime offsetDateTime = OffsetDateTimeCodec.INSTANCE.doDecode(buffer, length, type, OffsetDateTime.class);\n        return offsetDateTime.toZonedDateTime();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/codec/package-info.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Encoders and Decoders for the type that the service provider understands.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.codec;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/ClientMessage.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport org.reactivestreams.Publisher;\n\n/**\n * A message sent from a client to a server.\n *\n * @author Mark Paluch\n */\npublic interface ClientMessage extends Message {\n\n    /**\n     * Encode a message into a {@link TdsFragment data buffer}. Can encode either to a scalar {@link TdsFragment} or a {@link Publisher} of {@link TdsFragment}.\n     *\n     * @param allocator  the {@link ByteBufAllocator} to use to get a {@link ByteBuf data buffer} to write into.\n     * @param packetSize packet size hint.\n     * @return a scalar {@link TdsFragment} or a {@link Publisher} that produces the {@link TdsFragment} containing the encoded message.\n     */\n    Object encode(ByteBufAllocator allocator, int packetSize);\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/Message.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message;\n\nimport io.r2dbc.mssql.message.token.TokenStream;\n\n/**\n * A TDS message. Messages declare typically encode or decode functions.\n *\n * @see ClientMessage\n * @see TokenStream\n */\npublic interface Message {\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/TDSVersion.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message;\n\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * TDS protocol versions.\n *\n * @author Mark Paluch\n */\npublic enum TDSVersion {\n\n    // TDS 7.4\n    VER_DENALI(0x74000004),\n\n    // TDS 7.3B(includes null bit compression)\n    VER_KATMAI(0x730B0003),\n\n    // TDS 7.2\n    VER_YUKON(0x72090002),\n\n    UNKNOWN(0x00000000);\n\n    private final int version;\n\n    TDSVersion(int version) {\n        this.version = version;\n    }\n\n    public int getVersion() {\n        return this.version;\n    }\n\n    /**\n     * Check is the reference {@link TDSVersion} is greater or equal to {@code this} version.\n     *\n     * @param reference the reference version.\n     * @return {@code true} if the reference {@link TDSVersion} is greater or equal to {@code this} version.\n     */\n    public boolean isGreateOrEqualsTo(TDSVersion reference) {\n\n        Assert.requireNonNull(reference, \"Reference version must not be null\");\n        return getVersion() >= reference.getVersion();\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/TransactionDescriptor.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message;\n\nimport io.netty.buffer.ByteBufUtil;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.Arrays;\n\n/**\n * Descriptor for the transaction state.\n *\n * @author Mark Paluch\n */\npublic final class TransactionDescriptor {\n\n    /**\n     * Length in bytes of the binary transaction descriptor representation.\n     */\n    public static final int LENGTH = 8;\n\n    private final byte[] descriptor;\n\n    private TransactionDescriptor(byte[] descriptor) {\n\n        Assert.requireNonNull(descriptor, \"Descriptor bytes must not be null\");\n        Assert.isTrue(descriptor.length == LENGTH, \"Descriptor must be 8 bytes long\");\n\n        this.descriptor = descriptor;\n    }\n\n    /**\n     * Creates an empty {@link TransactionDescriptor}.\n     *\n     * @return the empty {@link TransactionDescriptor}.\n     */\n    public static TransactionDescriptor empty() {\n        return new TransactionDescriptor(new byte[8]);\n    }\n\n    /**\n     * Creates an {@link TransactionDescriptor} from {@code descriptor} bytes.\n     *\n     * @param descriptor descriptor bytes. Must be 8 bytes long.\n     * @return the {@link TransactionDescriptor} for {@code descriptor} bytes.\n     */\n    public static TransactionDescriptor from(byte[] descriptor) {\n        return new TransactionDescriptor(descriptor);\n    }\n\n    /**\n     * @return the binary representation of this {@link TransactionDescriptor}.\n     */\n    public byte[] toBytes() {\n        return this.descriptor;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof TransactionDescriptor)) {\n            return false;\n        }\n        TransactionDescriptor that = (TransactionDescriptor) o;\n        return Arrays.equals(this.descriptor, that.descriptor);\n    }\n\n    @Override\n    public int hashCode() {\n        return Arrays.hashCode(this.descriptor);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [\").append(ByteBufUtil.hexDump(this.descriptor));\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/header/DefaultHeaderOptions.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\n/**\n * Default implementation of {@link HeaderOptions}.\n *\n * @author Mark Paluch\n */\nclass DefaultHeaderOptions implements HeaderOptions {\n\n    private static final HeaderOptions[][] HEADER_CACHE = new HeaderOptions[Type.values().length][(-Byte.MIN_VALUE) + Byte.MAX_VALUE];\n\n    static {\n        for (Type value : Type.values()) {\n            for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {\n                int index = b - Byte.MIN_VALUE;\n                HEADER_CACHE[value.ordinal()][index] = new DefaultHeaderOptions(value, Status.fromBitmask(b));\n            }\n        }\n    }\n\n    static HeaderOptions get(Type value, Status status) {\n        int index = status.getValue() - Byte.MIN_VALUE;\n        return HEADER_CACHE[value.ordinal()][index];\n    }\n\n    private final Type type;\n\n    private final Status status;\n\n    DefaultHeaderOptions(Type type, Status status) {\n        this.type = type;\n        this.status = status;\n    }\n\n    @Override\n    public Type getType() {\n        return this.type;\n    }\n\n    @Override\n    public Status getStatus() {\n        return this.status;\n    }\n\n    @Override\n    public boolean is(Status.StatusBit bit) {\n        return this.status.is(bit);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [type=\").append(this.type);\n        sb.append(\", status=\").append(this.status);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/header/Header.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.Objects;\n\n/**\n * A header exchanged between client and server.\n */\npublic class Header implements HeaderOptions {\n\n    /**\n     * Number of bytes required to represent the header.\n     */\n    public static final int LENGTH = 8;\n\n    /**\n     * Type defines the type of message. 1-byte.\n     */\n    private final Type type;\n\n    /**\n     * Status is a bit field used to indicate the message state. 1-byte.\n     */\n    private final Status status;\n\n    /**\n     * Length is the size of the packet including the 8 bytes in the packet header. It is the number of bytes from the\n     * start of this header to the start of the next packet header. Length is a 2-byte, unsigned short int and is\n     * represented in network byte order (big-endian).\n     * <p/>\n     * Starting with TDS 7.3, the Length MUST be the negotiated packet size when sending a packet from client to server,\n     * unless it is the last packet of a request (that is, the EOM bit in Status is ON), or the client has not logged in.\n     */\n    private final short length;\n\n    /**\n     * Spid is the process ID on the server, corresponding to the current connection.\n     * <p/>\n     * This information is sent by the server to the client and is useful for identifying which thread on the server sent\n     * the TDS packet. It is provided for debugging purposes. The client MAY send the SPID value to the server. If the\n     * client does not, then a value of {@code 0x0000} SHOULD be sent to the server. This is a 2-byte value and is\n     * represented in network byte order (big-endian).\n     */\n    private final short spid;\n\n    /**\n     * PacketID is used for numbering message packets that contain data in addition to the packet header.\n     * <p/>\n     * PacketID is a 1-byte, unsigned char. Each time packet data is sent, the value of PacketID is incremented by 1,\n     * modulo 256.<7> This allows the receiver to track the sequence of TDS packets for a given message. This value is\n     * currently ignored.\n     */\n    private final byte packetId;\n\n    /**\n     * This 1 byte is currently not used. This byte SHOULD be set to 0x00 and SHOULD be ignored by the receiver.\n     */\n    private final byte window;\n\n    public Header(Type type, Status status, int length, int spid) {\n        this(type, status, (short) length, (short) spid, (byte) 0, (byte) 0);\n    }\n\n    public Header(Type type, Status status, int length, int spid, int packetId, int window) {\n        this(type, status, (short) length, (short) spid, (byte) packetId, (byte) window);\n    }\n\n    public Header(Type type, Status status, short length, short spid, byte packetId, byte window) {\n\n        Assert.requireNonNull(type, \"Type must not be null\");\n        Assert.requireNonNull(status, \"sStatus must not be null\");\n        Assert.isTrue(length >= 8, \"Header length must be greater or equal to 8\");\n\n        this.type = type;\n        this.status = status;\n        this.length = length;\n        this.spid = spid;\n        this.packetId = packetId;\n        this.window = window;\n    }\n\n    /**\n     * Create a {@link Header} given {@link HeaderOptions}, packet {@code length}, and {@link PacketIdProvider}.\n     *\n     * @param options          the {@link HeaderOptions}.\n     * @param length           packet length.\n     * @param packetIdProvider the {@link PacketIdProvider}.\n     * @return the {@link Header}.\n     * @throws IllegalArgumentException when {@link HeaderOptions} or {@link PacketIdProvider} is {@code null}.\n     */\n    public static Header create(HeaderOptions options, int length, PacketIdProvider packetIdProvider) {\n\n        Assert.requireNonNull(options, \"HeaderOptions must not be null\");\n        Assert.requireNonNull(packetIdProvider, \"PacketIdProvider must not be null\");\n\n        return new Header(options.getType(), options.getStatus(), length, 0, packetIdProvider.nextPacketId(), 0);\n    }\n\n    public Type getType() {\n        return this.type;\n    }\n\n    public Status getStatus() {\n        return this.status;\n    }\n\n    public boolean is(Status.StatusBit bit) {\n        return this.status.is(bit);\n    }\n\n    public short getSpid() {\n        return this.spid;\n    }\n\n    public byte getPacketId() {\n        return this.packetId;\n    }\n\n    public byte getWindow() {\n        return this.window;\n    }\n\n    public short getLength() {\n        return this.length;\n    }\n\n    /**\n     * Encode a header into a {@link ByteBuf}.\n     *\n     * @param buffer the target {@link ByteBuf}.\n     */\n    public void encode(ByteBuf buffer) {\n        encode(buffer, this.type, this.status, this.length, this.spid, this.packetId, this.window);\n    }\n\n    /**\n     * Encode a header into a {@link ByteBuf}.\n     *\n     * @param buffer           the target {@link ByteBuf}.\n     * @param packetIdProvider must not be {@code null}.\n     * @throws IllegalArgumentException when {@link HeaderOptions} or {@link PacketIdProvider} is {@code null}.\n     */\n    public void encode(ByteBuf buffer, PacketIdProvider packetIdProvider) {\n        encode(buffer, this.type, this.status, this.length, this.spid, packetIdProvider.nextPacketId(), this.window);\n    }\n\n    /**\n     * Encode a header into a {@link ByteBuf}.\n     *\n     * @param buffer           the target {@link ByteBuf}.\n     * @param options          header options.\n     * @param length           packet length.\n     * @param packetIdProvider must not be {@code null}.\n     * @throws IllegalArgumentException when {@link HeaderOptions} or {@link PacketIdProvider} is {@code null}.\n     */\n    public static void encode(ByteBuf buffer, HeaderOptions options, int length, PacketIdProvider packetIdProvider) {\n        encode(buffer, options.getType(), options.getStatus(), length, (short) 0, packetIdProvider.nextPacketId(), (byte) 0);\n    }\n\n    /**\n     * Encode the {@link Header}.\n     *\n     * @param buffer   the target {@link ByteBuf}.\n     * @param type     packet type.\n     * @param status   fragmentation/message status.\n     * @param length   packet length.\n     * @param spid     the spid (unused).\n     * @param packetId the packet Id.\n     * @param window   the window (unused).\n     */\n    public static void encode(ByteBuf buffer, Type type, Status status, int length, short spid, byte packetId, byte window) {\n\n        buffer.ensureWritable(8);\n\n        buffer.writeByte(type.getValue());\n        buffer.writeByte(status.getValue());\n        buffer.writeShort(length);\n        buffer.writeShort(spid);\n        buffer.writeByte(packetId);\n        buffer.writeByte(window);\n    }\n\n    /**\n     * @param buffer the data buffer to inspect.\n     * @return {@code true} if the header can be decoded.\n     */\n    public static boolean canDecode(ByteBuf buffer) {\n        return buffer.readableBytes() >= LENGTH;\n    }\n\n    /**\n     * @param buffer the data buffer.\n     * @return the decoded {@link Header}.\n     */\n    public static Header decode(ByteBuf buffer) {\n\n        Type type = Type.valueOf(buffer.readByte());\n        Status status = Status.fromBitmask(buffer.readByte());\n        short length = buffer.readShort();\n        short spid = buffer.readShort();\n        byte packetId = buffer.readByte();\n        byte window = buffer.readByte();\n\n        return new Header(type, status, length, spid, packetId, window);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof Header)) {\n            return false;\n        }\n        Header header = (Header) o;\n        return this.length == header.length &&\n            this.spid == header.spid &&\n            this.packetId == header.packetId &&\n            this.window == header.window &&\n            this.type == header.type &&\n            Objects.equals(this.status, header.status);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.type, this.status, this.length, this.spid, this.packetId, this.window);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [type=\").append(this.type);\n        sb.append(\", status=\").append(this.status);\n        sb.append(\", length=\").append(this.length);\n        sb.append(\", spid=\").append(this.spid);\n        sb.append(\", packetId=\").append(this.packetId);\n        sb.append(\", window=\").append(this.window);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/header/HeaderOptions.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Base header options defining {@link Type} and {@link Status}. Typically used to provide a TDS packet context so\n * lower-level components can form a {@link Header} packet from this options and TDS payload.\n */\npublic interface HeaderOptions {\n\n    /**\n     * Create a {@link HeaderOptions} object with {@link Status.StatusBit} set.\n     *\n     * @param bit status bit to set.\n     * @return the {@link HeaderOptions}.\n     */\n    default HeaderOptions and(Status.StatusBit bit) {\n\n        Status status = getStatus();\n        Status newStatus = status.and(bit);\n\n        if (status == newStatus) {\n            return this;\n        }\n\n        return DefaultHeaderOptions.get(getType(), newStatus);\n    }\n\n    /**\n     * Create a {@link HeaderOptions} object with {@link Status.StatusBit} removed.\n     *\n     * @param bit status bit to remove.\n     * @return the {@link HeaderOptions}.\n     */\n    default HeaderOptions not(Status.StatusBit bit) {\n\n        Status status = getStatus();\n        Status newStatus = status.not(bit);\n\n        if (status == newStatus) {\n            return this;\n        }\n\n        return DefaultHeaderOptions.get(getType(), newStatus);\n    }\n\n    /**\n     * Defines the type of message. 1-byte.\n     *\n     * @return the message type.\n     */\n    Type getType();\n\n    /**\n     * Status is a bit field used to indicate the message state. 1-byte.\n     *\n     * @return the {@link Status.StatusBit}.\n     */\n    Status getStatus();\n\n    /**\n     * Check if the header status has set the {@link Status.StatusBit}.\n     *\n     * @param bit the status bit.\n     * @return {@code true} of the bit is set; {@code false} otherwise.\n     */\n    boolean is(Status.StatusBit bit);\n\n    /**\n     * Create {@link HeaderOptions} given {@link Type} and {@link Status}.\n     *\n     * @param type   the header {@link Type}.\n     * @param status the {@link Status}.\n     * @return the {@link HeaderOptions}.\n     */\n    static HeaderOptions create(Type type, Status status) {\n\n        Assert.requireNonNull(type, \"Type must not be null\");\n        Assert.requireNonNull(status, \"Status must not be null\");\n\n        return new DefaultHeaderOptions(type, status);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/header/PacketIdProvider.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\nimport io.netty.buffer.ByteBuf;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * Provider for the PacketId {@link Header} field.\n *\n * @author Mark Paluch\n * @see Header#getPacketId()\n * @see Header#encode(ByteBuf, PacketIdProvider)\n */\n@FunctionalInterface\npublic interface PacketIdProvider {\n\n    /**\n     * @return the next packet id.\n     */\n    byte nextPacketId();\n\n    /**\n     * Static packet Id provider.\n     *\n     * @param value packet number.\n     * @return {@link PacketIdProvider} returning the static {@code value}.\n     */\n    static PacketIdProvider just(int value) {\n        return just((byte) (value % 256));\n    }\n\n    /**\n     * Static packet Id provider.\n     *\n     * @param value packet number.\n     * @return {@link PacketIdProvider} returning the static {@code value}.\n     */\n    static PacketIdProvider just(byte value) {\n        return () -> value;\n    }\n\n    /**\n     * Atomic/concurrent packetId provider.\n     *\n     * @return a thread-safe packet counter.\n     */\n    static PacketIdProvider atomic() {\n\n        AtomicLong counter = new AtomicLong();\n        return () -> (byte) (counter.incrementAndGet() % 256);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/header/Status.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.Collection;\nimport java.util.EnumSet;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Packet header status as defined in ch {@literal 2.2.3.1.2 Status} of the TDS v20180912 spec.\n * <p>\n * Status is a bit field used to indicate the message state. Status is a 1-byte unsigned char. The following Status bit\n * flags are defined.\n *\n * @author Mark Paluch\n * @see StatusBit\n */\npublic class Status {\n\n    private static final Status[] STATUS_CACHE = new Status[(-Byte.MIN_VALUE) + Byte.MAX_VALUE];\n\n    static {\n        for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {\n            STATUS_CACHE[b - Byte.MIN_VALUE] = fromBitmask0(b);\n        }\n    }\n\n    private final byte value;\n\n    private Status(Set<StatusBit> statusBits) {\n        this.value = getStatusValue(statusBits);\n    }\n\n    /**\n     * Return an empty {@link Status}.\n     *\n     * @return the empty {@link Status}.\n     */\n    public static Status empty() {\n        return fromBitmask((byte) 0);\n    }\n\n    /**\n     * Create {@link Status} {@link Set} from the given {@code bitmask}.\n     *\n     * @param bitmask status bitmask.\n     * @return the {@link Status} for {@code bitmask}.\n     */\n    public static Status fromBitmask(byte bitmask) {\n        return STATUS_CACHE[bitmask - Byte.MIN_VALUE];\n    }\n\n    private static Status fromBitmask0(byte bitmask) {\n\n        EnumSet<StatusBit> result = EnumSet.noneOf(StatusBit.class);\n\n        for (StatusBit status : StatusBit.values()) {\n            if ((bitmask & status.getBits()) != 0) {\n                result.add(status);\n            }\n        }\n\n        return new Status(result);\n    }\n\n    /**\n     * Create a {@link Status} from the given {@link StatusBit}.\n     *\n     * @param bit the status bit.\n     * @return the {@link Status} from the given {@link StatusBit}.\n     */\n    public static Status of(StatusBit bit) {\n\n        Assert.requireNonNull(bit, \"StatusBit must not be null\");\n        return fromBitmask(bit.bits);\n    }\n\n    /**\n     * Create a {@link Status} from the given {@link StatusBit}s.\n     *\n     * @param bit   the status bit.\n     * @param other the status bits.\n     * @return the {@link Status} from the given {@link StatusBit}.\n     */\n    public static Status of(StatusBit bit, StatusBit... other) {\n\n        Assert.requireNonNull(bit, \"StatusBit must not be null\");\n        Assert.requireNonNull(other, \"StatusBits must not be null\");\n\n        byte result = bit.bits;\n\n        for (Status.StatusBit s : other) {\n            result |= s.bits;\n        }\n\n        return fromBitmask(result);\n    }\n\n    /**\n     * Create a {@link Status} from the current state and add the {@link StatusBit}.\n     *\n     * @param bit the status bit.\n     * @return the {@link Status} from the given {@link StatusBit}.\n     */\n    public Status and(StatusBit bit) {\n\n        // If bit set, then we can optimize.\n        if (is(bit)) {\n            return this;\n        }\n\n        byte mask = (byte) (this.getValue() | bit.bits);\n        return fromBitmask(mask);\n    }\n\n    /**\n     * Create a {@link Status} from the current state and remove the {@link StatusBit}.\n     *\n     * @param bit the status bit.\n     * @return the {@link Status} from the given {@link StatusBit}.\n     */\n    public Status not(StatusBit bit) {\n\n        // If bit not set, then we can optimize.\n        if (!is(bit)) {\n            return this;\n        }\n\n        byte mask = this.getValue();\n        mask &= ~bit.bits;\n\n        return fromBitmask(mask);\n    }\n\n    /**\n     * Check if the header status has set the {@link Status.StatusBit}.\n     *\n     * @param bit the status bit.\n     * @return {@code true} of the bit is set; {@code false} otherwise.\n     */\n    public boolean is(Status.StatusBit bit) {\n        return (this.value & bit.bits) != 0;\n    }\n\n    /**\n     * @return the status byte.\n     */\n    public byte getValue() {\n        return this.value;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof Status)) {\n            return false;\n        }\n        Status status = (Status) o;\n        return this.value == status.value;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.value);\n    }\n\n    private static byte getStatusValue(Collection<StatusBit> statusBits) {\n\n        byte result = 0;\n\n        for (Status.StatusBit s : statusBits) {\n            result |= s.getBits();\n        }\n\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return Integer.toHexString(this.value);\n    }\n\n    /**\n     * Packet header status bits as defined in ch {@literal 2.2.3.1.2 Status} of the TDS v20180912 spec.\n     * <p>\n     * Status is a bit field used to indicate the message state. Status is a 1-byte unsigned char. The following Status\n     * bit flags are defined.\n     */\n    public enum StatusBit {\n\n        NORMAL(0x00), EOM(0x01), IGNORE(0x02),\n\n        /**\n         * RESETCONNECTION\n         *\n         * @since TDS 7.1\n         */\n        RESET_CONNECTION(0x08),\n\n        /**\n         * RESETCONNECTIONSKIPTRAN\n         *\n         * @since TDS 7.3\n         */\n        RESET_CONNECTION_SKIP_TRAN(0x10);\n\n        StatusBit(int bits) {\n            this.bits = Integer.valueOf(bits).byteValue();\n        }\n\n        private final byte bits;\n\n        public int getBits() {\n            return this.bits;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/header/Type.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\n/**\n * Packet header type as defined in ch {@literal 2.2.3.1.1 Type} of the TDS v20180912 spec.\n * <p>\n * Type defines the type of message. Type is a 1-byte unsigned char.\n */\npublic enum Type {\n\n    SQL_BATCH(1), PRE_TDS7_LOGIN(2), RPC(3), TABULAR_RESULT(4), ATTENTION(6), BULK_LOAD_DATA(7), FED_AUTH_TOKEN(\n        8), TX_MGR(14), TDS7_LOGIN(16), SSPI(17), PRE_LOGIN(18);\n\n    Type(int value) {\n        this.value = Integer.valueOf(value).byteValue();\n    }\n\n    private final byte value;\n\n    /**\n     * Resolve header {@code value} into {@link Type}.\n     *\n     * @param value packet type identifier.\n     * @return the resolved {@link Type}.\n     */\n    public static Type valueOf(byte value) {\n\n        switch (value) {\n            case 1:\n                return SQL_BATCH;\n            case 2:\n                return PRE_TDS7_LOGIN;\n            case 3:\n                return RPC;\n            case 4:\n                return TABULAR_RESULT;\n            case 6:\n                return ATTENTION;\n            case 7:\n                return BULK_LOAD_DATA;\n            case 8:\n                return FED_AUTH_TOKEN;\n            case 14:\n                return TX_MGR;\n            case 16:\n                return TDS7_LOGIN;\n            case 17:\n                return SSPI;\n            case 18:\n                return PRE_LOGIN;\n        }\n\n        throw new IllegalArgumentException(String.format(\"Invalid header type: 0x%01X\", value));\n    }\n\n    public byte getValue() {\n        return this.value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/package-info.java",
    "content": "\n/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The messages that are both sent from a client to a server and from a server to a client.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.message;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/ContextualTdsFragment.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Represents a TDS message with associated {@link HeaderOptions}. The encoder may split this packet into multiple\n * packets if the packet size exceeds the negotiated packet size.\n *\n * @author Mark Paluch\n */\npublic class ContextualTdsFragment extends TdsFragment {\n\n    private final HeaderOptions headerOptions;\n\n    /**\n     * Creates a new {@link ContextualTdsFragment}.\n     *\n     * @param headerOptions header options.\n     * @param byteBuf       the buffer.\n     */\n    public ContextualTdsFragment(HeaderOptions headerOptions, ByteBuf byteBuf) {\n\n        super(byteBuf);\n        this.headerOptions = Assert.requireNonNull(headerOptions, \"HeaderOptions must not be null\");\n    }\n\n    /**\n     * @return the header options.\n     */\n    public HeaderOptions getHeaderOptions() {\n        return this.headerOptions;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/Decode.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\nimport reactor.util.annotation.Nullable;\n\n/**\n * TDS-specific decode methods. This utility provides decoding methods according to TDS types.\n *\n * @author Mark Paluch\n */\npublic final class Decode {\n\n    private Decode() {\n    }\n\n    /**\n     * Decode a byte. SQL server type {@code BYTE}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code BYTE}.\n     */\n    public static byte asByte(ByteBuf buffer) {\n        return buffer.readByte();\n    }\n\n    /**\n     * Decode an unsigned byte. SQL server type {@code BYTE}\n     *\n     * @param buffer the data buffer.\n     * @return the decoded unsigned {@code BYTE}.\n     */\n    public static int uByte(ByteBuf buffer) {\n        return buffer.readUnsignedByte();\n    }\n\n    /**\n     * Decode a double word. SQL server type {@code DWORD}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code DWORD}.\n     */\n    public static long dword(ByteBuf buffer) {\n        return buffer.readUnsignedIntLE();\n    }\n\n    /**\n     * Decode byte number. SQL server type {@code BIT}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code BIT}.\n     */\n    public static byte bit(ByteBuf buffer) {\n        return asByte(buffer);\n    }\n\n    /**\n     * Decode float number. SQL server type {@code REAL}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code REAL}.\n     */\n    public static float asFloat(ByteBuf buffer) {\n        return Float.intBitsToFloat(buffer.readIntLE());\n    }\n\n    /**\n     * Decode double number. SQL server type {@code FLOAT}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code FLOAT}.\n     */\n    public static double asDouble(ByteBuf buffer) {\n        return Double.longBitsToDouble(buffer.readLongLE());\n    }\n\n    /**\n     * Decode byte number. SQL server type {@code TINYINT}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code TINYINT}.\n     */\n    public static byte tinyInt(ByteBuf buffer) {\n        return asByte(buffer);\n    }\n\n    /**\n     * Decode short number. SQL server type {@code SMALLINT}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code SMALLINT}.\n     */\n    public static short smallInt(ByteBuf buffer) {\n        return buffer.readShortLE();\n    }\n\n    /**\n     * Decode integer number. SQL server type {@code INT}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code INT}.\n     */\n    public static int asInt(ByteBuf buffer) {\n        return buffer.readIntLE();\n    }\n\n    /**\n     * Decode long number. SQL server type {@code BIGINT}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code BIGINT}.\n     */\n    public static long bigint(ByteBuf buffer) {\n        return buffer.readLongLE();\n    }\n\n    /**\n     * Decode long number. SQL server type {@code LONG}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code LONG}.\n     */\n    public static int asLong(ByteBuf buffer) {\n        return buffer.readIntLE();\n    }\n\n    /**\n     * Decode unsigned long number. SQL server type {@code LONGLONG}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code LONGLONG}.\n     */\n    public static long uLongLong(ByteBuf buffer) {\n        return buffer.readLongLE();\n    }\n\n    /**\n     * Decode a unsigned short. SQL server type {@code USHORT}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@code USHORT}.\n     */\n    public static int uShort(ByteBuf buffer) {\n        return buffer.readUnsignedShortLE();\n    }\n\n    /**\n     * Peek onto the next {@link #uShort(ByteBuf)}. This method retains the {@link ByteBuf#readerIndex()} and returns the {@code USHORT} value if it is readable (i.e. if the buffer has at least two\n     * readable bytes). Returns {@code null} if not readable.\n     *\n     * @param buffer the data buffer.\n     * @return the peeked {@code USHORT} value or {@code null}.\n     */\n    @Nullable\n    public static Integer peekUShort(ByteBuf buffer) {\n\n        if (buffer.readableBytes() >= 2) {\n\n            buffer.markReaderIndex();\n            int peek = Decode.uShort(buffer);\n            buffer.resetReaderIndex();\n\n            return peek;\n        }\n\n        return null;\n    }\n\n    /**\n     * Read an integer with big endian encoding. Typically used to evaluate bit masks.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded integer as big endian.\n     */\n    public static int intBigEndian(ByteBuf buffer) {\n        return buffer.readInt();\n    }\n\n    /**\n     * Decode a unicode ({@code VARCHAR}) string from {@link ByteBuf} with {@code unsigned short} length.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link String}.\n     */\n    public static String unicodeUString(ByteBuf buffer) {\n\n        int length = buffer.readUnsignedShortLE() * 2;\n\n        return decodeUnicode(buffer, length);\n    }\n\n    /**\n     * Decode a unicode ({@code VARCHAR}) string from {@link ByteBuf} with {@code byte} length.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link String}.\n     */\n    public static String unicodeBString(ByteBuf buffer) {\n\n        int length = buffer.readByte() * 2;\n\n        return decodeUnicode(buffer, length);\n    }\n\n    private static String decodeUnicode(ByteBuf buffer, int length) {\n\n        String result = buffer.toString(buffer.readerIndex(), length, ServerCharset.UNICODE.charset());\n        buffer.skipBytes(length);\n\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/Encode.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\n\nimport java.math.BigInteger;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.Charset;\n\n/**\n * Encode utilities for TDS. Encoding methods typically accept {@link ByteBuffer} and a value and write the encoded value to the given buffer.\n *\n * @author Mark Paluch\n */\npublic final class Encode {\n\n    public static final int U_SHORT_MAX_VALUE = Math.abs(Short.MIN_VALUE) + Short.MAX_VALUE;\n\n    private Encode() {\n    }\n\n    /**\n     * Encode a byte. SQL server type {@code BYTE}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void asByte(ByteBuf buffer, int value) {\n        buffer.writeByte(value);\n    }\n\n    /**\n     * Encode a byte. SQL server type {@code BYTE}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void asByte(ByteBuf buffer, byte value) {\n        buffer.writeByte(value);\n    }\n\n    /**\n     * Encode a float. SQL server type {@code REAL}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void asFloat(ByteBuf buffer, float value) {\n        buffer.writeIntLE(Float.floatToIntBits(value));\n    }\n\n    /**\n     * Encode a double. SQL server type {@code FLOAT}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void asDouble(ByteBuf buffer, double value) {\n        buffer.writeLongLE(Double.doubleToLongBits(value));\n    }\n\n    /**\n     * Encode a double word. SQL server type {@code DWORD}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void dword(ByteBuf buffer, int value) {\n        buffer.writeIntLE(value);\n    }\n\n    /**\n     * Encode long number. SQL server type {@code LONG}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void asLong(ByteBuf buffer, int value) {\n        buffer.writeIntLE(value);\n    }\n\n    /**\n     * Encode an unscaled {@link BigInteger} value. SQL server type {@code SMALLMONEY}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void smallMoney(ByteBuf buffer, BigInteger value) {\n        buffer.writeIntLE(value.intValue());\n    }\n\n    /**\n     * Encode an unscaled {@link BigInteger} value. SQL server type {@code MONEY}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void money(ByteBuf buffer, BigInteger value) {\n\n        int intBitsHi = (int) (value.longValue() >> 32 & 0xFFFFFFFFL);\n        int intBitsLo = (int) (value.longValue() & 0xFFFFFFFFL);\n\n        buffer.writeIntLE(intBitsHi);\n        buffer.writeIntLE(intBitsLo);\n    }\n\n    /**\n     * Encode a bit. SQL server type {@code BIT}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void bit(ByteBuf buffer, boolean value) {\n        asByte(buffer, (byte) (value ? 1 : 0));\n    }\n\n    /**\n     * Encode byte number. SQL server type {@code TINYINT}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void tinyInt(ByteBuf buffer, byte value) {\n        asByte(buffer, value);\n    }\n\n    /**\n     * Encode short number. SQL server type {@code SMALLINT}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void smallInt(ByteBuf buffer, short value) {\n        buffer.writeShortLE(value);\n    }\n\n    /**\n     * Encode short number. SQL server type {@code SMALLINT}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void smallInt(ByteBuf buffer, int value) {\n        buffer.writeShortLE(value);\n    }\n\n    /**\n     * Encode integer number. SQL server type {@code INT}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void asInt(ByteBuf buffer, int value) {\n        buffer.writeIntLE(value);\n    }\n\n    /**\n     * Encode long number. SQL server type {@code BIGINT}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void bigint(ByteBuf buffer, long value) {\n        buffer.writeLongLE(value);\n    }\n\n    /**\n     * Encode unsigned long number. SQL server type {@code LONGLONG}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void uLongLong(ByteBuf buffer, long value) {\n        buffer.writeLongLE(value);\n    }\n\n    /**\n     * Encode a unsigned short. SQL server type {@code USHORT}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void uShort(ByteBuf buffer, int value) {\n\n        if (value > U_SHORT_MAX_VALUE) {\n            throw new IllegalArgumentException(\"Value \" + value + \" exceeds uShort.MAX_VALUE\");\n        }\n\n        buffer.writeShortLE(value);\n    }\n\n    /**\n     * Encode an integer with big endian encoding. Typically used to write bit masks.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void intBigEndian(ByteBuf buffer, int value) {\n        buffer.writeInt(value);\n    }\n\n    /**\n     * Encode a short big endian.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void shortBE(ByteBuf buffer, short value) {\n        buffer.writeShort(value);\n    }\n\n    /**\n     * Encode a short (ushort) big endian.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void uShortBE(ByteBuf buffer, int value) {\n\n        if (value > U_SHORT_MAX_VALUE) {\n            throw new IllegalArgumentException(\"Value \" + value + \" exceeds uShort.MAX_VALUE\");\n        }\n\n        buffer.writeShort(value);\n    }\n\n    /**\n     * Encode a string. SQL server type {@code VARCHAR}/{@code NVARCHAR}.\n     *\n     * @param buffer  the data buffer.\n     * @param value   the value to encode.\n     * @param charset the charset to use.\n     */\n    public static void uString(ByteBuf buffer, String value, Charset charset) {\n\n        ByteBuf encoded = ByteBufUtil.encodeString(buffer.alloc(), CharBuffer.wrap(value), charset);\n        uShort(buffer, encoded.readableBytes());\n        buffer.writeBytes(encoded);\n        encoded.release();\n    }\n\n    /**\n     * Encode a {@link String} as unicode. SQL server type {@code UNICODESTREAM}.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void unicodeStream(ByteBuf buffer, String value) {\n\n        ByteBuf encoded = ByteBufUtil.encodeString(buffer.alloc(), CharBuffer.wrap(value), ServerCharset.UNICODE.charset());\n\n        buffer.writeBytes(encoded);\n\n        encoded.release();\n    }\n\n    /**\n     * Encode a {@link CharSequence} as RPC string using {@code UNICODE} encoding.\n     *\n     * @param buffer the data buffer.\n     * @param value  the value to encode.\n     */\n    public static void rpcString(ByteBuf buffer, CharSequence value) {\n\n        for (int i = 0; i < value.length(); i++) {\n\n            char ch = value.charAt(i);\n            buffer.writeByte((byte) (ch & 0xFF));\n            buffer.writeByte((byte) ((ch >> 8) & 0xFF));\n        }\n    }\n\n    /**\n     * Encode a {@link CharSequence} as RPC string using {@link Charset} encoding.\n     *\n     * @param buffer  the data buffer.\n     * @param value   the value to encode.\n     * @param charset the encoding to use.\n     */\n    public static void rpcString(ByteBuf buffer, CharSequence value, Charset charset) {\n        buffer.writeCharSequence(value, charset);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/FirstTdsFragment.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\n\n/**\n * First chunk of a TDS message. This message type signals the encoder to associate subsequent messages with the\n * attached {@link HeaderOptions}.\n *\n * @author Mark Paluch\n * @see ContextualTdsFragment\n * @see LastTdsFragment\n */\npublic final class FirstTdsFragment extends ContextualTdsFragment {\n\n    /**\n     * Creates a new {@link FirstTdsFragment}.\n     *\n     * @param headerOptions header options.\n     * @param byteBuf       the buffer.\n     */\n    FirstTdsFragment(HeaderOptions headerOptions, ByteBuf byteBuf) {\n        super(headerOptions, byteBuf);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/LastTdsFragment.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\n\n/**\n * Last chunk of a TDS message. This message type signals the encoder send this fragment with the previously associated\n * {@link HeaderOptions} and clear these after sending this message.\n *\n * @author Mark Paluch\n * @see FirstTdsFragment\n */\npublic final class LastTdsFragment extends TdsFragment {\n\n    /**\n     * Creates a new {@link LastTdsFragment}.\n     *\n     * @param byteBuf       the buffer.\n     * @param headerOptions header options.\n     */\n    LastTdsFragment(ByteBuf byteBuf) {\n        super(byteBuf);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/ProtocolException.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.r2dbc.spi.R2dbcNonTransientResourceException;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Exception indicating unsupported or invalid protocol states. This exception is thrown in cases where e.g. the clients\n * receives an invalid length, unexpected protocol frame or cannot decode a particular protocol frame. If a\n * {@link ProtocolException} is thrown, then the underlying transport connection is closed.\n *\n * @author Mark Paluch\n */\npublic final class ProtocolException extends R2dbcNonTransientResourceException {\n\n    public static final int DRIVER_ERROR_NONE = 0;\n\n    public static final int DRIVER_ERROR_FROM_DATABASE = 2;\n\n    public static final int DRIVER_ERROR_IO_FAILED = 3;\n\n    public static final int DRIVER_ERROR_INVALID_TDS = 4;\n\n    public static final int DRIVER_ERROR_SSL_FAILED = 5;\n\n    public static final int DRIVER_ERROR_UNSUPPORTED_CONFIG = 6;\n\n    public static final int DRIVER_ERROR_INTERMITTENT_TLS_FAILED = 7;\n\n    public static final int ERROR_SOCKET_TIMEOUT = 8;\n\n    public static final int ERROR_QUERY_TIMEOUT = 9;\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason the reason for the error. Set as the exception's message and retrieved with {@link #getMessage()}.\n     */\n    public ProtocolException(@Nullable String reason) {\n        super(reason, null, DRIVER_ERROR_NONE);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason          the reason for the error. Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @param driverErrorCode the driver error code.\n     */\n    public ProtocolException(@Nullable String reason, int driverErrorCode) {\n        super(reason, null, driverErrorCode);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason the reason for the error. Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @param cause  the cause.\n     */\n    public ProtocolException(@Nullable String reason, @Nullable Throwable cause) {\n        super(reason, null, DRIVER_ERROR_NONE, cause);\n    }\n\n    /**\n     * Creates a new exception.\n     *\n     * @param reason          the reason for the error. Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @param cause           the cause.\n     * @param driverErrorCode the driver error code.\n     */\n    public ProtocolException(@Nullable String reason, @Nullable Throwable cause, int driverErrorCode) {\n        super(reason, null, driverErrorCode, cause);\n    }\n\n    /**\n     * Create a new {@link ProtocolException} for invalid TDS.\n     *\n     * @param reason the reason for the error. Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @return the {@link ProtocolException}.\n     */\n    public static ProtocolException invalidTds(String reason) {\n        return new ProtocolException(reason, DRIVER_ERROR_INVALID_TDS);\n    }\n\n    /**\n     * Create a new {@link ProtocolException} for an unsupported configuration.\n     *\n     * @param reason the reason for the error. Set as the exception's message and retrieved with {@link #getMessage()}.\n     * @return the {@link ProtocolException}.\n     */\n    public static ProtocolException unsupported(String reason) {\n        return new ProtocolException(reason, DRIVER_ERROR_UNSUPPORTED_CONFIG);\n    }\n\n    /**\n     * Create a new {@link ProtocolException} for an unsupported configuration.\n     *\n     * @param cause the cause.\n     * @return the {@link ProtocolException}.\n     */\n    public static ProtocolException unsupported(Throwable cause) {\n        return new ProtocolException(null, cause, DRIVER_ERROR_UNSUPPORTED_CONFIG);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/Redirect.java",
    "content": "package io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n * Represents a client redirection to a different server.\n *\n * @author Lars Haatveit\n * @see io.r2dbc.mssql.message.token.EnvChangeToken.EnvChangeType#Routing\n * @since 0.8.2\n */\npublic final class Redirect {\n\n    private static final int PROTOCOL_TCP_IP = 0;\n\n    private final String serverName;\n\n    private final int port;\n\n    /**\n     * Get the alternate server name.\n     *\n     * @return the server name\n     */\n    public String getServerName() {\n        return this.serverName;\n    }\n\n    /**\n     * Get the alternate port.\n     *\n     * @return the port\n     */\n    public int getPort() {\n        return this.port;\n    }\n\n    private Redirect(String serverName, int port) {\n        this.serverName = serverName;\n        this.port = port;\n    }\n\n    /**\n     * Creates a new {@link Redirect}\n     *\n     * @param serverName the server name.\n     * @param port       the TCP port.\n     * @return the {@link Redirect}.\n     */\n    public static Redirect create(String serverName, int port) {\n        return new Redirect(serverName, port);\n    }\n\n    /**\n     * Decode a {@link Redirect} from {@link ByteBuf}.\n     *\n     * @param buffer the data buffer\n     * @return the decoded {@link Redirect}\n     */\n    public static Redirect decode(ByteBuf buffer) {\n\n        int routingDataValueLength = buffer.readUnsignedShortLE();\n\n        if (routingDataValueLength <= 5) {\n            throw new ProtocolException(\"Decoding error, buffer is too short\");\n        }\n\n        int protocol = buffer.readUnsignedByte();\n\n        if (protocol != PROTOCOL_TCP_IP) {\n            throw new ProtocolException(\"Unknown route protocol\");\n        }\n\n        // The ProtocolProperty field represents the remote port when the protocol is TCP/IP.\n        // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/2b3eb7e5-d43d-4d1b-bf4d-76b9e3afc791\n\n        int port = buffer.readUnsignedShortLE();\n        String serverName = Decode.unicodeUString(buffer);\n\n        return Redirect.create(serverName, port);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/ServerCharset.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport java.nio.charset.Charset;\n\n/**\n * Enumeration of encodings that are supported by SQL Server (and hopefully the JVM). See, for example,\n * <a href=\"https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html\">https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html</a>\n * for a complete list of supported encodings with their canonical names.\n */\npublic enum ServerCharset {\n\n    // @formatter:off\n    UNICODE (\"UTF-16LE\", true, false),\n    UTF8    (\"UTF-8\", true, false),\n    CP437   (\"Cp437\", false, false),\n    CP850   (\"Cp850\", false, false),\n    CP874   (\"MS874\", true, true),\n    CP932   (\"MS932\", true, false),\n    CP936   (\"MS936\", true, false),\n    CP949   (\"MS949\", true, false),\n    CP950   (\"MS950\", true, false),\n    CP1250  (\"Cp1250\", true, true),\n    CP1251  (\"Cp1251\", true, true),\n    CP1252  (\"Cp1252\", true, true),\n    CP1253  (\"Cp1253\", true, true),\n    CP1254  (\"Cp1254\", true, true),\n    CP1255  (\"Cp1255\", true, true),\n    CP1256  (\"Cp1256\", true, true),\n    CP1257  (\"Cp1257\", true, true),\n    CP1258  (\"Cp1258\", true, true);\n\n    // @formatter:on\n\n    private final String charsetName;\n\n    private final boolean supportsAsciiConversion;\n\n    private final boolean hasAsciiCompatibleSBCS;\n\n    private boolean jvmSupportConfirmed;\n\n    private Charset charset;\n\n    ServerCharset(String charsetName, boolean supportsAsciiConversion, boolean hasAsciiCompatibleSBCS) {\n\n        this.charsetName = charsetName;\n        this.supportsAsciiConversion = supportsAsciiConversion;\n        this.hasAsciiCompatibleSBCS = hasAsciiCompatibleSBCS;\n    }\n\n    /**\n     * Lazily check codepage ({@link Charset}) support.\n     *\n     * @throws UnsupportedOperationException if the charset is not supported.\n     */\n    private void checkSupported() {\n\n        if (!this.jvmSupportConfirmed) {\n            // Checks for support by converting a java.lang.String\n            // This works for all of the code pages above in SE 5 and later.\n            if (!Charset.isSupported(this.charsetName)) {\n                throw new UnsupportedOperationException(String.format(\"Code page not supported: %s\", this.charsetName));\n            }\n\n            this.jvmSupportConfirmed = true;\n        }\n    }\n\n    /**\n     * Return the {@link Charset} for this encoding.\n     *\n     * @return the charset for this encoding.\n     */\n    public Charset charset() {\n\n        checkSupported();\n\n        if (this.charset == null) {\n            this.charset = Charset.forName(this.charsetName);\n        }\n\n        return this.charset;\n    }\n\n    /**\n     * @return {@code true} if the collation supports conversion to {@code true}.\n     */\n    public boolean supportsAsciiConversion() {\n        return this.supportsAsciiConversion;\n    }\n\n    /**\n     * @return {@code true} if the collation supports conversion to {@literal ascii} AND it uses a single-byte character set.\n     */\n    public boolean hasAsciiCompatibleSBCS() {\n        return this.hasAsciiCompatibleSBCS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/TdsFragment.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Fragment of a TDS message. Can be a partial or complete TDS message. Headers are supplied during encoding.\n *\n * @author Mark Paluch\n */\npublic class TdsFragment {\n\n    private final ByteBuf byteBuf;\n\n    /**\n     * Create a TDS fragment.\n     *\n     * @param buffer the data buffer.\n     */\n    TdsFragment(ByteBuf buffer) {\n        this.byteBuf = Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n    }\n\n    /**\n     * @return the data buffer.\n     */\n    public ByteBuf getByteBuf() {\n        return this.byteBuf;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/TdsPacket.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.PacketIdProvider;\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Self-contained TDS packet containing a {@link Header} and {@link ByteBuf data}. Self-contained TDS packets must\n * consist of a single message that does not exceed the negotiated packet size.\n *\n * @author Mark Paluch\n */\npublic final class TdsPacket extends TdsFragment {\n\n    private final Header header;\n\n    TdsPacket(Header header, ByteBuf buffer) {\n\n        super(buffer);\n\n        this.header = Assert.requireNonNull(header, \"Header must not be null!\");\n\n        int expectedBodySize = header.getLength() - Header.LENGTH;\n        Assert.isTrue(buffer.readableBytes() == expectedBodySize,\n            () -> String.format(\n                \"ByteBuffer body size does not match length field in header. Expected body size [%d], actual size [%d]\",\n                buffer.readableBytes(), expectedBodySize));\n    }\n\n    /**\n     * Encode this packet using by obtaining a buffer from {@link ByteBufAllocator}.\n     *\n     * @param allocator the allocator.\n     * @return the encoded buffer.\n     * @throws IllegalArgumentException when {@link ByteBufAllocator} is {@code null}.\n     */\n    public ByteBuf encode(ByteBufAllocator allocator) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n\n        ByteBuf buffer = allocator.buffer(this.header.getLength());\n\n        this.header.encode(buffer);\n        buffer.writeBytes(getByteBuf());\n\n        getByteBuf().release();\n\n        return buffer;\n    }\n\n    /**\n     * Encode this packet using by obtaining a buffer from {@link ByteBufAllocator} and calculate the packet Id from\n     * {@link PacketIdProvider}.\n     *\n     * @param allocator        the allocator.\n     * @param packetIdProvider the {@link PacketIdProvider}.\n     * @return the encoded buffer.\n     * @throws IllegalArgumentException when {@link ByteBufAllocator} or {@link PacketIdProvider} is {@code null}.\n     */\n    public ByteBuf encode(ByteBufAllocator allocator, PacketIdProvider packetIdProvider) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n        Assert.requireNonNull(packetIdProvider, \"PacketIdProvider must not be null\");\n\n        ByteBuf buffer = allocator.buffer(this.header.getLength());\n\n        this.header.encode(buffer, packetIdProvider);\n        buffer.writeBytes(getByteBuf());\n\n        getByteBuf().release();\n\n        return buffer;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/TdsPackets.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.tds;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\n\n/**\n * Factory class for {@link TdsFragment} instances.\n *\n * @author Mark Paluch\n */\npublic interface TdsPackets {\n\n    /**\n     * Create a contextual TDS fragment.\n     *\n     * @param options the {@link HeaderOptions} to apply for the final {@link Header} creation.\n     * @param buffer  the TDS message.\n     * @return the {@link ContextualTdsFragment}.\n     */\n    static ContextualTdsFragment create(HeaderOptions options, ByteBuf buffer) {\n        return new ContextualTdsFragment(options, buffer);\n    }\n\n    /**\n     * Create a TDS fragment.\n     *\n     * @param buffer the TDS message.\n     * @return the {@link TdsFragment}.\n     */\n    static TdsFragment create(ByteBuf buffer) {\n        return new TdsFragment(buffer);\n    }\n\n    /**\n     * Create a first contextual TDS fragment.\n     *\n     * @param options header options to form an actual {@link Header} during encoding.\n     * @param buffer  the TDS message.\n     * @return the {@link ContextualTdsFragment}.\n     */\n    static FirstTdsFragment first(HeaderOptions options, ByteBuf buffer) {\n        return new FirstTdsFragment(options, buffer);\n    }\n\n    /**\n     * Create a last contextual TDS fragment.\n     *\n     * @param buffer the TDS message.\n     * @return the {@link ContextualTdsFragment}.\n     */\n    static LastTdsFragment last(ByteBuf buffer) {\n        return new LastTdsFragment(buffer);\n    }\n\n    /**\n     * Create a self-contained TDS Packet.\n     *\n     * @param header the TDS header.\n     * @param buffer the TDS message.\n     * @return the {@link TdsPacket}.\n     */\n    static TdsPacket create(Header header, ByteBuf buffer) {\n        return new TdsPacket(header, buffer);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/tds/package-info.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * TDS protocol support, message envelopes and encoding/decoding helpers.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.message.tds;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/AbstractDataToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\n/**\n * Data token. A data token is typed by a single byte and provides a symbolic name.\n *\n * @author Mark Paluch\n */\npublic abstract class AbstractDataToken implements DataToken {\n\n    private final byte type;\n\n    /**\n     * Creates a new {@link AbstractDataToken}.\n     *\n     * @param type token type.\n     */\n    protected AbstractDataToken(byte type) {\n        this.type = type;\n    }\n\n    /**\n     * @return the token type.\n     */\n    public byte getType() {\n        return this.type;\n    }\n\n    /**\n     * @return symbolic name of the {@link AbstractDataToken}.\n     */\n    public abstract String getName();\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/AbstractDoneToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Result;\n\nimport java.util.Objects;\n\n/**\n * Abstract base class for Done token implementation classes.\n *\n * @author Mark Paluch\n */\npublic abstract class AbstractDoneToken extends AbstractDataToken implements Result.UpdateCount {\n\n    /**\n     * Packet length in bytes.\n     */\n    public static final int LENGTH = 13;\n\n    /**\n     * This DONE is the final DONE in the request.\n     */\n    static final int DONE_FINAL = 0x00;\n\n    /**\n     * This DONE message is not the final DONE message in the response. Subsequent data streams to follow.\n     */\n    static final int DONE_MORE = 0x01;\n\n    /**\n     * An error occurred on the current SQL statement. A preceding ERROR token SHOULD be sent when this bit is set.\n     */\n    static final int DONE_ERROR = 0x02;\n\n    /**\n     * A transaction is in progress.\n     */\n    static final int DONE_INXACT = 0x04;\n\n    /**\n     * The DoneRowCount value is valid. This is used to distinguish between\n     * a valid value of 0 for DoneRowCount or just an initialized variable.\n     */\n    static final int DONE_COUNT = 0x10;\n\n    /**\n     * The DONE message is a server acknowledgement of a client ATTENTION message.\n     */\n    static final int DONE_ATTN = 0x20;\n\n    /**\n     * This DONEPROC message is associated with an RPC within a set of batched RPCs. This flag is not set on the last RPC in the RPC batch.\n     */\n    static final int DONE_RPCINBATCH = 0x80;\n\n    /**\n     * Used in place of DONE_ERROR when an error occurred on the current SQL statement, which is severe enough to require the result set, if any, to be discarded.\n     */\n    static final int DONE_SRVERROR = 0x100;\n\n    static final int CACHE_SIZE = 48;\n\n    private final int status;\n\n    /**\n     * The token of the current SQL statement. The token value is provided and controlled by the application layer, which utilizes TDS. The TDS layer does not evaluate the value.\n     */\n    private final int currentCommand;\n\n    /**\n     * The count of rows that were affected by the SQL statement. The value of DoneRowCount is valid if the value of Status includes {@link #DONE_COUNT}.\n     */\n    private final long rowCount;\n\n    /**\n     * Creates a new {@link AbstractDoneToken}.\n     *\n     * @param type           token type.\n     * @param status         status flags, see {@link AbstractDoneToken} constants.\n     * @param currentCommand the current command counter.\n     * @param rowCount       number of columns if {@link #hasCount()} is set.\n     */\n    protected AbstractDoneToken(byte type, int status, int currentCommand, long rowCount) {\n\n        super(type);\n\n        this.status = status;\n        this.currentCommand = currentCommand;\n        this.rowCount = rowCount;\n    }\n\n    /**\n     * Check whether the {@link Message} represents a attention acknowledgement.\n     *\n     * @param message the message to inspect.\n     * @return {@literal true} if the {@link Message} represents a attention acknowledgement.\n     * @since 0.9\n     */\n    public static boolean isAttentionAck(Message message) {\n\n        if (message instanceof AbstractDoneToken) {\n            return ((AbstractDoneToken) message).isAttentionAck();\n        }\n\n        return false;\n    }\n\n    /**\n     * Check whether the {@link Message} represents a finished done token.\n     *\n     * @param message the message to inspect.\n     * @return {@literal true} if the {@link Message} represents a finished done token.\n     */\n    public static boolean isDone(Message message) {\n\n        if (message instanceof AbstractDoneToken) {\n            return ((AbstractDoneToken) message).isDone();\n        }\n\n        return false;\n    }\n\n    /**\n     * Check whether the the {@link Message} has a count.\n     *\n     * @param message the message to inspect.\n     * @return {@literal true} if the {@link Message} has a count.\n     */\n    public static boolean hasCount(Message message) {\n\n        if (message instanceof AbstractDoneToken) {\n            return ((AbstractDoneToken) message).hasCount();\n        }\n\n        return false;\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link AbstractDoneToken}.\n     *\n     * @param buffer the data buffer.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a {@link AbstractDoneToken}.\n     * @throws IllegalArgumentException when {@link ByteBuf} is {@code null}.\n     */\n    public static boolean canDecode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        return buffer.readableBytes() >= LENGTH - 1 /* Decoding always decodes the token type first\n        so no need to check the for the type byte */;\n    }\n\n    /**\n     * Encode this token.\n     *\n     * @param buffer the data buffer.\n     * @throws IllegalArgumentException when {@link ByteBuf} is {@code null}.\n     */\n    public void encode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        buffer.writeByte(getType());\n        Encode.uShort(buffer, getStatus());\n        Encode.uShort(buffer, getCurrentCommand());\n        Encode.uLongLong(buffer, getRowCount());\n    }\n\n    public int getStatus() {\n        return this.status;\n    }\n\n    /**\n     * @return {@code true} if this token indicates the response is acknowledging the attention request.\n     */\n    public boolean isAttentionAck() {\n        return (getStatus() & DONE_ATTN) != 0;\n    }\n\n    /**\n     * @return {@code true} if this token indicates the response is done and has no more rows.\n     */\n    public boolean isDone() {\n        return (getStatus() & DONE_MORE) == 0;\n    }\n\n    /**\n     * @return {@code true} if this token indicates the response is done with a preceding error.\n     */\n    public boolean isError() {\n        return (getStatus() & DONE_ERROR) != 0;\n    }\n\n    /**\n     * @return {@code true} if this token indicates the response is not done yet and the stream contains more data.\n     */\n    public boolean hasMore() {\n        return (getStatus() & DONE_MORE) != 0;\n    }\n\n    /**\n     * @return {@code true} if this token contains a row count and {@link #getRowCount()} has a valid value.\n     */\n    public boolean hasCount() {\n        return (getStatus() & DONE_COUNT) != 0;\n    }\n\n    /**\n     * @return the application-level command counter.\n     */\n    public int getCurrentCommand() {\n        return this.currentCommand;\n    }\n\n    /**\n     * @return the row count. Only valid if {@link #hasCount()} is set.\n     */\n    public long getRowCount() {\n        return this.rowCount;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof AbstractDoneToken)) {\n            return false;\n        }\n        AbstractDoneToken doneToken = (AbstractDoneToken) o;\n        return this.status == doneToken.status &&\n            this.currentCommand == doneToken.currentCommand &&\n            this.rowCount == doneToken.rowCount;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.status, this.currentCommand, this.rowCount);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [done=\").append(isDone());\n        sb.append(\", hasCount=\").append(hasCount());\n        sb.append(\", rowCount=\").append(getRowCount());\n        sb.append(\", hasMore=\").append(hasMore());\n        sb.append(\", attnAck=\").append(isAttentionAck());\n        sb.append(\", currentCommand=\").append(getCurrentCommand());\n        sb.append(']');\n        return sb.toString();\n    }\n\n    @Override\n    public long value() {\n        return hasCount() ? getRowCount() : 0;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/AbstractInfoToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Info token.\n *\n * @author Mark Paluch\n */\npublic abstract class AbstractInfoToken extends AbstractDataToken {\n\n    /*\n     * The total length of the INFO data stream, in bytes.\n     */\n    private final long length;\n\n    /**\n     * Info number.\n     */\n    private final long number;\n\n    /**\n     * The error state, used as a modifier to the info Number.\n     */\n    private final byte state;\n\n    /**\n     * The class (severity) of the error. A class of less than 10 indicates an informational message.\n     */\n    private final byte infoClass;\n\n    /**\n     * Classification constant.\n     */\n    private final Classification classification;\n\n    /**\n     * The message text length and message text using US_VARCHAR format.\n     */\n    private final String message;\n\n    /**\n     * The server name length and server name using B_VARCHAR format.\n     */\n    private final String serverName;\n\n    /**\n     * The stored procedure name length and stored procedure name using B_VARCHAR format.\n     */\n    private final String procName;\n\n    /**\n     * The line number in the SQL batch or stored procedure that caused the error.\n     * <p/>\n     * Line numbers begin at 1; therefore, if the line number is not applicable to the message as determined by the upper\n     * layer, the value of LineNumber will be 0.\n     */\n    private final long lineNumber;\n\n    AbstractInfoToken(byte type, long length, long number, byte state, byte infoClass, String message, String serverName,\n                      String procName, long lineNumber) {\n\n        super(type);\n        this.length = length;\n        this.number = number;\n        this.state = state;\n        this.infoClass = infoClass;\n        this.classification = Classification.valueOf(this.infoClass);\n        this.message = message;\n        this.serverName = serverName;\n        this.procName = procName;\n        this.lineNumber = lineNumber;\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link AbstractInfoToken}.\n     *\n     * @param buffer the data buffer.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a {@link AbstractInfoToken}.\n     */\n    public static boolean canDecode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        Integer requiredLength = Decode.peekUShort(buffer);\n\n        return requiredLength != null && buffer.readableBytes() >= (requiredLength + /* length field */ 2);\n    }\n\n    public long getNumber() {\n        return this.number;\n    }\n\n    public byte getState() {\n        return this.state;\n    }\n\n    public byte getInfoClass() {\n        return this.infoClass;\n    }\n\n    public Classification getClassification() {\n        return this.classification;\n    }\n\n    public String getMessage() {\n        return this.message;\n    }\n\n    public String getServerName() {\n        return this.serverName;\n    }\n\n    public String getProcName() {\n        return this.procName;\n    }\n\n    public long getLineNumber() {\n        return this.lineNumber;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [number=\").append(this.number);\n        sb.append(\", state=\").append(this.state);\n        sb.append(\", infoClass=\").append(this.infoClass);\n        sb.append(\", message='\").append(this.message).append('\\\"');\n        sb.append(\", serverName='\").append(this.serverName).append('\\\"');\n        sb.append(\", procName='\").append(this.procName).append('\\\"');\n        sb.append(\", lineNumber=\").append(this.lineNumber);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    public enum Classification {\n\n        /**\n         * Informational messages that return status information or report errors that are not severe.\n         */\n        INFORMATIONAL(0, 10),\n\n        /**\n         * The given object or entity does not exist.\n         */\n        OBJECT_DOES_NOT_EXIST(11),\n\n        /**\n         * A special severity for SQL statements that do not use locking because of special options. In some cases, read operations performed by these SQL statements could result in inconsistent\n         * data, because locks are not taken to guarantee consistency.\n         */\n        INCONSISTENT_NO_LOCK(12),\n\n        /**\n         * Transaction deadlock errors.\n         */\n        TX_DEADLOCK(13),\n\n        /**\n         * Security-related errors, such as permission denied.\n         */\n        SECURITY(14),\n\n        /**\n         * Syntax errors in the SQL statement.\n         */\n        SYNTAX_ERROR(15),\n\n        /**\n         * General errors that can be corrected by the user.\n         */\n        GENERAL_ERROR(16),\n\n        /**\n         * The SQL statement caused the database server to run out of resources (such as memory, locks, or disk space for the database) or to exceed some limit set by the system administrator.\n         */\n        OUT_OF_RESOURCES(17),\n\n        /**\n         * There is a problem in the Database Engine software, but the SQL statement completes execution, and the connection to the instance of the Database Engine is maintained. System\n         * administrator action is required.\n         */\n        DATABASE_ENGINE_FAILURE(18),\n\n        /**\n         * A non-configurable Database Engine limit has been exceeded and the current SQL batch has been terminated. Error messages with a severity level of 19 or higher stop the execution of the\n         * current SQL batch.\n         */\n        DATABASE_LIMIT(19),\n\n        /**\n         * Indicates that a SQL statement has encountered a problem. Because the problem has affected only the current task, it is unlikely that the database itself has been damaged.\n         */\n        SYSTEM_SQL_PROBLEM(20),\n\n        /**\n         * Indicates that a problem has been encountered that affects all tasks in the current database, but it is unlikely that the database itself has been damaged.\n         */\n        ALL_TASKS_PROBLEM(21),\n\n        /**\n         * Indicates that the table or index specified in the message has been damaged by a software or hardware problem.\n         */\n        INDEX_PROBLEM(22),\n\n        /**\n         * Indicates that the integrity of the entire database is in question because of a hardware or software problem.\n         */\n        DATABASE_INTEGRITY_PROBLEM(23),\n\n        /**\n         * Indicates a media failure. The system administrator might have to restore the database or resolve a hardware issue.\n         */\n        MEDIA_ERROR(24),\n\n        /**\n         * Unknown classification.\n         */\n        UNKNOWN(-1);\n\n        final int from;\n\n        final int to;\n\n        Classification(int code) {\n            this(code, code);\n        }\n\n        Classification(int from, int to) {\n            this.from = from;\n            this.to = to;\n        }\n\n        /**\n         * Lookup classification by its class value.\n         *\n         * @param value the class value.\n         * @return the matching {@link Classification} or {@link Classification#UNKNOWN} if it cannot be resolved.\n         */\n        static Classification valueOf(int value) {\n\n            for (Classification classification : Classification.values()) {\n                if (value >= classification.from && value <= classification.to) {\n                    return classification;\n                }\n            }\n\n            return Classification.UNKNOWN;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/AllHeaders.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\n/**\n * All Headers data structure.\n *\n * @author Mark Paluch\n */\npublic final class AllHeaders {\n\n    private final byte[] transactionDescriptor;\n\n    private final int outstandingRequestCount;\n\n    private final int length;\n\n    private AllHeaders(byte[] transactionDescriptor, int outstandingRequestCount) {\n\n        this.transactionDescriptor = transactionDescriptor;\n        this.outstandingRequestCount = outstandingRequestCount;\n\n        int totalLength = 4; // DWORD\n\n        totalLength += transactionDescriptor.length + /* outstanding request count */ 4 + 4 /* Length field DWORD */ + 2 /* type */;\n\n        this.length = totalLength;\n    }\n\n    /**\n     * Creates {@link AllHeaders} containing only a {@link TransactionDescriptor transactional} descriptor.\n     *\n     * @param transactionDescriptor the binary transaction descriptor.\n     * @param outstandingRequests   number of outstanding requests\n     * @return the {@link AllHeaders} for {@link TransactionDescriptor} and {@literal outstandingRequests}.\n     * @throws IllegalArgumentException when {@link TransactionDescriptor} is {@code null}.\n     */\n    public static AllHeaders transactional(TransactionDescriptor transactionDescriptor, int outstandingRequests) {\n\n        Assert.requireNonNull(transactionDescriptor, \"Transaction descriptor must not be null\");\n\n        return transactional(transactionDescriptor.toBytes(), outstandingRequests);\n    }\n\n    /**\n     * Creates {@link AllHeaders} containing only a {@link TransactionDescriptor transactional} descriptor.\n     *\n     * @param transactionDescriptor the binary transaction descriptor.\n     * @param outstandingRequests   number of outstanding requests\n     * @return the {@link AllHeaders} for {@literal transactionDescriptor} and {@literal outstandingRequests}.\n     * @throws IllegalArgumentException when {@link TransactionDescriptor} is {@code null}.\n     */\n    public static AllHeaders transactional(byte[] transactionDescriptor, int outstandingRequests) {\n\n        Assert.requireNonNull(transactionDescriptor, \"Transaction descriptor must not be null\");\n\n        return new AllHeaders(transactionDescriptor, outstandingRequests);\n    }\n\n    /**\n     * Encode the header.\n     *\n     * @param buffer the data buffer.\n     */\n    public void encode(ByteBuf buffer) {\n\n        Encode.dword(buffer, this.length);\n        Encode.dword(buffer, this.transactionDescriptor.length + /* outstanding request count */ 4 + 6);\n        Encode.uShort(buffer, 0x02);\n        buffer.writeBytes(this.transactionDescriptor);\n        Encode.dword(buffer, this.outstandingRequestCount);\n    }\n\n    public int getLength() {\n        return this.length;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof AllHeaders)) {\n            return false;\n        }\n        AllHeaders that = (AllHeaders) o;\n        return this.outstandingRequestCount == that.outstandingRequestCount &&\n            this.length == that.length &&\n            Arrays.equals(this.transactionDescriptor, that.transactionDescriptor);\n    }\n\n    @Override\n    public int hashCode() {\n        int result = Objects.hash(this.outstandingRequestCount, this.length);\n        result = 31 * result + Arrays.hashCode(this.transactionDescriptor);\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/Attention.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsPackets;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.ReferenceCountUtil;\n\nimport java.util.Objects;\n\n/**\n * Attention signal to cancel a running operation.\n *\n * @author Mark Paluch\n * @author Tomasz Marciniak\n * @since 0.9\n */\npublic final class Attention implements ClientMessage, TokenStream {\n\n    private static final HeaderOptions header = HeaderOptions.create(Type.ATTENTION, Status.empty());\n\n    private final AllHeaders allHeaders;\n\n    /**\n     * Creates a new {@link Attention} token.\n     *\n     * @param outstandingRequests   the number of outstanding requests.\n     * @param transactionDescriptor the transaction descriptor (8 byte).\n     */\n    private Attention(int outstandingRequests, byte[] transactionDescriptor) {\n\n        Assert.requireNonNull(transactionDescriptor, \"Transaction descriptor must not be null\");\n\n        this.allHeaders = AllHeaders.transactional(transactionDescriptor, outstandingRequests);\n    }\n\n    /**\n     * Creates a new {@link Attention}.\n     *\n     * @param outstandingRequests   the number of outstanding requests.\n     * @param transactionDescriptor the transaction descriptor\n     * @return the {@link Attention} token.\n     */\n    public static Attention create(int outstandingRequests, TransactionDescriptor transactionDescriptor) {\n\n        Assert.requireNonNull(transactionDescriptor, \"Transaction descriptor must not be null\");\n\n        return new Attention(outstandingRequests, transactionDescriptor.toBytes());\n    }\n\n    @Override\n    public TdsFragment encode(ByteBufAllocator allocator, int packetSize) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n\n        int length = this.allHeaders.getLength();\n\n        ByteBuf buffer = allocator.buffer(length);\n        encode(buffer);\n\n        ReferenceCountUtil.maybeRelease(buffer);\n\n        return TdsPackets.create(header, Unpooled.EMPTY_BUFFER);\n    }\n\n    void encode(ByteBuf buffer) {\n        this.allHeaders.encode(buffer);\n    }\n\n    @Override\n    public String getName() {\n        return \"ATTN\";\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof Attention)) {\n            return false;\n        }\n        Attention attn = (Attention) o;\n        return Objects.equals(this.allHeaders, attn.allHeaders);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.allHeaders);\n    }\n\n    @Override\n    public String toString() {\n        return getName();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/ColInfoToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Column info token. Describes the column information in browse mode.\n *\n * @author Mark Paluch\n */\npublic class ColInfoToken extends AbstractDataToken {\n\n    public static final byte TYPE = (byte) 0xA5;\n\n    public static final int STATUS_EXPRESSION = 0x04;\n\n    public static final int STATUS_KEY = 0x08;\n\n    public static final int STATUS_HIDDEN = 0x10;\n\n    public static final int STATUS_DIFFERENT_NAME = 0x20;\n\n    private static final ColInfoToken EMPTY = new ColInfoToken(Collections.emptyList());\n\n    private final List<ColInfo> columns;\n\n    private ColInfoToken(List<ColInfo> columns) {\n        super(TYPE);\n\n        this.columns = columns;\n    }\n\n    /**\n     * Decode a {@link ColInfoToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the {@link ColInfoToken}.\n     */\n    public static ColInfoToken skip(ByteBuf buffer) {\n\n        int length = Decode.uShort(buffer);\n        buffer.skipBytes(length);\n\n        return EMPTY;\n    }\n\n    /**\n     * Decode a {@link ColInfoToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the {@link ColInfoToken}.\n     */\n    public static ColInfoToken decode(ByteBuf buffer) {\n\n        int length = Decode.uShort(buffer);\n\n        int readerIndex = buffer.readerIndex();\n\n        List<ColInfo> columns = new ArrayList<>();\n\n        while (buffer.readerIndex() - readerIndex < length) {\n\n            byte column = Decode.asByte(buffer);\n            byte table = Decode.asByte(buffer);\n            byte status = Decode.asByte(buffer);\n            String columnName = (status & STATUS_DIFFERENT_NAME) != 0 ? Decode.unicodeBString(buffer) : null;\n\n            columns.add(new ColInfo(column, table, status, columnName));\n        }\n\n        return new ColInfoToken(columns);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link ColInfoToken}.\n     *\n     * @param buffer the data buffer.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a {@link ColInfoToken}.\n     */\n    public static boolean canDecode(ByteBuf buffer) {\n\n        if (buffer.readableBytes() >= 5) {\n\n            Integer requiredLength = Decode.peekUShort(buffer);\n            return requiredLength != null && buffer.readableBytes() >= (requiredLength + /* length field */ 2);\n        }\n\n        return false;\n    }\n\n    public List<ColInfo> getColumns() {\n        return this.columns;\n    }\n\n    @Override\n    public String getName() {\n        return \"COLINFO\";\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [columns=\").append(this.columns);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    /**\n     * Column info for a column returned by a sp_cursor operation.\n     */\n    static final class ColInfo {\n\n        /**\n         * The column number in the result set.\n         */\n        private final byte column;\n\n        /**\n         * The number of the base table that the column was derived from. The value is 0 if the value of Status is EXPRESSION.\n         */\n        private final byte table;\n\n        /**\n         * 0x4: EXPRESSION (the column was the result of an expression). 0x8: KEY (the column is part of a key for the associated table).\n         * 0x10: HIDDEN (the column was not requested, but was added because it was part of a key for the associated table).\n         * 0x20: DIFFERENT_NAME (the column name is different than the requested column name in the case of a column alias).\n         */\n        private final byte status;\n\n        /**\n         * The base column name. This only occurs if DIFFERENT_NAME is set in Status.\n         */\n        @Nullable\n        private final String name;\n\n        private ColInfo(byte column, byte table, byte status, @Nullable String name) {\n            this.column = column;\n            this.table = table;\n            this.status = status;\n            this.name = name;\n        }\n\n        public byte getColumn() {\n            return this.column;\n        }\n\n        public byte getTable() {\n            return this.table;\n        }\n\n        public byte getStatus() {\n            return this.status;\n        }\n\n        @Nullable\n        public String getName() {\n            return this.name;\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [column=\").append(this.column);\n            sb.append(\", table=\").append(this.table);\n            sb.append(\", status=\").append(this.status);\n            sb.append(\", columnName=\\\"\").append(this.name).append('\\\"');\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/Column.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.r2dbc.mssql.codec.Decodable;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.Objects;\n\n/**\n * A {@link Decodable} column within a result set.\n *\n * @author Mark Paluch\n */\npublic class Column implements Decodable {\n\n    private final int index;\n\n    private final String name;\n\n    private final TypeInformation type;\n\n    @Nullable\n    private final Identifier table;\n\n    /**\n     * Creates a new {@link Column}.\n     *\n     * @param index the index (ordinal position) within the result set, zero-based.\n     * @param name  the column name.\n     * @param type  the associated type of this column.\n     */\n    public Column(int index, String name, TypeInformation type) {\n        this(index, name, type, null);\n    }\n\n    /**\n     * Creates a new {@link Column}.\n     *\n     * @param index the index (ordinal position) within the result set, zero-based.\n     * @param name  the column name.\n     * @param type  the associated type of this column.\n     * @param table the optional {@link Identifier table name}.\n     */\n    public Column(int index, String name, TypeInformation type, @Nullable Identifier table) {\n\n        this.index = index;\n        this.name = Assert.requireNonNull(name, \"Column name must not be null\");\n        this.type = Assert.requireNonNull(type, \"Type information must not be null\");\n        this.table = table;\n    }\n\n    /**\n     * Returns the column index.\n     *\n     * @return the column index.\n     */\n    public int getIndex() {\n        return this.index;\n    }\n\n    /**\n     * Returns the column name.\n     *\n     * @return the column name.\n     */\n    @Override\n    public String getName() {\n        return this.name;\n    }\n\n    /**\n     * Returns the column {@link TypeInformation type}.\n     *\n     * @return the column {@link TypeInformation type}.\n     */\n    @Override\n    public TypeInformation getType() {\n        return this.type;\n    }\n\n    /**\n     * Returns the {@link Identifier table} name.\n     *\n     * @return the {@link Identifier table} name, can be {@code null}.\n     */\n    @Nullable\n    public Identifier getTable() {\n        return this.table;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [name='\").append(this.name).append('\\\"');\n        sb.append(\", type=\").append(this.type);\n        sb.append(\", table=\").append(this.table);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof Column)) {\n            return false;\n        }\n        Column column = (Column) o;\n        return this.index == column.index &&\n            Objects.equals(this.name, column.name) &&\n            Objects.equals(this.table, column.table);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.index, this.name, this.table);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/ColumnMetadataToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Column metadata token.\n *\n * @author Mark Paluch\n */\npublic final class ColumnMetadataToken extends AbstractDataToken {\n\n    public static final byte TYPE = (byte) 0x81;\n\n    public static final int NO_COLUMNS = 0xFFFF;\n\n    private static final byte TYPE_SQLDATACLASSIFICATION = (byte) 0xa3;\n\n    private static final ColumnMetadataToken EMPTY = new ColumnMetadataToken(new Column[0]);\n\n    private final Column[] columns;\n\n    private final Map<String, Column> namedColumns;\n\n    /**\n     * Creates a new {@link ColumnMetadataToken}.\n     *\n     * @param columns the columns.\n     */\n    private ColumnMetadataToken(Column[] columns) {\n\n        super(TYPE);\n\n        this.columns = columns;\n\n        if (columns.length == 1) {\n            this.namedColumns = Collections.singletonMap(columns[0].getName(), columns[0]);\n        } else {\n\n            Map<String, Column> byName = new HashMap<>(this.columns.length, 1);\n\n            for (Column column : columns) {\n                Column old = byName.put(column.getName(), column);\n                if (old != null) {\n                    byName.put(column.getName(), old);\n                }\n            }\n\n            this.namedColumns = byName;\n        }\n    }\n\n    /**\n     * Creates a new {@link ColumnMetadataToken} given {@link List} of {@link Column}s.\n     *\n     * @param columns the columns.\n     * @return the {@link ColumnMetadataToken}.\n     */\n    public static ColumnMetadataToken create(Column[] columns) {\n        return new ColumnMetadataToken(columns);\n    }\n\n    /**\n     * Decode the {@link ColumnMetadataToken} response from a {@link ByteBuf}.\n     *\n     * @param buffer              must not be null.\n     * @param encryptionSupported whether encryption is supported.\n     * @return the decoded {@link ColumnMetadataToken}.\n     */\n    public static ColumnMetadataToken decode(ByteBuf buffer, boolean encryptionSupported) {\n\n        int columnCount = Decode.uShort(buffer);\n\n        // Handle the magic NoMetaData value\n        if (columnCount == NO_COLUMNS) {\n            return EMPTY;\n        }\n\n        if (encryptionSupported) {\n\n            int tableSize = Decode.uShort(buffer);\n\n            if (tableSize != 0) {\n                throw new UnsupportedOperationException(\"Driver does not support encryption\");\n            }\n        }\n\n        Column[] columns = new Column[columnCount];\n\n        for (int i = 0; i < columnCount; i++) {\n\n            columns[i] = decodeColumn(buffer, encryptionSupported, i);\n            decodeDataClassification(buffer);\n        }\n\n        return new ColumnMetadataToken(columns);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link ColumnMetadataToken}.\n     *\n     * @param buffer              the data buffer.\n     * @param encryptionSupported whether encryption is supported.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a {@link ColumnMetadataToken}.\n     */\n    public static boolean canDecode(ByteBuf buffer, boolean encryptionSupported) {\n\n        if (buffer.readableBytes() < 2) {\n            return false;\n        }\n\n        int readerIndex = buffer.readerIndex();\n\n        try {\n            int columnCount = Decode.uShort(buffer);\n\n            // Handle the magic NoMetaData value\n            if (columnCount == NO_COLUMNS) {\n                return true;\n            }\n\n            if (encryptionSupported) {\n\n                if (buffer.readableBytes() < 2) {\n                    return false;\n                }\n                buffer.skipBytes(2);\n            }\n\n            for (int i = 0; i < columnCount; i++) {\n\n                if (!TypeInformation.canDecode(buffer, true)) {\n                    return false;\n                }\n\n                if (!canDecodeColumn(buffer, encryptionSupported)) {\n                    return false;\n                }\n            }\n        } finally {\n            buffer.readerIndex(readerIndex);\n        }\n\n        return true;\n    }\n\n    private static boolean canDecodeColumn(ByteBuf buffer, boolean encryptionSupported) {\n\n        TypeInformation typeInfo = TypeInformation.decode(buffer, true);\n\n        if (typeInfo.getServerType() == SqlServerType.TEXT || typeInfo.getServerType() == SqlServerType.NTEXT\n            || typeInfo.getServerType() == SqlServerType.IMAGE) {\n            // Yukon and later, table names are returned as multi-part SQL identifiers.\n            if (!Identifier.canDecodeAndSkipBytes(buffer)) {\n                return false;\n            }\n        }\n\n        if (encryptionSupported && typeInfo.isEncrypted()) {\n            throw new UnsupportedOperationException(\"Driver does not support encryption\");\n        }\n\n        if (!buffer.isReadable()) {\n            return false;\n        }\n\n        int length = buffer.readByte() * 2;\n\n        if (length > buffer.readableBytes()) {\n            return false;\n        }\n\n        buffer.skipBytes(length);\n        decodeDataClassification(buffer);\n\n        return true;\n    }\n\n    private static void decodeDataClassification(ByteBuf buffer) {\n\n        if (buffer.readableBytes() > 1) {\n\n            buffer.markReaderIndex();\n            byte nextToken = Decode.asByte(buffer);\n            buffer.resetReaderIndex();\n\n            if (nextToken == TYPE_SQLDATACLASSIFICATION) {\n                throw new UnsupportedOperationException(\"Driver does not support SQL Data Classification\");\n            }\n        }\n    }\n\n    private static Column decodeColumn(ByteBuf buffer, boolean encryptionSupported, int columnIndex) {\n\n        TypeInformation typeInfo = TypeInformation.decode(buffer, true);\n        Identifier tableName = null;\n\n        if (typeInfo.getServerType() == SqlServerType.TEXT || typeInfo.getServerType() == SqlServerType.NTEXT\n            || typeInfo.getServerType() == SqlServerType.IMAGE) {\n            // Yukon and later, table names are returned as multi-part SQL identifiers.\n            tableName = Identifier.decode(buffer);\n        }\n\n        if (encryptionSupported && typeInfo.isEncrypted()) {\n            throw new UnsupportedOperationException(\"Driver does not support encryption\");\n        }\n\n        String name = Decode.unicodeBString(buffer);\n\n        return new Column(columnIndex, name, typeInfo, tableName);\n    }\n\n    public Column[] getColumns() {\n        return this.columns;\n    }\n\n    public boolean hasColumns() {\n        return this.columns.length != 0;\n    }\n\n    @Override\n    public String getName() {\n        return \"COLMETADATA\";\n    }\n\n    public Map<String, Column> toMap() {\n        return this.namedColumns;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [columns=\").append(Arrays.toString(this.columns));\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/DataToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.r2dbc.mssql.message.Message;\n\n/**\n * Data token.\n *\n * @author Mark Paluch\n */\npublic interface DataToken extends Message {\n\n    /**\n     * @return the token type.\n     */\n    byte getType();\n\n    /**\n     * @return symbolic name of the {@link DataToken}.\n     */\n    String getName();\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/DoneInProcToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.tds.Decode;\n\n/**\n * Done in Proc token.\n *\n * @author Mark Paluch\n */\npublic final class DoneInProcToken extends AbstractDoneToken {\n\n    public static final byte TYPE = (byte) 0xFF;\n\n    private static final DoneInProcToken[] INTERMEDIATE = new DoneInProcToken[CACHE_SIZE];\n\n    private static final DoneInProcToken[] MORE_WITH_COUNT_CACHE = new DoneInProcToken[CACHE_SIZE];\n\n    private static final DoneInProcToken[] DONE_WITH_COUNT_CACHE = new DoneInProcToken[CACHE_SIZE];\n\n    private static final DoneInProcToken[] MORE_CACHE = new DoneInProcToken[CACHE_SIZE];\n\n    private static final int DONE_WITH_COUNT = DONE_FINAL | DONE_COUNT;\n\n    private static final int MORE_WITH_COUNT = DONE_MORE | DONE_COUNT;\n\n    private static final int MORE = DONE_MORE;\n\n    static {\n        for (int i = 0; i < INTERMEDIATE.length; i++) {\n            INTERMEDIATE[i] = new DoneInProcToken(0, 0, i);\n            DONE_WITH_COUNT_CACHE[i] = new DoneInProcToken(DONE_WITH_COUNT, 0, i);\n            MORE_WITH_COUNT_CACHE[i] = new DoneInProcToken(MORE_WITH_COUNT, 0, i);\n            MORE_CACHE[i] = new DoneInProcToken(MORE, 0, i);\n        }\n    }\n\n    /**\n     * Creates a new {@link DoneInProcToken}.\n     *\n     * @param status         status flags, see {@link AbstractDoneToken} constants.\n     * @param currentCommand the current command counter.\n     * @param rowCount       number of columns if {@link #hasCount()} is set.\n     */\n    private DoneInProcToken(int status, int currentCommand, long rowCount) {\n        super(TYPE, status, currentCommand, rowCount);\n    }\n\n    /**\n     * Creates a new {@link DoneInProcToken} indicating a final packet and {@code rowCount}.\n     *\n     * @param rowCount the row count.\n     * @return the {@link DoneInProcToken}.\n     */\n    public static DoneInProcToken create(long rowCount) {\n        return create0(DONE_WITH_COUNT, 0, rowCount);\n    }\n\n    /**\n     * Check whether the the {@link Message} represents a finished {@link DoneInProcToken}.\n     *\n     * @param message the message to inspect.\n     * @return {@literal true} if the {@link Message} represents a finished {@link DoneInProcToken}.\n     */\n    public static boolean isDone(Message message) {\n\n        if (message instanceof DoneInProcToken) {\n            return ((DoneInProcToken) message).isDone();\n        }\n\n        return false;\n    }\n\n    /**\n     * Decode the {@link DoneInProcToken} response from a {@link ByteBuf}.\n     *\n     * @param buffer must not be null.\n     * @return the decoded {@link DoneInProcToken}.\n     */\n    public static DoneInProcToken decode(ByteBuf buffer) {\n\n        int status = Decode.uShort(buffer);\n        int currentCommand = Decode.uShort(buffer);\n        long rowCount = Decode.uLongLong(buffer);\n\n        return create0(status, currentCommand, rowCount);\n    }\n\n    private static DoneInProcToken create0(int status, int currentCommand, long rowCount) {\n\n        if (rowCount >= 0 && rowCount < CACHE_SIZE) {\n\n            switch (status) {\n                case 0:\n                    return INTERMEDIATE[(int) rowCount];\n                case DONE_WITH_COUNT:\n                    return DONE_WITH_COUNT_CACHE[(int) rowCount];\n                case MORE_WITH_COUNT:\n                    return MORE_WITH_COUNT_CACHE[(int) rowCount];\n                case MORE:\n                    return MORE_CACHE[(int) rowCount];\n            }\n        }\n\n        return new DoneInProcToken(status, currentCommand, rowCount);\n    }\n\n    @Override\n    public String getName() {\n        return \"DONEINPROC\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/DoneProcToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\n\n/**\n * Done Proc token.\n *\n * @author Mark Paluch\n */\npublic final class DoneProcToken extends AbstractDoneToken {\n\n    public static final byte TYPE = (byte) 0xFE;\n\n    private static final DoneProcToken[] INTERMEDIATE = new DoneProcToken[CACHE_SIZE];\n\n    private static final DoneProcToken[] MORE_WITH_COUNT_CACHE = new DoneProcToken[CACHE_SIZE];\n\n    private static final DoneProcToken[] DONE_WITH_COUNT_CACHE = new DoneProcToken[CACHE_SIZE];\n\n    private static final DoneProcToken[] MORE_CACHE = new DoneProcToken[CACHE_SIZE];\n\n    private static final int DONE_WITH_COUNT = DONE_FINAL | DONE_COUNT;\n\n    private static final int MORE_WITH_COUNT = DONE_MORE | DONE_COUNT;\n\n    private static final int MORE = DONE_MORE;\n\n    static {\n        for (int i = 0; i < INTERMEDIATE.length; i++) {\n            INTERMEDIATE[i] = new DoneProcToken(0, 0, i);\n            DONE_WITH_COUNT_CACHE[i] = new DoneProcToken(DONE_WITH_COUNT, 0, i);\n            MORE_WITH_COUNT_CACHE[i] = new DoneProcToken(MORE_WITH_COUNT, 0, i);\n            MORE_CACHE[i] = new DoneProcToken(MORE, 0, i);\n        }\n    }\n\n    /**\n     * Creates a new {@link DoneProcToken}.\n     *\n     * @param status         status flags, see {@link AbstractDoneToken} constants.\n     * @param currentCommand the current command counter.\n     * @param rowCount       number of columns if {@link #hasCount()} is set.\n     */\n    private DoneProcToken(int status, int currentCommand, long rowCount) {\n        super(TYPE, status, currentCommand, rowCount);\n    }\n\n    /**\n     * Creates a new {@link DoneProcToken} indicating a final packet and {@code rowCount}.\n     *\n     * @param rowCount the row count.\n     * @return the {@link DoneProcToken}.\n     */\n    public static DoneProcToken create(long rowCount) {\n        return create0(DONE_FINAL | DONE_COUNT, 0, rowCount);\n    }\n\n    /**\n     * Decode the {@link DoneProcToken} response from a {@link ByteBuf}.\n     *\n     * @param buffer must not be null.\n     * @return the decoded {@link DoneProcToken}.\n     */\n    public static DoneProcToken decode(ByteBuf buffer) {\n\n        int status = Decode.uShort(buffer);\n        int currentCommand = Decode.uShort(buffer);\n        long rowCount = Decode.uLongLong(buffer);\n\n        return create0(status, currentCommand, rowCount);\n    }\n\n    private static DoneProcToken create0(int status, int currentCommand, long rowCount) {\n\n        if (rowCount >= 0 && rowCount < CACHE_SIZE) {\n\n            switch (status) {\n                case 0:\n                    return INTERMEDIATE[(int) rowCount];\n                case DONE_WITH_COUNT:\n                    return DONE_WITH_COUNT_CACHE[(int) rowCount];\n                case MORE_WITH_COUNT:\n                    return MORE_WITH_COUNT_CACHE[(int) rowCount];\n                case MORE:\n                    return MORE_CACHE[(int) rowCount];\n            }\n        }\n\n        return new DoneProcToken(status, currentCommand, rowCount);\n    }\n\n    @Override\n    public String getName() {\n        return \"DONEPROC\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/DoneToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.tds.Decode;\n\n/**\n * Done token.\n *\n * @author Mark Paluch\n */\npublic final class DoneToken extends AbstractDoneToken {\n\n    public static final byte TYPE = (byte) 0xFD;\n\n    private static final DoneToken[] INTERMEDIATE = new DoneToken[CACHE_SIZE];\n\n    private static final DoneToken[] MORE_WITH_COUNT_CACHE = new DoneToken[CACHE_SIZE];\n\n    private static final DoneToken[] DONE_WITH_COUNT_CACHE = new DoneToken[CACHE_SIZE];\n\n    private static final DoneToken[] MORE_CACHE = new DoneToken[CACHE_SIZE];\n\n    private static final int DONE_WITH_COUNT = DONE_FINAL | DONE_COUNT;\n\n    private static final int MORE_WITH_COUNT = DONE_MORE | DONE_COUNT;\n\n    private static final int MORE = DONE_MORE;\n\n    static {\n        for (int i = 0; i < INTERMEDIATE.length; i++) {\n            INTERMEDIATE[i] = new DoneToken(0, 0, i);\n            DONE_WITH_COUNT_CACHE[i] = new DoneToken(DONE_WITH_COUNT, 0, i);\n            MORE_WITH_COUNT_CACHE[i] = new DoneToken(MORE_WITH_COUNT, 0, i);\n            MORE_CACHE[i] = new DoneToken(MORE, 0, i);\n        }\n    }\n\n    /**\n     * Creates a new {@link DoneToken}.\n     *\n     * @param status         status flags, see {@link AbstractDoneToken} constants.\n     * @param currentCommand the current command counter.\n     * @param rowCount       number of columns if {@link #hasCount()} is set.\n     */\n    private DoneToken(int status, int currentCommand, long rowCount) {\n        super(TYPE, status, currentCommand, rowCount);\n    }\n\n    /**\n     * Creates a new {@link DoneToken} indicating a final packet and {@code rowCount}.\n     *\n     * @param rowCount the row count.\n     * @return the {@link DoneToken}.\n     * @see #isDone()\n     * @see #getRowCount()\n     * @see #hasCount()\n     */\n    public static DoneToken create(long rowCount) {\n        return create0(DONE_WITH_COUNT, 0, rowCount);\n    }\n\n    /**\n     * Creates a new {@link DoneToken} with just a {@code rowCount}.\n     *\n     * @param rowCount the row count.\n     * @return the {@link DoneToken}.\n     * @see #getRowCount()\n     * @see #hasCount()\n     */\n    public static DoneToken count(long rowCount) {\n        return create0(DONE_COUNT, 0, rowCount);\n    }\n\n    /**\n     * Creates a new {@link DoneToken} with just a {@code rowCount}.\n     *\n     * @param rowCount the row count.\n     * @return the {@link DoneToken}.\n     * @see #getRowCount()\n     * @see #hasCount()\n     */\n    public static DoneToken more(long rowCount) {\n        return create0(DONE_MORE | DONE_COUNT, 0, rowCount);\n    }\n\n    /**\n     * Check whether the the {@link Message} represents a finished {@link DoneToken}.\n     *\n     * @param message the message to inspect.\n     * @return {@literal true} if the {@link Message} represents a finished {@link DoneToken}.\n     */\n    public static boolean isDone(Message message) {\n\n        if (message instanceof DoneToken) {\n            return ((AbstractDoneToken) message).isDone();\n        }\n\n        return false;\n    }\n\n    /**\n     * Decode the {@link DoneToken} response from a {@link ByteBuf}.\n     *\n     * @param buffer must not be null.\n     * @return the decoded {@link DoneToken}.\n     */\n    public static DoneToken decode(ByteBuf buffer) {\n\n        int status = Decode.uShort(buffer);\n        int currentCommand = Decode.uShort(buffer);\n        long rowCount = Decode.uLongLong(buffer);\n\n        return create0(status, currentCommand, rowCount);\n    }\n\n    private static DoneToken create0(int status, int currentCommand, long rowCount) {\n\n        if (rowCount >= 0 && rowCount < CACHE_SIZE) {\n\n            switch (status) {\n                case 0:\n                    return INTERMEDIATE[(int) rowCount];\n                case DONE_WITH_COUNT:\n                    return DONE_WITH_COUNT_CACHE[(int) rowCount];\n                case MORE_WITH_COUNT:\n                    return MORE_WITH_COUNT_CACHE[(int) rowCount];\n                case MORE:\n                    return MORE_CACHE[(int) rowCount];\n            }\n        }\n\n        return new DoneToken(status, currentCommand, rowCount);\n    }\n\n    @Override\n    public String getName() {\n        return \"DONE\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/EnvChangeToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\n/**\n * A notification of an environment change (for example, database, language, and so on).\n *\n * @author Mark Paluch\n * @author Lars Haatveit\n * @see EnvChangeType\n */\npublic final class EnvChangeToken extends AbstractDataToken {\n\n    public static final byte TYPE = (byte) 0xE3;\n\n    /**\n     * The total length of the ENVCHANGE data stream (EnvValueData).\n     */\n    private final int length;\n\n    /**\n     * The type of environment change.\n     */\n    private final EnvChangeType changeType;\n\n    private final byte[] newValue;\n\n    private final byte[] oldValue;\n\n    public EnvChangeToken(int length, EnvChangeType changeType, byte[] newValue, @Nullable byte[] oldValue) {\n\n        super(TYPE);\n\n        Assert.requireNonNull(changeType, \"EnvChangeType must not be null\");\n        Assert.requireNonNull(newValue, \"New value must not be null\");\n\n        this.length = length;\n        this.changeType = changeType;\n        this.newValue = newValue;\n        this.oldValue = oldValue;\n    }\n\n    /**\n     * Decode a {@link EnvChangeToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link EnvChangeToken}\n     */\n    public static EnvChangeToken decode(ByteBuf buffer) {\n\n        int length = Decode.uShort(buffer);\n        byte type = Decode.asByte(buffer);\n\n        EnvChangeType envChangeType = EnvChangeType.valueOf(type);\n\n        byte[] newValue;\n        byte[] oldValue;\n\n        // The routing message contains structured data, while the other environment change tokens contain old/new value pairs prefixed with data length.\n\n        if (envChangeType == EnvChangeType.Routing) {\n\n            newValue = new byte[length - 1];\n            buffer.readBytes(newValue);\n\n            oldValue = null;\n\n        } else {\n\n            int newValueLen = envChangeType.toByteLength(Decode.asByte(buffer));\n\n            newValue = new byte[newValueLen];\n            buffer.readBytes(newValue);\n\n            int oldValueLen = envChangeType.toByteLength(Decode.asByte(buffer));\n            oldValue = new byte[oldValueLen];\n            buffer.readBytes(oldValue);\n        }\n\n        return new EnvChangeToken(length, envChangeType, newValue, oldValue);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link EnvChangeType}.\n     *\n     * @param buffer the data buffer.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a {@link EnvChangeType}.\n     */\n    public static boolean canDecode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        Integer requiredLength = Decode.peekUShort(buffer);\n\n        return requiredLength != null && buffer.readableBytes() >= (requiredLength + /* length field */ 2);\n    }\n\n    @Override\n    public String getName() {\n        return \"ENVCHANGE_TOKEN\";\n    }\n\n    public int getLength() {\n        return this.length;\n    }\n\n    public EnvChangeType getChangeType() {\n        return this.changeType;\n    }\n\n    public byte[] getNewValue() {\n        return this.newValue;\n    }\n\n    @Nullable\n    public byte[] getOldValue() {\n        return this.oldValue;\n    }\n\n    public String getOldValueString() {\n        return new String(this.oldValue, 0, this.oldValue.length, ServerCharset.UNICODE.charset());\n    }\n\n    public String getNewValueString() {\n        return new String(this.newValue, 0, this.newValue.length, ServerCharset.UNICODE.charset());\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getName());\n        sb.append(\" [length=\").append(this.length);\n        sb.append(\", changeType=\").append(this.changeType);\n        sb.append(\", newValue=\").append(getNewValueString());\n        sb.append(']');\n        return sb.toString();\n    }\n\n    /**\n     * Environment change payload type (type of environment change).\n     */\n    public enum EnvChangeType {\n\n        Database(1) {\n            @Override\n            public int toByteLength(byte dataLength) {\n                return super.toByteLength(dataLength) * 2;\n            }\n        },\n        Language(2) {\n            @Override\n            public int toByteLength(byte dataLength) {\n                return super.toByteLength(dataLength) * 2;\n            }\n        },\n        Charset(3) {\n            @Override\n            public int toByteLength(byte dataLength) {\n                return super.toByteLength(dataLength) * 2;\n            }\n        },\n        Packetsize(4) {\n            @Override\n            public int toByteLength(byte dataLength) {\n                return super.toByteLength(dataLength) * 2;\n            }\n        },\n        UnicodeLCID(5) {\n            @Override\n            public int toByteLength(byte dataLength) {\n                return super.toByteLength(dataLength) * 2;\n            }\n        },\n        UnicodeSortingComparison(6) {\n            @Override\n            public int toByteLength(byte dataLength) {\n                return super.toByteLength(dataLength) * 2;\n            }\n        },\n        SQLCollation(7), BeginTx(8), CommitTx(9), RollbackTx(10), EnlistDTC(11), DefectTx(12), RealtimeLogShipping(13) {\n            @Override\n            public int toByteLength(byte dataLength) {\n                return super.toByteLength(dataLength) * 2;\n            }\n        },\n        PromoteTx(15), TXMgrAddress(16), TxEnd(17), RSETACK(18), UserInstance(19) {\n            @Override\n            public int toByteLength(byte dataLength) {\n                return super.toByteLength(dataLength) * 2;\n            }\n        },\n        // Routing is used for redirections to a different server\n        Routing(20);\n\n        private final byte type;\n\n        EnvChangeType(int type) {\n            this.type = (byte) type;\n        }\n\n        public byte getType() {\n            return this.type;\n        }\n\n        /**\n         * Lookup {@link EnvChangeType} by its by {@code value}.\n         *\n         * @param value the env change type byte value.\n         * @return the resolved {@link EnvChangeType}.\n         * @throws IllegalArgumentException if the {@code value} cannot be resolved to a {@link EnvChangeType}.\n         */\n        public static EnvChangeType valueOf(int value) {\n\n            for (EnvChangeType envChangeType : values()) {\n                if (envChangeType.getType() == (byte) value) {\n                    return envChangeType;\n                }\n            }\n\n            throw new IllegalArgumentException(String.format(\"Invalid env change type 0x%01X\", value));\n        }\n\n        public int toByteLength(byte dataLength) {\n            return dataLength;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/ErrorToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\n\n/**\n * Error token.\n *\n * @author Mark Paluch\n */\npublic final class ErrorToken extends AbstractInfoToken {\n\n    public static final byte TYPE = (byte) 0xAA;\n\n    public ErrorToken(long length, long number, int state, int infoClass, String message, String serverName, String procName, long lineNumber) {\n        super(TYPE, length, number, (byte) state, (byte) infoClass, message, serverName, procName, lineNumber);\n    }\n\n    public ErrorToken(long length, long number, byte state, byte infoClass, String message, String serverName, String procName, long lineNumber) {\n        super(TYPE, length, number, state, infoClass, message, serverName, procName, lineNumber);\n    }\n\n    /**\n     * Decode the {@link ErrorToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link ErrorToken}\n     */\n    public static ErrorToken decode(ByteBuf buffer) {\n\n        int length = Decode.uShort(buffer);\n        long number = Decode.asLong(buffer);\n        byte state = Decode.asByte(buffer);\n        byte infoClass = Decode.asByte(buffer);\n\n        String msgText = Decode.unicodeUString(buffer);\n        String serverName = Decode.unicodeBString(buffer);\n        String procName = Decode.unicodeBString(buffer);\n\n        long lineNumber = buffer.readUnsignedInt();\n\n        return new ErrorToken(length, number, state, infoClass, msgText, serverName, procName, lineNumber);\n    }\n\n    @Override\n    public String getName() {\n        return \"ERROR\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/FeatureExtAckToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Feature Extension Acknowledgement token.\n *\n * @author Mark Paluch\n */\npublic final class FeatureExtAckToken extends AbstractDataToken {\n\n    public static final byte TYPE = (byte) 0xAE;\n\n    public static final byte TERMINATOR = (byte) 0xFF;\n\n    private final List<FeatureToken> featureTokens;\n\n    private FeatureExtAckToken(List<FeatureToken> featureTokens) {\n        super(TYPE);\n        this.featureTokens = featureTokens;\n    }\n\n    /**\n     * Decode the {@link FeatureExtAckToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link FeatureExtAckToken}.\n     */\n    public static FeatureExtAckToken decode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n        List<FeatureToken> featureTokens = new ArrayList<>();\n\n        while (true) {\n\n            byte featureId = buffer.readByte();\n\n            if (featureId == TERMINATOR) {\n                break;\n            }\n\n            if (featureId == ColumnEncryption.FEATURE_ID) {\n                featureTokens.add(ColumnEncryption.decode(buffer));\n                continue;\n            }\n\n            featureTokens.add(UnknownFeature.decode(featureId, buffer));\n        }\n\n        return new FeatureExtAckToken(featureTokens);\n    }\n\n    public List<FeatureToken> getFeatureTokens() {\n        return this.featureTokens;\n    }\n\n    @Override\n    public String getName() {\n        return \"FEATUREEXTACK\";\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [featureTokens=\").append(this.featureTokens);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    /**\n     * Acknowledged feature token.\n     */\n    public abstract static class FeatureToken {\n\n        /**\n         * The unique identifier number of a feature.\n         */\n        private final byte featureId;\n\n        /**\n         * The length of FeatureAckData, in bytes.\n         */\n        private final long length;\n\n        public FeatureToken(byte featureId, long length) {\n            this.featureId = featureId;\n            this.length = length;\n        }\n\n    }\n\n    /**\n     * Column encryption.\n     */\n    public final static class ColumnEncryption extends FeatureToken {\n\n        public static final byte FEATURE_ID = 0x04;\n\n        /**\n         * Supported table column encryption version.\n         */\n        private final byte tceVersion;\n\n        public ColumnEncryption(long length, byte tceVersion) {\n\n            super(FEATURE_ID, length);\n            this.tceVersion = tceVersion;\n        }\n\n        /**\n         * Decode an unknown feature.\n         *\n         * @param buffer the data buffer.\n         * @return the decoded {@link ColumnEncryption}.\n         */\n        public static ColumnEncryption decode(ByteBuf buffer) {\n\n            Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n            long length = Decode.dword(buffer);\n\n            if (length != 1) {\n                throw ProtocolException.unsupported(\"Unknown version number for AE\");\n            }\n\n            byte tceVersion = buffer.readByte();\n\n            if (tceVersion != 1) {\n                throw ProtocolException.unsupported(\"Unsupported version number for AE\");\n            }\n\n            return new ColumnEncryption(length, tceVersion);\n        }\n\n        public byte getTceVersion() {\n            return this.tceVersion;\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [tceVersion=\").append(this.tceVersion);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * Placeholder for unknown features.\n     */\n    public final static class UnknownFeature extends FeatureToken {\n\n        private final byte[] data;\n\n        public UnknownFeature(byte featureId, long length, byte[] data) {\n            super(featureId, length);\n            this.data = data;\n        }\n\n        /**\n         * Decode an unknown feature.\n         *\n         * @param featureId the passed-through feature Id.\n         * @param buffer    the data buffer.\n         * @return the decoded {@link UnknownFeature}.\n         */\n        public static UnknownFeature decode(byte featureId, ByteBuf buffer) {\n\n            Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n            long length = Decode.dword(buffer);\n            byte[] bytes = new byte[Math.toIntExact(length)];\n\n            buffer.readBytes(bytes);\n\n            return new UnknownFeature(featureId, length, bytes);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/Identifier.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.StringUtils;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.Objects;\n\n/**\n * Identifier for an object, typically a type or a table name.\n *\n * @author Mark Paluch\n */\npublic final class Identifier {\n\n    @Nullable\n    private final String serverName;\n\n    @Nullable\n    private final String databaseName;\n\n    @Nullable\n    private final String schemaName;\n\n    private final String objectName;\n\n    private Identifier(@Nullable String serverName, @Nullable String databaseName, @Nullable String schemaName, String objectName) {\n\n        this.serverName = serverName;\n        this.databaseName = databaseName;\n        this.schemaName = schemaName;\n        this.objectName = Assert.requireNonNull(objectName, \"Object name must not be null\");\n    }\n\n    /**\n     * Create a new {@link Identifier} given {@code objectName}.\n     *\n     * @param objectName the object name.\n     * @return the {@link Identifier} for {@code objectName}.\n     * @throws IllegalArgumentException when {@code objectName} is {@code null}.\n     */\n    public static Identifier objectName(String objectName) {\n        return new Identifier(null, null, null, objectName);\n    }\n\n    /**\n     * Creates a new {@link Builder} to build {@link Identifier}.\n     *\n     * @return a new {@link Builder}.\n     */\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    /**\n     * Decode the identifier.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link Identifier}.\n     * @throws IllegalArgumentException when {@link ByteBuf} is {@code null}.\n     */\n    public static Identifier decode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n        // Multi-part names should have between 1 and 4 parts\n        int parts = Decode.uByte(buffer);\n        if (!(1 <= parts && parts <= 4)) {\n            throw ProtocolException.invalidTds(String.format(\"Identifier must contain one to four parts, got: %d\", parts));\n        }\n\n        // Each part is a length-prefixed Unicode string\n        String[] nameParts = new String[parts];\n        for (int i = 0; i < parts; i++) {\n            nameParts[i] = Decode.unicodeUString(buffer);\n        }\n\n        String serverName = null;\n        String databaseName = null;\n        String schemaName = null;\n        String objectName = nameParts[parts - 1];\n\n        if (parts >= 2) {\n            schemaName = nameParts[parts - 2];\n        }\n\n        if (parts >= 3) {\n            databaseName = nameParts[parts - 3];\n        }\n\n        if (parts == 4) {\n            serverName = nameParts[parts - 4];\n        }\n\n        return new Identifier(serverName, databaseName, schemaName, objectName);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link ColumnMetadataToken}. Advances the {@link ByteBuf#readerIndex()}.\n     *\n     * @param buffer the data buffer.\n     * @return {@literal true} if the {@link Identifier} can be decoded.\n     * @throws IllegalArgumentException when {@link ByteBuf} is {@code null}.\n     */\n    static boolean canDecodeAndSkipBytes(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n        if (!buffer.isReadable()) {\n            return false;\n        }\n\n        // Multi-part names should have between 1 and 4 parts\n        int parts = Decode.uByte(buffer);\n\n        // Each part is a length-prefixed Unicode string\n        String[] nameParts = new String[parts];\n        for (int i = 0; i < parts; i++) {\n\n            if (1 > buffer.readableBytes()) {\n                return false;\n            }\n            int length = buffer.readUnsignedShortLE() * 2;\n\n            if (length > buffer.readableBytes()) {\n\n                return false;\n            }\n            buffer.skipBytes(length);\n        }\n\n        return true;\n    }\n\n    @Nullable\n    public String getServerName() {\n        return this.serverName;\n    }\n\n    @Nullable\n    public String getDatabaseName() {\n        return this.databaseName;\n    }\n\n    @Nullable\n    public String getSchemaName() {\n        return this.schemaName;\n    }\n\n    public String getObjectName() {\n        return this.objectName;\n    }\n\n    public String asEscapedString() {\n\n        StringBuilder fullName = new StringBuilder(256);\n\n        if (StringUtils.hasText(this.serverName)) {\n            fullName.append(\"[\" + this.serverName + \"].\");\n        }\n\n        if (StringUtils.hasText(this.databaseName)) {\n            fullName.append(\"[\" + this.databaseName + \"].\");\n        } else {\n            Assert.state(StringUtils.isEmpty(this.serverName), \"Server name must be empty\");\n        }\n\n        if (StringUtils.hasText(this.schemaName)) {\n            fullName.append(\"[\" + this.schemaName + \"].\");\n        } else if (StringUtils.hasText(this.databaseName)) {\n            fullName.append('.');\n        }\n\n        fullName.append(\"[\" + this.objectName + \"]\");\n\n        return fullName.toString();\n    }\n\n    @Override\n    public String toString() {\n        return asEscapedString();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof Identifier)) {\n            return false;\n        }\n        Identifier that = (Identifier) o;\n        return Objects.equals(this.serverName, that.serverName) &&\n            Objects.equals(this.databaseName, that.databaseName) &&\n            Objects.equals(this.schemaName, that.schemaName) &&\n            Objects.equals(this.objectName, that.objectName);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.serverName, this.databaseName, this.schemaName, this.objectName);\n    }\n\n    /**\n     * Builder for {@link Identifier}.\n     */\n    public static class Builder {\n\n        @Nullable\n        private String serverName;\n\n        @Nullable\n        private String databaseName;\n\n        @Nullable\n        private String schemaName;\n\n        @Nullable\n        private String objectName;\n\n        private Builder() {\n        }\n\n        /**\n         * Configure an {@code objectName}.\n         *\n         * @param objectName the object name, must not be {@code null}.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code objectName} is {@code null}.\n         */\n        public Builder objectName(String objectName) {\n\n            this.objectName = Assert.requireNonNull(objectName, \"Object name must not be null\");\n\n            return this;\n        }\n\n        /**\n         * Configure a {@code schemaName}.\n         *\n         * @param schemaName the schema name, must not be {@code null}.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code schemaName} is {@code null}.\n         */\n        public Builder schemaName(String schemaName) {\n\n            this.schemaName = Assert.requireNonNull(schemaName, \"Schema name must not be null\");\n\n            return this;\n        }\n\n        /**\n         * Configure a {@code databaseName}.\n         *\n         * @param databaseName the database name, must not be {@code null}.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link ByteBuf} is {@code null}.\n         */\n        public Builder databaseName(String databaseName) {\n\n            this.databaseName = Assert.requireNonNull(databaseName, \"Database name must not be null\");\n\n            return this;\n        }\n\n        /**\n         * Configure a {@code serverName}. Requires the {@link #databaseName(String)} be set if the server name is not empty.\n         *\n         * @param serverName the server name, must not be {@code null}.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code serverName} is {@code null}.\n         */\n        public Builder serverName(String serverName) {\n\n            this.serverName = Assert.requireNonNull(serverName, \"Server name must not be null\");\n\n            return this;\n        }\n\n        /**\n         * Build the {@link Identifier}.\n         *\n         * @return the {@link Identifier}\n         */\n        public Identifier build() {\n\n            Assert.notNull(this.objectName, \"Object name must not be null\");\n\n            Assert.state(StringUtils.isEmpty(this.serverName) || !StringUtils.isEmpty(this.databaseName), \"Server name must be either null or both, server name and database name must \" +\n                \"be provided\");\n\n            return new Identifier(this.serverName, this.databaseName, this.schemaName, this.objectName);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/InfoToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\n\n/**\n * Info token.\n *\n * @author Mark Paluch\n */\npublic final class InfoToken extends AbstractInfoToken {\n\n    public static final byte TYPE = (byte) 0xAB;\n\n    public InfoToken(long length, long number, int state, int infoClass, String message, String serverName, String procName, long lineNumber) {\n        super(TYPE, length, number, (byte) state, (byte) infoClass, message, serverName, procName, lineNumber);\n    }\n\n    public InfoToken(long length, long number, byte state, byte infoClass, String message, String serverName, String procName, long lineNumber) {\n        super(TYPE, length, number, state, infoClass, message, serverName, procName, lineNumber);\n    }\n\n    /**\n     * Decode the {@link InfoToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link InfoToken}\n     */\n    public static InfoToken decode(ByteBuf buffer) {\n\n        int length = Decode.uShort(buffer);\n        long number = Decode.asLong(buffer);\n        byte state = Decode.asByte(buffer);\n        byte infoClass = Decode.asByte(buffer);\n\n        String msgText = Decode.unicodeUString(buffer);\n        String serverName = Decode.unicodeBString(buffer);\n        String procName = Decode.unicodeBString(buffer);\n\n        long lineNumber = buffer.readUnsignedInt();\n\n        return new InfoToken(length, number, state, infoClass, msgText, serverName, procName, lineNumber);\n    }\n\n    @Override\n    public String getName() {\n        return \"INFO\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/Login7.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.TDSVersion;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsPackets;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.DriverVersion;\nimport io.r2dbc.mssql.util.Version;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.function.Function;\n\n/**\n * Login 7 message.\n *\n * @author Mark Paluch\n */\npublic final class Login7 implements TokenStream, ClientMessage {\n\n    private static final short TDS_LOGIN_REQUEST_BASE_LEN = 94;\n\n    private static final HeaderOptions header = HeaderOptions.create(Type.TDS7_LOGIN, Status.empty());\n\n    private final int estimatedPacketLength;\n\n    private final int baseLength;\n\n    /**\n     * The highest TDS version being used by the client (for example, 0x00000071 for TDS 7.1).\n     * <p/>\n     * If the TDSVersion value sent by the client is greater than the value that the server recognizes, the server MUST\n     * use the highest TDS version that it can use. This provides a mechanism for clients to discover the server TDS by\n     * sending a standard LOGIN7 message. If the TDSVersion value sent by the client is lower than the highest TDS version\n     * the server recognizes, the server MUST use the TDS version sent by the client\n     */\n    private final TDSVersion tdsVersion;\n\n    /**\n     * The packet size being requested by the client.\n     */\n    private final int packetSize;\n\n    /**\n     * The version of the interface library (for example, ODBC or OLEDB) being used by the client, 4 byte.\n     */\n    private final byte[] clientProgVer;\n\n    /**\n     * The process ID of the client application.\n     */\n    private final int clientPid;\n\n    /**\n     * The connection ID of the primary Server. Used when connecting to an \"Always Up\" backup server.\n     */\n    private final int connectionId;\n\n    private final OptionFlags1 optionFlags1;\n\n    private final OptionFlags2 optionFlags2;\n\n    private final TypeFlags typeFlags;\n\n    private final OptionFlags3 optionFlags3;\n\n    private final Collection<LoginRequestToken> tokens;\n\n    private final byte[] clientId;\n\n    private final ConditionalProtocolSegment passwordChange;\n\n    private Login7(TDSVersion tdsVersion, int packetSize, byte[] clientProgVer, int clientPid, int connectionId,\n                   OptionFlags1 optionFlags1, OptionFlags2 optionFlags2, TypeFlags typeFlags, OptionFlags3 optionFlags3,\n                   Collection<LoginRequestToken> tokens, byte[] clientId) {\n\n        this.tdsVersion = tdsVersion;\n        this.packetSize = packetSize;\n        this.clientProgVer = clientProgVer;\n        this.clientPid = clientPid;\n        this.connectionId = connectionId;\n        this.optionFlags1 = optionFlags1;\n        this.optionFlags2 = optionFlags2;\n        this.typeFlags = typeFlags;\n        this.optionFlags3 = optionFlags3;\n        this.tokens = tokens;\n        this.clientId = clientId;\n\n        int baseLength = TDS_LOGIN_REQUEST_BASE_LEN;\n\n        EnumSet<TokenType> lengthRelevant = EnumSet.of(TokenType.Hostname, TokenType.AppName, TokenType.Servername,\n            TokenType.IntName, TokenType.Database, TokenType.Username);\n        for (LoginRequestToken token : tokens) {\n\n            if (lengthRelevant.contains(token.getTokenType())) {\n                baseLength += token.getValue().length;\n            }\n        }\n\n        baseLength += getToken(TokenType.Password).getEncrypted().length;\n\n        ConditionalProtocolSegment passwordChange = Conditionals.DISABLED;\n\n        if (tdsVersion.isGreateOrEqualsTo(TDSVersion.VER_YUKON)) {\n            passwordChange = Conditionals.PASSWORD_CHANGE;\n        }\n\n        this.passwordChange = passwordChange;\n        this.baseLength = baseLength + 4 /* AE */;\n        this.estimatedPacketLength = this.baseLength + Header.LENGTH + 2 + passwordChange.length() + 1;\n    }\n\n    /**\n     * @return a builder for {@link Login7}.\n     */\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    @Override\n    public String getName() {\n        return \"LOGIN7\";\n    }\n\n    @Override\n    public TdsFragment encode(ByteBufAllocator allocator, int packetSize) {\n\n        ByteBuf buffer = allocator.buffer(this.estimatedPacketLength);\n\n        encode(buffer);\n        return TdsPackets.create(header, buffer);\n    }\n\n    void encode(ByteBuf buffer) {\n\n        int len = this.baseLength;\n        int aeoffset = len;\n\n        buffer.writeIntLE(this.baseLength + 6 + 1);\n        buffer.writeIntLE(this.tdsVersion.getVersion());\n        buffer.writeIntLE(this.packetSize);\n        buffer.writeBytes(this.clientProgVer);\n        buffer.writeIntLE(this.clientPid);\n        buffer.writeInt(0); // Primary server connection ID\n        buffer.writeByte(this.optionFlags1.getValue());\n        buffer.writeByte(this.optionFlags2.getValue());\n        buffer.writeByte(this.typeFlags.getValue());\n        buffer.writeByte(this.optionFlags3.getValue());\n        buffer.writeIntLE(0); // Client time zone\n        buffer.writeIntLE(0); // Client LCID\n\n        int dataLen = 0;\n\n        LoginRequestToken hostname = getToken(TokenType.Hostname);\n        LoginRequestToken username = getToken(TokenType.Username);\n        LoginRequestToken password = getToken(TokenType.Password);\n        LoginRequestToken appName = getToken(TokenType.AppName);\n        LoginRequestToken serverName = getToken(TokenType.Servername);\n        LoginRequestToken intName = getToken(TokenType.IntName);\n        LoginRequestToken database = getToken(TokenType.Database);\n\n        // Hostname position + length\n        dataLen = writeToken(dataLen, buffer, hostname, LoginRequestToken::getValue);\n\n        // Credentials position + length\n        dataLen = writeToken(dataLen, buffer, username, LoginRequestToken::getValue);\n        dataLen = writeToken(dataLen, buffer, password, LoginRequestToken::getEncrypted);\n\n        // AppName position + length\n        dataLen = writeToken(dataLen, buffer, appName, LoginRequestToken::getValue);\n\n        // Server name position + length\n        dataLen = writeToken(dataLen, buffer, serverName, LoginRequestToken::getValue);\n\n        // Unused\n        // AE is always ON\n        buffer.writeShortLE(TDS_LOGIN_REQUEST_BASE_LEN + dataLen);\n        buffer.writeShortLE(4);\n        dataLen += 4;\n\n        // Interface library name position + length\n        dataLen = writeToken(dataLen, buffer, intName, LoginRequestToken::getValue);\n\n        // Language\n        buffer.writeShort(0);\n        buffer.writeShort(0);\n\n        // Database name position + length\n        dataLen = writeToken(dataLen, buffer, database, LoginRequestToken::getValue);\n\n        buffer.writeBytes(this.clientId);\n\n        // SSPI/Integrated security disabled.\n        buffer.writeShort(0);\n        buffer.writeShort(0);\n\n        // Database to attach during connection process\n        buffer.writeShort(0);\n        buffer.writeShort(0);\n\n        // TDS 7.2: Password change\n        this.passwordChange.encode(buffer);\n\n        buffer.writeBytes(hostname.getValue());\n        buffer.writeBytes(username.getValue());\n        buffer.writeBytes(password.getEncrypted());\n        buffer.writeBytes(appName.getValue());\n        buffer.writeBytes(serverName.getValue());\n\n        // Extension disabled\n        buffer.writeIntLE(aeoffset);\n\n        buffer.writeBytes(intName.getValue());\n        buffer.writeBytes(database.getValue());\n\n        // AE\n        buffer.writeByte(4);\n        buffer.writeIntLE(1);\n        buffer.writeByte(1);\n\n        // AE Terminator\n        buffer.writeByte(-1);\n    }\n\n    private int writeToken(int dataLength, ByteBuf buffer, LoginRequestToken token,\n                           Function<LoginRequestToken, byte[]> valueFunction) {\n\n        buffer.writeShortLE(TDS_LOGIN_REQUEST_BASE_LEN + dataLength);\n        buffer.writeShortLE(token.getLength());\n\n        return dataLength + valueFunction.apply(token).length;\n    }\n\n    private LoginRequestToken getToken(TokenType tokenType) {\n\n        for (LoginRequestToken token : this.tokens) {\n            if (token.getTokenType() == tokenType) {\n                return token;\n            }\n        }\n\n        return new LoginRequestToken(TokenType.Unknown, \"\");\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [header=\").append(header);\n        sb.append(\", tdsVersion=\").append(this.tdsVersion);\n        sb.append(\", packetSize=\").append(this.packetSize);\n        sb.append(\", clientPid=\").append(this.clientPid);\n        sb.append(\", connectionId=\").append(this.connectionId);\n        sb.append(\", tokens=\").append(this.tokens);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    /**\n     * Builder for {@link Login7} requests. Pre-initializes driver version, driver name, app name, and hostname.\n     */\n    public static class Builder {\n\n        private TDSVersion tdsVersion;\n\n        /**\n         * The packet size being requested by the client.\n         */\n        private int packetSize = 8000;\n\n        /**\n         * The version of the interface library (for example, ODBC or OLEDB) being used by the client.\n         */\n        private Version clientLibraryVersion = DriverVersion.getVersion();\n\n        /**\n         * The process ID of the client application.\n         */\n        private int clientPid;\n\n        /**\n         * The client Id.\n         */\n        private byte[] clientId = new byte[6];\n\n        /**\n         * The connection ID of the primary Server. Used when connecting to an \"Always Up\" backup server.\n         */\n        private int connectionId;\n\n        private OptionFlags1 optionFlags1 = OptionFlags1.empty().byteOrderX86().charSetAscii().floatIeee754().dumpLoadOn()\n            .useDbOff().initDatabaseFailFatal().enableLang();\n\n        private OptionFlags2 optionFlags2 = OptionFlags2.empty().setInitLangFailFatal().enableOdbc()\n            .disableIntegratedSecurity();\n\n        private TypeFlags typeFlags = TypeFlags.empty().defaultSqlType();\n\n        private OptionFlags3 optionFlags3 = OptionFlags3.empty().enableUnknownCollationHandling().enableExtensions();\n\n        @Nullable\n        private CharSequence username;\n\n        @Nullable\n        private CharSequence password;\n\n        @Nullable\n        private CharSequence applicationName;\n\n        @Nullable\n        private CharSequence hostname;\n\n        private CharSequence clientLibraryName;\n\n        @Nullable\n        private CharSequence databaseName;\n\n        @Nullable\n        private CharSequence serverName;\n\n        private Builder() {\n\n            String clientLibraryName = \"R2DBC Driver for Microsoft SQL Server v\";\n\n            if (this.clientLibraryVersion != null) {\n                clientLibraryName += this.clientLibraryVersion.toString();\n            }\n\n            this.clientLibraryName = clientLibraryName;\n            this.applicationName = clientLibraryName;\n        }\n\n        /**\n         * Set the TDS version.\n         *\n         * @param tdsVersion the TDS protocol version.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link TDSVersion} is {@code null}.\n         */\n        public Builder tdsVersion(TDSVersion tdsVersion) {\n\n            this.tdsVersion = Assert.requireNonNull(tdsVersion, \"TDS version must not be null\");\n            return this;\n        }\n\n        /**\n         * Set the requested packet size.\n         *\n         * @param packetSize the requested packet size.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder packetSize(int packetSize) {\n            this.packetSize = packetSize;\n            return this;\n        }\n\n        /**\n         * Configure {@link OptionFlags1}.\n         *\n         * @param optionFlags1 option 1 flags.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link OptionFlags1} is {@code null}.\n         */\n        public Builder optionFlags1(OptionFlags1 optionFlags1) {\n\n            this.optionFlags1 = Assert.requireNonNull(optionFlags1, \"Option flags 1 must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure {@link OptionFlags2}.\n         *\n         * @param optionFlags2 option 2 flags.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link OptionFlags2} is {@code null}.\n         */\n        public Builder optionFlags2(OptionFlags2 optionFlags2) {\n\n            this.optionFlags2 = Assert.requireNonNull(optionFlags2, \"Option flags 2 must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure {@link OptionFlags3}.\n         *\n         * @param optionFlags3 option 3 flags.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link OptionFlags3} is {@code null}.\n         */\n        public Builder optionFlags3(OptionFlags3 optionFlags3) {\n\n            this.optionFlags3 = Assert.requireNonNull(optionFlags3, \"Option flags 3 must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure {@link TypeFlags}.\n         *\n         * @param typeFlags the type flags.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link TypeFlags} is {@code null}.\n         */\n        public Builder typeFlags(TypeFlags typeFlags) {\n\n            this.typeFlags = Assert.requireNonNull(typeFlags, \"Type flags must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the user name. Must not exceed 127 chars.\n         *\n         * @param username login username.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code username} is {@code null}.\n         */\n        public Builder username(CharSequence username) {\n\n            this.username = Assert.requireNonNull(username, \"Username must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the password. Must not exceed 128 chars.\n         *\n         * @param password login password.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code password} is {@code null}.\n         */\n        public Builder password(CharSequence password) {\n\n            Assert.requireNonNull(password, \"Password must not be null\");\n            Assert.isTrue(password.length() < 128, \"Password name must be shorter than 128 chars\");\n\n            this.password = password;\n            return this;\n        }\n\n        /**\n         * Configure the application name. Must not exceed 128 chars.\n         *\n         * @param applicationName the application name.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code applicationName} is {@code null}.\n         */\n        public Builder applicationName(CharSequence applicationName) {\n\n            Assert.requireNonNull(applicationName, \"App name must not be null\");\n            Assert.isTrue(applicationName.length() < 128, \"Application name must be shorter than 128 chars\");\n\n            this.applicationName = applicationName;\n            return this;\n        }\n\n        /**\n         * Configure the client library name. Must not exceed 128 chars.\n         *\n         * @param clientLibraryName driver name.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code clientLibraryName} is {@code null}.\n         */\n        public Builder clientLibraryName(CharSequence clientLibraryName) {\n\n            Assert.requireNonNull(clientLibraryName, \"Client library name must not be null\");\n            Assert.isTrue(clientLibraryName.length() < 128, \"Client library name must be shorter than 128 chars\");\n\n            this.clientLibraryName = clientLibraryName;\n            return this;\n        }\n\n        /**\n         * Configure the client library version. Must not exceed 128 chars.\n         *\n         * @param clientLibraryVersion driver version.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link Version} is {@code null}.\n         */\n        public Builder clientLibraryVersion(Version clientLibraryVersion) {\n\n            this.clientLibraryVersion = Assert.requireNonNull(clientLibraryVersion,\n                \"Client library version must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the client host name. Must not exceed 128 chars.\n         *\n         * @param hostname the client hostname.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code hostname} is {@code null}.\n         */\n        public Builder hostName(CharSequence hostname) {\n\n            Assert.requireNonNull(hostname, \"Hostname must not be null\");\n            Assert.isTrue(hostname.length() < 128, \"Hostname name must be shorter than 128 chars\");\n\n            this.hostname = hostname;\n            return this;\n        }\n\n        /**\n         * Configure the database name. Must not exceed 128 chars.\n         *\n         * @param databaseName the initial database name.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code databaseName} is {@code null}.\n         */\n        public Builder database(CharSequence databaseName) {\n\n            Assert.requireNonNull(databaseName, \"Database name must not be null\");\n            Assert.isTrue(databaseName.length() < 128, \"Database name must be shorter than 128 chars\");\n\n            this.databaseName = databaseName;\n            return this;\n        }\n\n        /**\n         * Configure the client server name. Must not exceed 128 chars.\n         *\n         * @param serverName the remote server name.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code serverName} is {@code null}.\n         */\n        public Builder serverName(CharSequence serverName) {\n\n            Assert.requireNonNull(serverName, \"Server name must not be null\");\n            Assert.isTrue(serverName.length() < 128, \"Server name must be shorter than 128 chars\");\n\n            this.serverName = serverName;\n            return this;\n        }\n\n        /**\n         * Configure the client Id. Must be exactly 6 bytes.\n         *\n         * @param clientId the client Id.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@code clientId} is {@code null}.\n         */\n        public Builder clientId(byte[] clientId) {\n\n            Assert.requireNonNull(clientId, \"Client name must not be null\");\n            Assert.isTrue(clientId.length == 6, \"Client Id must be exactly 6 chars\");\n\n            this.clientId = Arrays.copyOf(clientId, 6);\n            return this;\n        }\n\n        /**\n         * Configure the client process Id.\n         *\n         * @param processId the client process Id.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder clientProcessId(int processId) {\n\n            this.clientPid = processId;\n            return this;\n        }\n\n        /**\n         * Build a new {@link Login7} message.\n         *\n         * @return a new {@link Login7} message.\n         * @throws IllegalStateException if {@code username}, {@code password}, or {@code databaseName} is {@code null} (unconfigured).\n         */\n        public Login7 build() {\n\n            Assert.state(this.username != null, \"Username must not be null\");\n            Assert.state(this.password != null, \"Password must not be null\");\n            Assert.state(this.databaseName != null, \"Database must not be null\");\n\n            List<LoginRequestToken> requestTokens = new ArrayList<>();\n            requestTokens.add(new LoginRequestToken(TokenType.Hostname, this.hostname));\n            requestTokens.add(new LoginRequestToken(TokenType.Username, this.username));\n            requestTokens.add(new LoginRequestToken(TokenType.Password, this.password));\n            requestTokens.add(new LoginRequestToken(TokenType.AppName, this.applicationName));\n            requestTokens.add(new LoginRequestToken(TokenType.Servername, this.serverName));\n            requestTokens.add(new LoginRequestToken(TokenType.IntName, this.clientLibraryName));\n            requestTokens.add(new LoginRequestToken(TokenType.Database, this.databaseName));\n\n            byte[] interfaceLibVersion = new byte[4];\n\n            if (this.clientLibraryVersion != null) {\n                interfaceLibVersion = new byte[]{(byte) 0, (byte) this.clientLibraryVersion.getBugfix(),\n                    (byte) this.clientLibraryVersion.getMinor(), (byte) this.clientLibraryVersion.getMajor()};\n            }\n\n            return new Login7(this.tdsVersion, this.packetSize, interfaceLibVersion, this.clientPid, this.connectionId,\n                this.optionFlags1, this.optionFlags2, this.typeFlags, this.optionFlags3, requestTokens, this.clientId);\n\n        }\n\n    }\n\n    /**\n     * First option byte.\n     */\n    public final static class OptionFlags1 {\n\n        /**\n         * fByteOrder: The byte order used by client for numeric and datetime data types.\n         */\n        public static final byte LOGIN_OPTION1_ORDER_X86 = 0x00;\n\n        public static final byte LOGIN_OPTION1_ORDER_68000 = 0x01;\n\n        /**\n         * fChar: The character set used on the client.\n         */\n        public static final byte LOGIN_OPTION1_CHARSET_ASCII = 0x00;\n\n        public static final byte LOGIN_OPTION1_CHARSET_EBCDIC = 0x02;\n\n        /**\n         * fFLoat: The type of floating point representation used by the client.\n         */\n        public static final byte LOGIN_OPTION1_FLOAT_IEEE_754 = 0x00;\n\n        public static final byte LOGIN_OPTION1_FLOAT_VAX = 0x04;\n\n        public static final byte LOGIN_OPTION1_FLOAT_ND5000 = 0x08;\n\n        /**\n         * fDumpLoad: Set is dump/load or BCP capabilities are needed by the client.\n         */\n        public static final byte LOGIN_OPTION1_DUMPLOAD_ON = 0x00;\n\n        public static final byte LOGIN_OPTION1_DUMPLOAD_OFF = 0x10;\n\n        /**\n         * UseDB: Set if the client requires warning messages on execution of the USE SQL statement. If this flag is not\n         * set, the server MUST NOT inform the client when the database changes, and therefore the client will be unaware of\n         * any accompanying collation changes.\n         */\n        public static final byte LOGIN_OPTION1_USE_DB_ON = 0x00;\n\n        public static final byte LOGIN_OPTION1_USE_DB_OFF = 0x20;\n\n        /**\n         * fDatabase: Set if the change to initial database needs to succeed if the connection is to succeed.\n         */\n        public static final byte LOGIN_OPTION1_INIT_DB_WARN = 0x00;\n\n        public static final byte LOGIN_OPTION1_INIT_DB_FATAL = 0x40;\n\n        /**\n         * fSetLang: Set if the client requires warning messages on execution of a language change statement.\n         */\n        public static final byte LOGIN_OPTION1_SET_LANG_OFF = 0x00;\n\n        public static final byte LOGIN_OPTION1_SET_LANG_ON = (byte) 0x80;\n\n        private final int optionByte;\n\n        private OptionFlags1(int optionByte) {\n            this.optionByte = optionByte;\n        }\n\n        /**\n         * Creates an empty {@link OptionFlags1}.\n         *\n         * @return a new {@link OptionFlags1}.\n         */\n        public static OptionFlags1 empty() {\n            return new OptionFlags1((byte) 0x00);\n        }\n\n        /**\n         * Enable x68 byte order.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 byteOrderX86() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_ORDER_X86);\n        }\n\n        /**\n         * Enable 68000 byte order.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 byteOrder6800() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_ORDER_68000);\n        }\n\n        /**\n         * Enable ASCII charset use.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 charSetAscii() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_CHARSET_ASCII);\n        }\n\n        /**\n         * Enable EBCDIC charset use.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 charSetEbcdic() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_CHARSET_EBCDIC);\n        }\n\n        /**\n         * Represent floating point numbers using IEE 754.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 floatIeee754() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_FLOAT_IEEE_754);\n        }\n\n        /**\n         * Represent floating point numbers using VAX representation.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 floatVax() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_FLOAT_VAX);\n        }\n\n        /**\n         * Represent floating point numbers using ND500.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 floatNd500() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_FLOAT_ND5000);\n        }\n\n        /**\n         * Enable dump (BCP) loading capabilities.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 dumpLoadOn() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_DUMPLOAD_ON);\n        }\n\n        /**\n         * Disable dump (BCP) loading capabilities.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 dumpLoadOff() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_DUMPLOAD_OFF);\n        }\n\n        /**\n         * Disable {@code USE <database>} usage.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 useDbOff() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_USE_DB_OFF);\n        }\n\n        /**\n         * Enable {@code USE <database>} usage.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 useDbOn() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_USE_DB_ON);\n        }\n\n        /**\n         * Warn if the initial database cannot be used (selected).\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 initDatabaseFailWarn() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_INIT_DB_WARN);\n        }\n\n        /**\n         * Fail if the initial database cannot be used (selected).\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 initDatabaseFailFatal() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_INIT_DB_FATAL);\n        }\n\n        /**\n         * Disable language change.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 disableLang() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_SET_LANG_OFF);\n        }\n\n        /**\n         * Enable language change.\n         *\n         * @return new {@link OptionFlags1} with the option applied.\n         */\n        public OptionFlags1 enableLang() {\n            return new OptionFlags1(this.optionByte | LOGIN_OPTION1_SET_LANG_ON);\n        }\n\n        /**\n         * @return the combined option byte.\n         */\n        public byte getValue() {\n            return (byte) this.optionByte;\n        }\n\n    }\n\n    /**\n     * Second option byte.\n     */\n    public final static class OptionFlags2 {\n\n        /**\n         * fLanguage: Set if the change to initial language needs to succeed if the connect is to succeed.\n         */\n        public static final byte LOGIN_OPTION2_INIT_LANG_WARN = 0x00;\n\n        public static final byte LOGIN_OPTION2_INIT_LANG_FATAL = 0x01;\n\n        /**\n         * fODBC: Set if the client is the ODBC driver. This causes the server to set ANSI_DEFAULTS to ON,\n         * CURSOR_CLOSE_ON_COMMIT and IMPLICIT_TRANSACTIONS to OFF, TEXTSIZE to 0x7FFFFFFF (2GB) (TDS 7.2 and earlier),\n         * TEXTSIZE to infinite (introduced in TDS 7.3), and ROWCOUNT to infinite.\n         */\n        public static final byte LOGIN_OPTION2_ODBC_OFF = 0x00;\n\n        public static final byte LOGIN_OPTION2_ODBC_ON = 0x02;\n\n        public static final byte LOGIN_OPTION2_TRAN_BOUNDARY_OFF = 0x00;\n\n        public static final byte LOGIN_OPTION2_TRAN_BOUNDARY_ON = 0x04;\n\n        public static final byte LOGIN_OPTION2_CACHE_CONNECTION_OFF = 0x00;\n\n        public static final byte LOGIN_OPTION2_CACHE_CONNECTION_ON = 0x08;\n\n        /**\n         * fUserType: The type of user connecting to the server.\n         */\n        public static final byte LOGIN_OPTION2_USER_NORMAL = 0x00;\n\n        public static final byte LOGIN_OPTION2_USER_SERVER = 0x10;\n\n        public static final byte LOGIN_OPTION2_USER_REMUSER = 0x20;\n\n        public static final byte LOGIN_OPTION2_USER_SQLREPL = 0x30;\n\n        /**\n         * fIntSecurity: The type of security required by the client.\n         */\n        public static final byte LOGIN_OPTION2_INTEGRATED_SECURITY_OFF = 0x00;\n\n        public static final byte LOGIN_OPTION2_INTEGRATED_SECURITY_ON = (byte) 0x80;\n\n        private final int optionByte;\n\n        private OptionFlags2(int optionByte) {\n            this.optionByte = optionByte;\n        }\n\n        /**\n         * Creates an empty {@link OptionFlags2}.\n         *\n         * @return new {@link OptionFlags2}.\n         */\n        public static OptionFlags2 empty() {\n            return new OptionFlags2((byte) 0x00);\n        }\n\n        /**\n         * Warn if initial language cannot be selected.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 setInitLangFailWarn() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_INIT_LANG_WARN);\n        }\n\n        /**\n         * Fail if initial language cannot be selected.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 setInitLangFailFatal() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_INIT_LANG_FATAL);\n        }\n\n        /**\n         * Enable ODBC use.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 disableOdbc() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_ODBC_OFF);\n        }\n\n        /**\n         * Enable ODBC use.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 enableOdbc() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_ODBC_ON);\n        }\n\n        /**\n         * Use regular login.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 normalUserType() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_USER_NORMAL);\n        }\n\n        /**\n         * (reserved).\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 serverUserType() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_USER_SERVER);\n        }\n\n        /**\n         * Use remote (distributed query login) login.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 remoteUserType() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_USER_REMUSER);\n        }\n\n        /**\n         * Use replication login.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 sqlreplUserType() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_USER_SQLREPL);\n        }\n\n        /**\n         * Disable integrated security.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 disableIntegratedSecurity() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_INTEGRATED_SECURITY_OFF);\n        }\n\n        /**\n         * Enable integrated security.\n         *\n         * @return new {@link OptionFlags2} with the option applied.\n         */\n        public OptionFlags2 enableIntegratedSecurity() {\n            return new OptionFlags2(this.optionByte | LOGIN_OPTION2_INTEGRATED_SECURITY_ON);\n        }\n\n        /**\n         * @return the combined option byte.\n         */\n        public byte getValue() {\n            return (byte) this.optionByte;\n        }\n\n    }\n\n    /**\n     * Type flags.\n     */\n    public final static class TypeFlags {\n\n        /**\n         * fSQLType: The type of SQL the client sends to the server.\n         */\n        static final byte LOGIN_SQLTYPE_DEFAULT = 0x00;\n\n        static final byte LOGIN_SQLTYPE_TSQL = 0x01;\n\n        /**\n         * fOLEDB: Set if the client is the OLEDB driver. This causes the server to set ANSI_DEFAULTS to ON,\n         * CURSOR_CLOSE_ON_COMMIT and IMPLICIT_TRANSACTIONS to OFF, TEXTSIZE to 0x7FFFFFFF (2GB) (TDS 7.2 and earlier),\n         * TEXTSIZE to infinite (introduced in TDS 7.3), and ROWCOUNT to infinite.\n         */\n        static final byte LOGIN_OLEDB_OFF = 0x00;\n\n        static final byte LOGIN_OLEDB_ON = 0x10;\n\n        /**\n         * fReadOnlyIntent: This bit was introduced in TDS 7.4; however, TDS 7.1, 7.2, and 7.3 clients can also use this bit\n         * in LOGIN7 to specify that the application intent of the connection is read-only. The server SHOULD ignore this\n         * bit if the highest TDS version supported by the server is lower than TDS 7.4.\n         */\n        static final byte LOGIN_READ_ONLY_INTENT = 0x20;\n\n        static final byte LOGIN_READ_WRITE_INTENT = 0x00;\n\n        private final int optionByte;\n\n        private TypeFlags(int optionByte) {\n            this.optionByte = optionByte;\n        }\n\n        /**\n         * Creates an empty {@link TypeFlags}.\n         *\n         * @return new {@link TypeFlags}.\n         */\n        public static TypeFlags empty() {\n            return new TypeFlags((byte) 0x00);\n        }\n\n        /**\n         * Use the default SQL type.\n         *\n         * @return new {@link TypeFlags} with the option applied.\n         */\n        public TypeFlags defaultSqlType() {\n            return new TypeFlags(this.optionByte | LOGIN_SQLTYPE_DEFAULT);\n        }\n\n        /**\n         * Use T-SQL.\n         *\n         * @return new {@link TypeFlags} with the option applied.\n         */\n        public TypeFlags tsqlType() {\n            return new TypeFlags(this.optionByte | LOGIN_SQLTYPE_TSQL);\n        }\n\n        /**\n         * Disable OLEDB defaults.\n         *\n         * @return new {@link TypeFlags} with the option applied.\n         */\n        public TypeFlags disableOledb() {\n            return new TypeFlags(this.optionByte | LOGIN_OLEDB_OFF);\n        }\n\n        /**\n         * Enable OLEDB defaults.\n         *\n         * @return new {@link TypeFlags} with the option applied.\n         */\n        public TypeFlags enableOledb() {\n            return new TypeFlags(this.optionByte | LOGIN_OLEDB_ON);\n        }\n\n        /**\n         * @return the combined option byte.\n         */\n        public byte getValue() {\n            return (byte) this.optionByte;\n        }\n\n    }\n\n    /**\n     * Third option byte.\n     */\n    public final static class OptionFlags3 {\n\n        /**\n         * fChangePassword: Specifies whether the login request SHOULD change password.\n         */\n        static final byte LOGIN_OPTION3_DEFAULT = 0x00;\n\n        static final byte LOGIN_OPTION3_CHANGE_PASSWORD = 0x01;\n\n        /**\n         * fSendYukonBinaryXML: 1 if XML data type instances are returned as binary XML.\n         */\n        static final byte LOGIN_OPTION3_SEND_YUKON_BINARY_XML = 0x02;\n\n        /**\n         * fUserInstance: 1 if client is requesting separate process to be spawned as user instance.\n         */\n        static final byte LOGIN_OPTION3_USER_INSTANCE = 0x04;\n\n        /**\n         * fUnknownCollationHandling: This bit is used by the server to determine if a client is able to properly handle\n         * collations introduced after TDS 7.2. TDS 7.2 and earlier clients are encouraged to use this login packet bit.\n         * Servers MUST ignore this bit when it is sent by TDS 7.3 or 7.4 clients. See [MSDN-SQLCollation] and [MS-LCID]\n         * documents for the complete list of collations for a database server that supports SQL and LCIDs.\n         */\n        static final byte LOGIN_OPTION3_UNKNOWN_COLLATION_HANDLING = 0x08;\n\n        /**\n         * fExtension: Specifies whether ibExtension/cbExtension fields are used.\n         */\n        static final byte LOGIN_OPTION3_FEATURE_EXTENSION = 0x10;\n\n        private final int optionByte;\n\n        private OptionFlags3(int optionByte) {\n            this.optionByte = optionByte;\n        }\n\n        /**\n         * Creates an empty {@link OptionFlags3}.\n         *\n         * @return new {@link OptionFlags3}.\n         */\n        public static OptionFlags3 empty() {\n            return new OptionFlags3((byte) 0x00);\n        }\n\n        /**\n         * Request to change the password with the login.\n         *\n         * @return new {@link OptionFlags3} with the option applied.\n         */\n        public OptionFlags3 changePassword() {\n            return new OptionFlags3(this.optionByte | LOGIN_OPTION3_CHANGE_PASSWORD);\n        }\n\n        /**\n         * Fail if initial language cannot be selected.\n         *\n         * @return new {@link OptionFlags3} with the option applied.\n         */\n        public OptionFlags3 enableUserInstance() {\n            return new OptionFlags3(this.optionByte | LOGIN_OPTION3_USER_INSTANCE);\n        }\n\n        /**\n         * Enable ODBC use.\n         *\n         * @return new {@link OptionFlags3} with the option applied.\n         */\n        public OptionFlags3 enableUnknownCollationHandling() {\n            return new OptionFlags3(this.optionByte | LOGIN_OPTION3_UNKNOWN_COLLATION_HANDLING);\n        }\n\n        /**\n         * Enable Feature Extensions.\n         *\n         * @return new {@link OptionFlags3} with the option applied.\n         */\n        public OptionFlags3 enableExtensions() {\n            return new OptionFlags3(this.optionByte | LOGIN_OPTION3_FEATURE_EXTENSION);\n        }\n\n        /**\n         * @return the combined option byte.\n         */\n        public byte getValue() {\n            return (byte) this.optionByte;\n        }\n\n    }\n\n    public final static class LoginRequestToken {\n\n        private final TokenType tokenType;\n\n        private final int length;\n\n        private final byte[] value;\n\n        private final byte[] encrypted;\n\n        LoginRequestToken(TokenType tokenType, @Nullable CharSequence value) {\n            this.tokenType = tokenType;\n            this.length = value != null ? value.length() : 0;\n            this.value = toUCS16(value);\n            this.encrypted = encryptPassword(value);\n        }\n\n        public TokenType getTokenType() {\n            return this.tokenType;\n        }\n\n        public byte[] getValue() {\n            return this.value;\n        }\n\n        public byte[] getEncrypted() {\n            return this.encrypted;\n        }\n\n        public int getLength() {\n            return this.length;\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [tokenType=\").append(this.tokenType);\n            sb.append(']');\n            return sb.toString();\n        }\n\n        /**\n         * Convert to a String UCS16 encoding.\n         *\n         * @param s the string\n         * @return the encoded data\n         */\n        private static byte[] toUCS16(@Nullable CharSequence s) {\n            if (s == null) {\n                return new byte[0];\n            }\n            int l = s.length();\n            byte[] data = new byte[l * 2];\n            int offset = 0;\n            for (int i = 0; i < l; i++) {\n                int c = s.charAt(i);\n                byte b1 = (byte) (c & 0xFF);\n                data[offset++] = b1;\n                data[offset++] = (byte) ((c >> 8) & 0xFF); // Unicode MSB\n            }\n            return data;\n        }\n\n        /**\n         * Encrypt a password for the SQL Server logon.\n         *\n         * @param pwd the password\n         * @return the encryption\n         */\n        private byte[] encryptPassword(@Nullable CharSequence pwd) {\n            // Changed to handle non ascii passwords\n            if (pwd == null) {\n                pwd = \"\";\n            }\n            int len = pwd.length();\n            byte[] data = new byte[len * 2];\n            for (int i1 = 0; i1 < len; i1++) {\n                int j1 = pwd.charAt(i1) ^ 0x5a5a;\n                j1 = (j1 & 0xf) << 4 | (j1 & 0xf0) >> 4 | (j1 & 0xf00) << 4 | (j1 & 0xf000) >> 4;\n                byte b1 = (byte) ((j1 & 0xFF00) >> 8);\n                data[(i1 * 2) + 1] = b1;\n                byte b2 = (byte) ((j1 & 0x00FF));\n                data[(i1 * 2) + 0] = b2;\n            }\n            return data;\n        }\n\n    }\n\n    enum TokenType {\n        Hostname, Username, Password, AppName, Servername, IntName, Language, Database, Unknown\n    }\n\n    /**\n     * Conditional protocol segment.\n     */\n    interface ConditionalProtocolSegment {\n\n        /**\n         * @return length in bytes.\n         */\n        int length();\n\n        /**\n         * Encode the segment onto the {@link ByteBuf}.\n         *\n         * @param buffer the target {@link ByteBuf}.\n         */\n        void encode(ByteBuf buffer);\n\n    }\n\n    /**\n     * Collection of {@link ConditionalProtocolSegment}s.\n     */\n    enum Conditionals implements ConditionalProtocolSegment {\n\n        DISABLED,\n\n        /**\n         * @since TDS 7.2\n         */\n        PASSWORD_CHANGE {\n            @Override\n            public int length() {\n                return 8;\n            }\n\n            @Override\n            public void encode(ByteBuf buffer) {\n                buffer.writeShort((short) 0);\n                buffer.writeShort((short) 0);\n                buffer.writeInt((short) 0);\n            }\n        };\n\n        @Override\n        public int length() {\n            return 0;\n        }\n\n        @Override\n        public void encode(ByteBuf buffer) {\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/LoginAckToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.util.Version;\n\n/**\n * Info token.\n *\n * @author Mark Paluch\n */\npublic class LoginAckToken extends AbstractDataToken {\n\n    public static final byte TYPE = (byte) 0xAD;\n\n    public static final byte CLIENT_INTEFACE_DEFAULT = 0;\n\n    public static final byte CLIENT_INTEFACE_TSQL = 1;\n\n    /*\n     * The total length, in bytes, of the following fields: Interface, TDSVersion, Progname, and ProgVersion.\n     */\n    private final long length;\n\n    /**\n     * The type of interface with which the server will accept client requests:\n     * <ul>\n     * <li>SQL_DFLT (server confirms that whatever is sent by the client is acceptable. If the client requested SQL_DFLT,\n     * SQL_TSQL will be used)</li>\n     * <li>SQL_TSQL (TSQL is accepted)</li>\n     * </ul>\n     */\n    private final byte clientInterface;\n\n    /**\n     * The TDS version being used by the server.\n     */\n    private final int tdsVersion;\n\n    /**\n     * The name of the server.\n     */\n    private final String progrName;\n\n    /**\n     * Server version.\n     */\n    private final Version version;\n\n    public LoginAckToken(long length, byte clientInterface, int tdsVersion, String progrName, Version version) {\n        super(TYPE);\n        this.length = length;\n        this.clientInterface = clientInterface;\n        this.tdsVersion = tdsVersion;\n        this.progrName = progrName;\n        this.version = version;\n    }\n\n    /**\n     * Decode the {@link LoginAckToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link LoginAckToken}.\n     */\n    public static LoginAckToken decode(ByteBuf buffer) {\n\n        int length = Decode.uShort(buffer);\n        byte clientInterface = Decode.asByte(buffer);\n        int tdsVersion = Decode.intBigEndian(buffer);\n\n        String progName = Decode.unicodeBString(buffer);\n        int major = Decode.asByte(buffer);\n        int minor = Decode.asByte(buffer);\n        int build = buffer.readShort();\n\n        Version serverVersion = new Version(major, minor, build);\n\n        return new LoginAckToken(length, clientInterface, tdsVersion, progName, serverVersion);\n    }\n\n    public byte getClientInterface() {\n        return this.clientInterface;\n    }\n\n    public int getTdsVersion() {\n        return this.tdsVersion;\n    }\n\n    public String getProgrName() {\n        return this.progrName;\n    }\n\n    public Version getVersion() {\n        return this.version;\n    }\n\n    @Override\n    public String getName() {\n        return \"LOGINACK\";\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [clientInterface=\").append(this.clientInterface);\n        sb.append(\", tdsVersion=\").append(this.tdsVersion);\n        sb.append(\", progrName='\").append(this.progrName).append('\\\"');\n        sb.append(\", version=\").append(this.version);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/NbcRowToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.Arrays;\n\n/**\n * NBC Row (Null-bitmap compressed row). Expresses nullability through a bitmap.\n * <p><strong>Note:</strong> PLP values are aggregated in a single {@link ByteBuf} and not yet streamed. This is to be fixed.\n *\n * @author Mark Paluch\n */\npublic final class NbcRowToken extends RowToken {\n\n    public static final byte TYPE = (byte) 0xD2;\n\n    private final boolean[] nullMarker;\n\n    /**\n     * Creates a {@link NbcRowToken}.\n     *\n     * @param data       the row data.\n     * @param nullMarker {@code null} bitmap.\n     */\n    private NbcRowToken(ByteBuf[] data, boolean[] nullMarker) {\n        super(data);\n        this.nullMarker = nullMarker;\n    }\n\n    /**\n     * Decode a {@link NbcRowToken}.\n     *\n     * @param buffer  the data buffer.\n     * @param columns column descriptors.\n     * @return the {@link RowToken}.\n     */\n    public static NbcRowToken decode(ByteBuf buffer, Column[] columns) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n        Assert.requireNonNull(columns, \"List of Columns must not be null\");\n\n        return doDecode(buffer, columns);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link NbcRowToken}.\n     *\n     * @param buffer  the data buffer.\n     * @param columns column descriptors.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a row.\n     */\n    public static boolean canDecode(ByteBuf buffer, Column[] columns) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n        Assert.requireNonNull(columns, \"List of Columns must not be null\");\n\n        int readerIndex = buffer.readerIndex();\n        int nullBitmapSize = getNullBitmapSize(columns);\n        try {\n\n            if (buffer.readableBytes() < nullBitmapSize) {\n                return false;\n            }\n\n            boolean[] nullBitmap = getNullBitmap(buffer, columns);\n\n            for (int i = 0; i < columns.length; i++) {\n\n                Column column = columns[i];\n\n                if (nullBitmap[i]) {\n                    continue;\n                }\n\n                if (!canDecodeColumn(buffer, column)) {\n                    return false;\n                }\n            }\n\n            return true;\n        } finally {\n            buffer.readerIndex(readerIndex);\n        }\n    }\n\n    @Override\n    public ByteBuf getColumnData(int index) {\n        return this.nullMarker[index] ? null : super.getColumnData(index);\n    }\n\n    private static NbcRowToken doDecode(ByteBuf buffer, Column[] columns) {\n\n        ByteBuf[] data = new ByteBuf[columns.length];\n\n        boolean[] nullMarkers = getNullBitmap(buffer, columns);\n\n        for (int i = 0; i < columns.length; i++) {\n\n            Column column = columns[i];\n\n            if (nullMarkers[i]) {\n                data[i] = Unpooled.EMPTY_BUFFER;\n            } else {\n                data[i] = decodeColumnData(buffer, column);\n            }\n        }\n\n        return new NbcRowToken(data, nullMarkers);\n    }\n\n    private static boolean[] getNullBitmap(ByteBuf buffer, Column[] columns) {\n\n        int nullBitmapSize = getNullBitmapSize(columns);\n\n        boolean[] nullMarkers = new boolean[columns.length];\n        int column = 0;\n\n        for (int byteNo = 0; byteNo < nullBitmapSize; byteNo++) {\n\n            byte byteValue = Decode.asByte(buffer);\n\n            // if this byte is 0, skip to the next byte\n            // and increment the column number by 8(no of bits)\n            if (byteValue == 0) {\n                column = column + 8;\n                continue;\n            }\n\n            for (int bitNo = 0; bitNo < 8 && column < columns.length; bitNo++) {\n                if ((byteValue & (1 << bitNo)) != 0) {\n                    nullMarkers[column] = true;\n                }\n                column++;\n            }\n        }\n        return nullMarkers;\n    }\n\n    private static int getNullBitmapSize(Column[] columns) {\n        return ((columns.length - 1) >> 3) + 1;\n    }\n\n    @Override\n    public String getName() {\n        return \"NBCROW\";\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getName());\n        sb.append(\" [nullMarker=\").append(Arrays.toString(this.nullMarker));\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/OrderToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Order token to inform the client by which columns the data is ordered.\n *\n * @author Mark Paluch\n */\nclass OrderToken extends AbstractDataToken {\n\n    public static final byte TYPE = (byte) 0xA9;\n\n    private final List<Integer> orderByColumns;\n\n    /**\n     * Create a new {@link OrderToken} given {@link List} of column indexed by which data is ordered.\n     *\n     * @param orderByColumns must not be null.\n     */\n    private OrderToken(List<Integer> orderByColumns) {\n        super(TYPE);\n        this.orderByColumns = orderByColumns;\n    }\n\n    /**\n     * Decode a {@link OrderToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the {@link OrderToken}.\n     */\n    public static OrderToken decode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        int length = Decode.uShort(buffer);\n        int readerIndex = buffer.readerIndex();\n\n        List<Integer> columns = new ArrayList<>();\n\n        while (buffer.readerIndex() - readerIndex < length) {\n\n            int column = Decode.uShort(buffer);\n            columns.add(column);\n        }\n\n        return new OrderToken(columns);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link OrderToken}.\n     *\n     * @param buffer the data buffer.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode {@link OrderToken}\n     */\n    public static boolean canDecode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        Integer length = Decode.peekUShort(buffer);\n        return length != null && buffer.readableBytes() >= length + /* length field */ 2;\n    }\n\n    public List<Integer> getOrderByColumns() {\n        return this.orderByColumns;\n    }\n\n    @Override\n    public String getName() {\n        return \"ORDER\";\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [orderByColumns=\").append(this.orderByColumns);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/Prelogin.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Status.StatusBit;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.ContextualTdsFragment;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.NoSuchElementException;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.UUID;\n\n/**\n * Stream structure for {@code PRELOGIN}.\n *\n * @author Mark Paluch\n * @see Token\n */\npublic final class Prelogin implements TokenStream, ClientMessage {\n\n    private static final HeaderOptions HEADER_OPTIONS = HeaderOptions.create(Type.PRE_LOGIN, Status.of(StatusBit.EOM));\n\n    private final List<? extends Token> tokens;\n\n    /**\n     * Create a new {@link Prelogin} given {@link List} of {@link Token}.\n     *\n     * @param tokens must not be null.\n     */\n    public Prelogin(List<? extends Token> tokens) {\n\n        Assert.requireNonNull(tokens, \"Tokens must not be null\");\n        this.tokens = tokens;\n    }\n\n    /**\n     * @return a new builder for {@link Prelogin}.\n     */\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    /**\n     * Decode the {@link Prelogin} response from a {@link ByteBuf}.\n     *\n     * @param buffer must not be null.\n     * @return the decoded {@link Prelogin} response {@link Message}.\n     */\n    public static Prelogin decode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"ByteBuf must not be null\");\n\n        List<Token> decodedTokens = new ArrayList<>();\n        Prelogin prelogin = new Prelogin(decodedTokens);\n\n        TokenDecodingState decodingState = TokenDecodingState.create(buffer);\n\n        while (true) {\n\n            if (!decodingState.canDecode()) {\n                break;\n            }\n\n            byte type = Decode.asByte(buffer);\n\n            if (type == Terminator.TYPE) {\n                decodedTokens.add(Terminator.INSTANCE);\n                break;\n            }\n\n            if (type == Version.TYPE) {\n                decodedTokens.add(Version.decode(decodingState));\n                continue;\n            }\n\n            if (type == Encryption.TYPE) {\n                decodedTokens.add(Encryption.decode(decodingState));\n                continue;\n            }\n\n            if (type == InstanceValidation.TYPE) {\n                decodedTokens.add(InstanceValidation.decode(decodingState));\n                continue;\n            }\n\n            decodedTokens.add(UnknownToken.decode(type, decodingState));\n        }\n\n        // ignore remaining bytes of PreLogin response\n        buffer.skipBytes(buffer.readableBytes());\n\n        return prelogin;\n    }\n\n    /**\n     * @return the tokens.\n     */\n    public List<? extends Token> getTokens() {\n        return this.tokens;\n    }\n\n    /**\n     * Resolve a {@link Token} given its {@link Class type}.\n     *\n     * @param tokenType the type to filter.\n     * @param <T>       token type.\n     * @return {@link Optional} containing the potentially found {@code tokenType}.\n     */\n    public <T extends Token> Optional<T> getToken(Class<? extends T> tokenType) {\n\n        Assert.requireNonNull(tokenType, \"Token type must not be null\");\n\n        for (Token token : this.tokens) {\n            if (tokenType.isInstance(token)) {\n                return Optional.of(tokenType.cast(token));\n            }\n        }\n\n        return Optional.empty();\n    }\n\n    /**\n     * Resolve a {@link Token} given its {@link Class type}.\n     *\n     * @param tokenType the type to filter.\n     * @param <T>       token type.\n     * @return the token of type  {@code tokenType}.\n     * @throws NoSuchElementException if the token could not be found.\n     */\n    public <T extends Token> T getRequiredToken(Class<? extends T> tokenType) {\n\n        Assert.requireNonNull(tokenType, \"Token type must not be null\");\n\n        return getToken(tokenType).orElseThrow(\n            () -> new NoSuchElementException(String.format(\"No token of type [%s] available\", tokenType.getName())));\n    }\n\n    @Override\n    public String getName() {\n        return \"PRELOGIN\";\n    }\n\n    @Override\n    public TdsFragment encode(ByteBufAllocator allocator, int packetSize) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n\n        ByteBuf buffer = allocator.buffer(getSize(this.tokens));\n        encode(buffer);\n\n        return new ContextualTdsFragment(HEADER_OPTIONS, buffer);\n    }\n\n    /**\n     * Encode the {@link Prelogin} request message.\n     *\n     * @param buffer the data buffer to write to.\n     */\n    void encode(ByteBuf buffer) {\n\n        int tokenHeaderLength = 0;\n\n        for (Token token : this.tokens) {\n            tokenHeaderLength += token.getTokenHeaderLength();\n        }\n\n        int position = tokenHeaderLength;\n        for (Token token : this.tokens) {\n\n            token.encodeToken(buffer, position);\n            position += token.getDataLength();\n        }\n\n        for (Token token : this.tokens) {\n\n            token.encodeStream(buffer);\n            position += token.getDataLength();\n        }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof Prelogin)) {\n            return false;\n        }\n        Prelogin prelogin = (Prelogin) o;\n        return Objects.equals(this.tokens, prelogin.tokens);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.tokens);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getName());\n        sb.append(\" [tokens=\").append(this.tokens);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    private static int getSize(List<? extends Token> tokens) {\n\n        int size = Header.LENGTH;\n\n        for (Token token : tokens) {\n            size += token.getTotalLength();\n        }\n\n        return size;\n    }\n\n    /**\n     * Builder for {@link Prelogin}.\n     *\n     * @author Mark Paluch\n     */\n    public static class Builder {\n\n        /**\n         * Client application thread id;\n         */\n        private Integer threadId;\n\n        /**\n         * Client application trace id (for debugging purposes).\n         */\n        @Nullable\n        private UUID connectionId;\n\n        /**\n         * Client application activity id (for debugging purposes).\n         */\n        @Nullable\n        private UUID activityId;\n\n        /**\n         * Client application activity sequence (for debugging purposes).\n         */\n        private int activitySequence;\n\n        private byte encryption = Encryption.ENCRYPT_OFF;\n\n        private String instanceName = InstanceValidation.MSSQLSERVER_VALUE;\n\n        private Builder() {\n        }\n\n        /**\n         * Configure the client-side connection {@link UUID}. Typically used for tracing.\n         *\n         * @param connectionId the connection UUID.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withConnectionId(UUID connectionId) {\n\n            Assert.requireNonNull(connectionId, \"ConnectionID must not be null\");\n            this.connectionId = connectionId;\n\n            return this;\n        }\n\n        /**\n         * Configure the client-side activity {@link UUID}. Typically used for tracing.\n         *\n         * @param activityId the activity UUID.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withActivityId(UUID activityId) {\n\n            Assert.requireNonNull(activityId, \"Activity ID must not be null\");\n            this.activityId = activityId;\n\n            return this;\n        }\n\n        /**\n         * Configure the client-side activity sequence. Typically used for tracing.\n         *\n         * @param activitySequence the activity sequence.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withActivitySequence(int activitySequence) {\n\n            this.activitySequence = activitySequence;\n\n            return this;\n        }\n\n        /**\n         * Configure the client-side Thread Id. Typically used for tracing.\n         *\n         * @param threadId the Thread Id.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withThreadId(int threadId) {\n\n            this.threadId = threadId;\n\n            return this;\n        }\n\n        /**\n         * Disable encryption.\n         *\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withEncryptionDisabled() {\n\n            this.encryption = Encryption.ENCRYPT_OFF;\n\n            return this;\n        }\n\n        /**\n         * Enable encryption.\n         *\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withEncryptionEnabled() {\n\n            this.encryption = Encryption.ENCRYPT_ON;\n\n            return this;\n        }\n\n        /**\n         * Disable encryption by indicating encryption not supported.\n         *\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withEncryptionNotSupported() {\n\n            this.encryption = Encryption.ENCRYPT_NOT_SUP;\n\n            return this;\n        }\n\n        public Builder withInstanceName(String instanceName) {\n\n            Assert.requireNonNull(instanceName, \"Instance name must not be null\");\n            this.instanceName = instanceName;\n\n            return this;\n        }\n\n        /**\n         * Build the {@link Prelogin} message.\n         *\n         * @return the {@link Prelogin} message.\n         */\n        public Prelogin build() {\n\n            List<Token> tokens = new ArrayList<>();\n\n            tokens.add(new Version(0, 0));\n            tokens.add(new Encryption(this.encryption));\n            tokens.add(new InstanceValidation(this.instanceName));\n\n            if (this.threadId != null) {\n                tokens.add(new ThreadId(this.threadId));\n            }\n\n            if (this.connectionId != null) {\n                tokens.add(new TraceId(this.connectionId, this.activityId, this.activitySequence));\n            }\n\n            tokens.add(Terminator.INSTANCE);\n\n            return new Prelogin(tokens);\n        }\n\n    }\n\n    /**\n     * Pre-Login Token.\n     */\n    public abstract static class Token {\n\n        private final byte type;\n\n        private final int length;\n\n        Token(int type, int length) {\n\n            if (type > Byte.MAX_VALUE) {\n                throw new IllegalArgumentException(\"Type \" + type + \" exceeds byte value\");\n            }\n\n            this.type = (byte) type;\n            this.length = length;\n        }\n\n        /**\n         * Apply functional token decoding.\n         *\n         * @param toDecode  the decoding state.\n         * @param validator validator for\n         * @param decoder   token decode function.\n         * @return the decoded token.\n         */\n        static <T extends Token> T decode(TokenDecodingState toDecode, LengthValidator validator,\n                                          DecodeFunction<T> decoder) {\n\n            Assert.requireNonNull(toDecode, \"TokenDecodingState must not be null\");\n            Assert.requireNonNull(validator, \"LengthValidator must not be null\");\n            Assert.requireNonNull(decoder, \"DecodeFunction must not be null\");\n\n            ByteBuf buffer = toDecode.buffer;\n            short position = buffer.readShort();\n            short length = buffer.readShort();\n\n            validator.validate(length);\n\n            buffer.markReaderIndex();\n\n            ByteBuf data = toDecode.readBody(position, length);\n\n            T result = decoder.decode(length, data);\n\n            data.release();\n\n            buffer.resetReaderIndex();\n\n            toDecode.afterTokenDecoded();\n            return result;\n        }\n\n        public void encodeToken(ByteBuf buffer, int position) {\n\n            Encode.asByte(buffer, this.type);\n            Encode.uShortBE(buffer, position);\n            Encode.uShortBE(buffer, this.length);\n        }\n\n        /**\n         * Returns the token type.\n         *\n         * @return the token type.\n         */\n        byte getType() {\n            return this.type;\n        }\n\n        /**\n         * Returns the token length.\n         *\n         * @return the token data length.\n         */\n        int getLength() {\n            return this.length;\n        }\n\n        public abstract void encodeStream(ByteBuf buffer);\n\n        /**\n         * @return total length in bytes (including token header).\n         */\n        final int getTotalLength() {\n            return getDataLength() + getTokenHeaderLength();\n        }\n\n        /**\n         * @return token header length in bytes.\n         */\n        int getTokenHeaderLength() {\n            return 5;\n        }\n\n        /**\n         * @return length of data bytes.\n         */\n        int getDataLength() {\n            return this.length;\n        }\n\n    }\n\n    /**\n     * Terminating token indicating the end of prelogin tokens.\n     */\n    public static class Terminator extends Token {\n\n        public static final Terminator INSTANCE = new Terminator();\n\n        public static final byte TYPE = (byte) 0xFF;\n\n        Terminator() {\n            super(TYPE, 0);\n        }\n\n        @Override\n        public void encodeToken(ByteBuf buffer, int position) {\n            buffer.writeByte(getType());\n        }\n\n        @Override\n        public int getTokenHeaderLength() {\n            return 1;\n        }\n\n        @Override\n        public void encodeStream(ByteBuf buffer) {\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" []\");\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * Version information representing the SQL server version.\n     */\n    public static class Version extends Token {\n\n        public static final byte TYPE = 0x00;\n\n        /**\n         * version of the sender.\n         */\n        private final int version;\n\n        /**\n         * sub-build number of the sender\n         */\n        private final short subbuild;\n\n        /**\n         * Creates a new {@link Version} given major {@code version} and the {@code subbuild}.\n         *\n         * @param version  the server major version.\n         * @param subbuild the server subbuild.\n         */\n        public Version(int version, int subbuild) {\n            this(version, (byte) subbuild);\n        }\n\n        /**\n         * Creates a new {@link Version} given major {@code version} and the {@code subbuild}.\n         *\n         * @param version  the server major version.\n         * @param subbuild the server subbuild.\n         */\n        public Version(int version, short subbuild) {\n\n            super(TYPE, 6);\n\n            this.version = version;\n            this.subbuild = subbuild;\n        }\n\n        /**\n         * Decode the {@link Version} token.\n         *\n         * @param toDecode the current decoding state.\n         * @return the decoded {@link Version} token.\n         */\n        public static Version decode(TokenDecodingState toDecode) {\n\n            return decode(toDecode,\n                length -> {\n                    if (length != 6) {\n                        throw ProtocolException.invalidTds(String.format(\"Invalid version length: %s\", length));\n                    }\n                },\n                (length, body) -> {\n\n                    int major = Decode.asByte(body);\n                    int minor = Decode.asByte(body);\n                    short build = body.readShort();\n\n                    return new Version(major, build);\n                });\n        }\n\n        public int getVersion() {\n            return this.version;\n        }\n\n        public short getSubbuild() {\n            return this.subbuild;\n        }\n\n        @Override\n        public void encodeStream(ByteBuf buffer) {\n\n            Encode.dword(buffer, this.version);\n            Encode.shortBE(buffer, this.subbuild);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [version=\").append(this.version);\n            sb.append(\", subbuild=\").append(this.subbuild);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * Allows validating a remote SQL server instance.\n     */\n    public static class InstanceValidation extends Token {\n\n        static final String MSSQLSERVER_VALUE = \"MSSQLServer\";\n\n        public static final byte TYPE = 0x02;\n\n        /**\n         * Instance name for validation\n         */\n        private final byte[] instanceName;\n\n        /**\n         * Request SQL server instance name validation.\n         *\n         * @param instanceName the requested instance name.\n         */\n        public InstanceValidation(String instanceName) {\n            this(toBytes(instanceName));\n        }\n\n        /**\n         * Request SQL server instance name validation.\n         *\n         * @param instanceName the requested instance name.\n         */\n        private InstanceValidation(byte[] instanceName) {\n\n            super(TYPE, Assert.requireNonNull(instanceName, \"Instance name must not be null\").length);\n            this.instanceName = instanceName;\n        }\n\n        /**\n         * Decode the {@link InstanceValidation} token.\n         *\n         * @param toDecode the current decoding state.\n         * @return the decoded {@link InstanceValidation}.\n         */\n        public static InstanceValidation decode(TokenDecodingState toDecode) {\n\n            return decode(toDecode, LengthValidator.ignore(), (length, body) -> {\n\n                byte[] validation = new byte[length];\n                body.readBytes(validation, 0, length);\n\n                return new InstanceValidation(validation);\n            });\n        }\n\n        @Override\n        public void encodeStream(ByteBuf buffer) {\n            buffer.writeBytes(this.instanceName);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [instanceName=\").append(this.instanceName == null ? \"null\" : new String(this.instanceName));\n            sb.append(']');\n            return sb.toString();\n        }\n\n        // TODO: find an approach to use Encode.as(…)\n        private static byte[] toBytes(String instanceName) {\n\n            Assert.requireNonNull(instanceName, \"Instance name must not be null\");\n\n            byte[] name = instanceName.getBytes(StandardCharsets.UTF_8);\n            byte[] result = new byte[name.length + 1];\n            System.arraycopy(name, 0, result, 0, name.length);\n\n            return result;\n        }\n\n    }\n\n    /**\n     * Allows negotiation of the used encryption (Transport-Level Encryption via SSL) mode.\n     */\n    public static class Encryption extends Token {\n\n        public static final byte TYPE = 0x01;\n\n        /**\n         * Disabled encryption but enabled/required for login with credentials.\n         */\n        public static final byte ENCRYPT_OFF = 0x00;\n\n        /**\n         * Encryption enabled.\n         */\n        public static final byte ENCRYPT_ON = 0x01;\n\n        /**\n         * Encryption not supported.\n         */\n        public static final byte ENCRYPT_NOT_SUP = 0x02;\n\n        /**\n         * Encryption required.\n         */\n        public static final byte ENCRYPT_REQ = 0x03;\n\n        private final byte encryption;\n\n        /**\n         * Create a new {@link Encryption} token.\n         *\n         * @param encryption encryption capability flag.\n         */\n        public Encryption(byte encryption) {\n\n            super(TYPE, 1);\n\n            this.encryption = encryption;\n        }\n\n        /**\n         * Decode the {@link Encryption} token.\n         *\n         * @param toDecode the state to decode.\n         * @return the decoded {@link Encryption}.\n         */\n        public static Encryption decode(TokenDecodingState toDecode) {\n\n            return decode(toDecode,\n                length -> {\n                    if (length != 1) {\n                        throw ProtocolException.invalidTds(String.format(\"Invalid encryption length: %s\", length));\n                    }\n                },\n                (length, body) -> {\n\n                    byte encryption = Decode.asByte(body);\n\n                    return new Encryption(encryption);\n                });\n        }\n\n        public byte getEncryption() {\n            return this.encryption;\n        }\n\n        @Override\n        public void encodeStream(ByteBuf buffer) {\n            Encode.asByte(buffer, this.encryption);\n        }\n\n        /**\n         * Returns {@code true} if the subsequent communication requires a SSL handshake.\n         *\n         * @return {@code true} if the subsequent communication requires a SSL handshake.\n         */\n        public boolean requiresSslHandshake() {\n            return getEncryption() == Prelogin.Encryption.ENCRYPT_REQ || getEncryption() == Prelogin.Encryption.ENCRYPT_OFF\n                || getEncryption() == Prelogin.Encryption.ENCRYPT_ON;\n        }\n\n        /**\n         * Returns {@code true} if a SSL handshake is required to enable SSL for sending the Login packet only.\n         *\n         * @return {@code true} if a SSL handshake is required to enable SSL for sending the Login packet only.\n         */\n        public boolean requiresLoginSslHandshake() {\n            return getEncryption() == Prelogin.Encryption.ENCRYPT_OFF;\n        }\n\n        /**\n         * Returns {@code true} if a SSL handshake is required to enable SSL for the entire connection.\n         *\n         * @return {@code true} if a SSL handshake is required to enable SSL for the entire connection.\n         */\n        public boolean requiresConnectionSslHandshake() {\n            return getEncryption() == Encryption.ENCRYPT_ON || getEncryption() == Encryption.ENCRYPT_REQ;\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [encryption=\").append(this.encryption);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * Token that allows associating a client application Thread Id with the connection.\n     */\n    public static class ThreadId extends Token {\n\n        public static final byte TYPE = 0x03;\n\n        /**\n         * Client application thread id;\n         */\n        private final int threadId;\n\n        /**\n         * Create a new {@link ThreadId} given the application {@code threadId}.\n         *\n         * @param threadId application thread Id.\n         */\n        public ThreadId(int threadId) {\n\n            super(TYPE, 4);\n\n            this.threadId = threadId;\n        }\n\n        @Override\n        public void encodeStream(ByteBuf buffer) {\n            buffer.writeInt(this.threadId);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [threadId=\").append(this.threadId);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * TraceId token that allows associating a connectionId/activityId with the connection.\n     */\n    public static class TraceId extends Token {\n\n        /**\n         * Client application trace id (for debugging purposes).\n         */\n        @Nullable\n        private final UUID connectionId;\n\n        /**\n         * Client application activity id (for debugging purposes).\n         */\n        @Nullable\n        private final UUID activityId;\n\n        /**\n         * Client application activity sequence (for debugging purposes).\n         */\n        private final int activitySequence;\n\n        /**\n         * Create a {@link TraceId} to associate trace Ids with the connection.\n         *\n         * @param connectionId     can be {@code null}.\n         * @param activityId       can be {@code null}.\n         * @param activitySequence can be {@code null}.\n         */\n        public TraceId(@Nullable UUID connectionId, @Nullable UUID activityId, int activitySequence) {\n\n            super(0x05, 36);\n\n            this.connectionId = connectionId;\n            this.activityId = activityId;\n            this.activitySequence = activitySequence;\n        }\n\n        @Override\n        public void encodeStream(ByteBuf buffer) {\n\n            if (this.connectionId != null) {\n                buffer.writeLong(this.connectionId.getMostSignificantBits());\n                buffer.writeLong(this.connectionId.getLeastSignificantBits());\n            } else {\n                buffer.writeLong(0);\n                buffer.writeLong(0);\n            }\n\n            if (this.activityId != null) {\n                buffer.writeLong(this.activityId.getMostSignificantBits());\n                buffer.writeLong(this.activityId.getLeastSignificantBits());\n            } else {\n                buffer.writeLong(0);\n                buffer.writeLong(0);\n            }\n\n            Encode.intBigEndian(buffer, this.activitySequence);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [connectionId=\").append(this.connectionId);\n            sb.append(\", activityId=\").append(this.activityId);\n            sb.append(\", activitySequence=\").append(this.activitySequence);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * Token placeholder that consumes unknown tokens.\n     */\n    public static class UnknownToken extends Token {\n\n        UnknownToken(int type, int length) {\n            super(type, length);\n        }\n\n        /**\n         * Decode the unknown token.\n         *\n         * @param type     feature type.\n         * @param toDecode decoding state.\n         * @return the {@link UnknownToken}.\n         */\n        public static UnknownToken decode(byte type, TokenDecodingState toDecode) {\n\n            return decode(toDecode, LengthValidator.ignore(), (length, body) -> {\n                return new UnknownToken(type, length);\n            });\n        }\n\n        @Override\n        public void encodeStream(ByteBuf buffer) {\n        }\n\n        @Override\n        public String toString() {\n            return getClass().getSimpleName();\n        }\n\n    }\n\n    /**\n     * Function to apply decoding.\n     *\n     * @param <T>\n     */\n    @FunctionalInterface\n    interface DecodeFunction<T> {\n\n        T decode(short length, ByteBuf buffer);\n\n    }\n\n    /**\n     * Length validator.\n     *\n     * @param <T>\n     */\n    @FunctionalInterface\n    interface LengthValidator {\n\n        LengthValidator IGNORE = length -> {\n        };\n\n        /**\n         * Validate the token data {@code length}.\n         *\n         * @param length token length.\n         * @throws ProtocolException if the length is invalid.\n         */\n        void validate(short length);\n\n        /**\n         * Returns a {@link LengthValidator} that ignores the length.\n         *\n         * @return {@link LengthValidator} that ignores the length.\n         */\n        static LengthValidator ignore() {\n            return IGNORE;\n        }\n\n    }\n\n    /**\n     * Decoding state for Token Stream decoding using positional data length and positional index data reading.\n     */\n    static class TokenDecodingState {\n\n        ByteBuf buffer;\n\n        int initialReaderIndex;\n\n        int readPositionOffset;\n\n        TokenDecodingState(ByteBuf buffer) {\n            this.initialReaderIndex = buffer.readerIndex();\n            this.buffer = buffer;\n        }\n\n        public static TokenDecodingState create(ByteBuf byteBuf) {\n            return new TokenDecodingState(byteBuf);\n        }\n\n        public boolean canDecode() {\n            return this.buffer.readableBytes() > 0;\n        }\n\n        /**\n         * Callback to update the state after reading.\n         */\n        void afterTokenDecoded() {\n            this.readPositionOffset = this.buffer.readerIndex() - this.initialReaderIndex;\n        }\n\n        /**\n         * Read data at {@code position} of {@code length}.\n         *\n         * @param position position index within the entire buffer.\n         * @param length   bytes to read.\n         * @return the data bytes.\n         */\n        ByteBuf readBody(int position, short length) {\n\n            this.buffer.skipBytes(position - 5 /* type 1 byte, position 2 byte, length 2 byte */ - this.readPositionOffset);\n\n            ByteBuf data = this.buffer.alloc().buffer(length);\n            this.buffer.readBytes(data, length);\n\n            return data;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/ReturnStatus.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Return Status token.\n *\n * @author Mark Paluch\n */\npublic class ReturnStatus extends AbstractDataToken {\n\n    private static final ReturnStatus[] CACHED = new ReturnStatus[128];\n\n    static {\n\n        for (int i = 0; i < CACHED.length; i++) {\n            CACHED[i] = new ReturnStatus(i);\n        }\n    }\n\n    public static final byte TYPE = (byte) 0x79;\n\n    private final int status;\n\n    private ReturnStatus(int status) {\n\n        super(TYPE);\n        this.status = status;\n    }\n\n    /**\n     * Creates a new {@link ReturnStatus}\n     *\n     * @param status the status value.\n     * @return the {@link ReturnStatus}.\n     */\n    public static ReturnStatus create(int status) {\n\n        if (status >= 0 && status < CACHED.length) {\n            return CACHED[status];\n        }\n\n        return new ReturnStatus(status);\n    }\n\n    /**\n     * Decode a {@link ReturnStatus}.\n     *\n     * @param buffer the data buffer.\n     * @return the decoded {@link ReturnStatus}.\n     */\n    public static ReturnStatus decode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        return ReturnStatus.create(Decode.asLong(buffer));\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into a {@link ReturnStatus}.\n     *\n     * @param buffer the data buffer.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a {@link ReturnStatus}.\n     */\n    public static boolean canDecode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        return buffer.readableBytes() >= 5;\n    }\n\n    public int getStatus() {\n        return this.status;\n    }\n\n    @Override\n    public byte getType() {\n        return TYPE;\n    }\n\n    @Override\n    public String getName() {\n        return \"RETURNSTATUS\";\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [status=\").append(this.status);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/ReturnValue.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.util.AbstractReferenceCounted;\nimport io.netty.util.ReferenceCounted;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.codec.Decodable;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\n/**\n * A returned value from a RPC call.\n *\n * @author Mark Paluch\n * @see RpcDirection#OUT\n */\npublic class ReturnValue extends AbstractReferenceCounted implements DataToken {\n\n    public static final byte TYPE = (byte) 0xAC;\n\n    /**\n     * Indicates the ordinal position of the output parameter in the original RPC call. Large Object output parameters are reordered to appear at the end of the stream. First the group of small\n     * parameters is sent, followed by the group of large output parameters. There is no reordering within the groups.\n     */\n    private final int ordinal;\n\n    /**\n     * The parameter name.\n     */\n    @Nullable\n    private final String parameterName;\n\n    private final byte status;\n\n    private final TypeInformation type;\n\n    private final ByteBuf value;\n\n    /**\n     * Creates a new {@link ReturnValue}.\n     *\n     * @param ordinal       the ordinal position of the output parameter in the original RPC call.\n     * @param parameterName the parameter name.\n     * @param status        indicator whether the value is a {@literal OUT} value or a UDF.\n     * @param type          type descriptor of this value.\n     * @param value         the actual value.\n     */\n    public ReturnValue(int ordinal, @Nullable String parameterName, int status, TypeInformation type, ByteBuf value) {\n        this(ordinal, parameterName, (byte) status, type, value);\n    }\n\n    /**\n     * Creates a new {@link ReturnValue}.\n     *\n     * @param ordinal       the ordinal position of the output parameter in the original RPC call.\n     * @param parameterName the parameter name.\n     * @param status        indicator whether the value is a {@literal OUT} value or a UDF.\n     * @param type          type descriptor of this value.\n     * @param value         the actual value.\n     */\n    public ReturnValue(int ordinal, @Nullable String parameterName, byte status, TypeInformation type, ByteBuf value) {\n\n        super();\n\n        this.ordinal = ordinal;\n        this.parameterName = parameterName;\n        this.status = status;\n        this.type = type;\n        this.value = value;\n    }\n\n    /**\n     * Decode a {@link RowToken}.\n     *\n     * @param buffer              the data buffer.\n     * @param encryptionSupported whether encryption is supported.\n     * @return the {@link ReturnValue}.\n     */\n    public static ReturnValue decode(ByteBuf buffer, boolean encryptionSupported) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        int ordinal = Decode.uShort(buffer);\n        String name = Decode.unicodeBString(buffer);\n        byte status = Decode.asByte(buffer);\n        TypeInformation type = TypeInformation.decode(buffer, true);\n\n        // Preserve length for Codecs\n        int beforeLengthDescriptor = buffer.readerIndex();\n        Length length = Length.decode(buffer, type);\n\n        int descriptorLength = buffer.readerIndex() - beforeLengthDescriptor;\n        buffer.readerIndex(beforeLengthDescriptor);\n\n        ByteBuf value = buffer.readRetainedSlice(descriptorLength + length.getLength());\n\n        return new ReturnValue(ordinal, name, status, type, value);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link ReturnValue}.\n     *\n     * @param buffer              the data buffer.\n     * @param encryptionSupported whether encryption is supported.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a {@link ReturnValue}.\n     */\n    public static boolean canDecode(ByteBuf buffer, boolean encryptionSupported) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        int readerIndex = buffer.readerIndex();\n\n        try {\n\n            int requiredLength = 3;\n            if (buffer.readableBytes() >= requiredLength) {\n\n                buffer.skipBytes(2);\n                int nameLength = Decode.asByte(buffer);\n\n                if (buffer.readableBytes() < (nameLength * 2) + /* status */ 1) {\n                    return false;\n                }\n\n                buffer.skipBytes((nameLength * 2) + 1);\n\n                if (!TypeInformation.canDecode(buffer, true)) {\n                    return false;\n                }\n\n                TypeInformation type = TypeInformation.decode(buffer, true);\n\n                if (!Length.canDecode(buffer, type)) {\n                    return false;\n                }\n\n                Length length = Length.decode(buffer, type);\n\n                if (buffer.readableBytes() >= length.getLength()) {\n                    return true;\n                }\n            }\n        } finally {\n            buffer.readerIndex(readerIndex);\n        }\n\n        return false;\n    }\n\n    /**\n     * Check whether the {@link Message} is a {@link ReturnValue} that matches the parameter {@literal name}.\n     *\n     * @param message the message.\n     * @param name    the parameter name.\n     * @return {@code true} if  the {@link Message} is a {@link ReturnValue} that matches the parameter {@literal name}.\n     */\n    public static boolean matches(Message message, String name) {\n\n        Assert.requireNonNull(message, \"Message must not be null\");\n        Assert.requireNonNull(name, \"Name must not be null\");\n\n        return message instanceof ReturnValue && name.equals(((ReturnValue) message).getParameterName());\n    }\n\n    /**\n     * Check whether the {@link Message} is a {@link ReturnValue} that matches the parameter {@literal ordinal}.\n     *\n     * @param message the message.\n     * @param ordinal the parameter ordinal.\n     * @return {@code true} if  the {@link Message} is a {@link ReturnValue} that matches the parameter {@literal ordinal}.\n     */\n    public static boolean matches(Message message, int ordinal) {\n\n        Assert.requireNonNull(message, \"Message must not be null\");\n\n        return message instanceof ReturnValue && ordinal == ((ReturnValue) message).getOrdinal();\n    }\n\n    public int getOrdinal() {\n        return this.ordinal;\n    }\n\n    @Nullable\n    public String getParameterName() {\n        return this.parameterName;\n    }\n\n    public TypeInformation getValueType() {\n        return this.type;\n    }\n\n    public byte getStatus() {\n        return this.status;\n    }\n\n    public ByteBuf getValue() {\n        return this.value;\n    }\n\n    /**\n     * Create a {@link Decodable} from this {@link ReturnValue} to allow decoding.\n     *\n     * @return the {@link Decodable}.\n     * @see Codecs#decode(ByteBuf, Decodable, Class)\n     */\n    public Decodable asDecodable() {\n\n        return new Decodable() {\n\n            @Override\n            public TypeInformation getType() {\n                return getValueType();\n            }\n\n            @Override\n            public String getName() {\n                return getParameterName() == null ? \"\" : getParameterName();\n            }\n        };\n    }\n\n    @Override\n    public byte getType() {\n        return TYPE;\n    }\n\n    @Override\n    public String getName() {\n        return \"RETURNVALUE\";\n    }\n\n    @Override\n    public ReferenceCounted touch(Object hint) {\n        this.value.touch(hint);\n        return this;\n    }\n\n    @Override\n    protected void deallocate() {\n        this.value.release();\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [ordinal=\").append(this.ordinal);\n        sb.append(\", parameterName='\").append(this.parameterName).append('\\'');\n        sb.append(\", value=\").append(this.value);\n        sb.append(\", type=\").append(this.type);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/RowToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.CompositeByteBuf;\nimport io.netty.util.AbstractReferenceCounted;\nimport io.netty.util.ReferenceCountUtil;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.PlpLength;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.Buffer;\n\n/**\n * Row token message containing row bytes.\n * Extends {@link AbstractReferenceCounted} to release associated {@link ByteBuf}s once the row is de-allocated.\n *\n * <p><strong>Note:</strong> PLP values are aggregated in a single {@link ByteBuf} and not yet streamed. This is to be fixed.\n *\n * @author Mark Paluch\n */\npublic class RowToken extends AbstractReferenceCounted implements DataToken {\n\n    public static final byte TYPE = (byte) 0xD1;\n\n    private final ByteBuf[] data;\n\n    /**\n     * Creates a {@link RowToken}.\n     *\n     * @param data the row data.\n     */\n    RowToken(ByteBuf[] data) {\n        this.data = data;\n    }\n\n    /**\n     * Decode a {@link RowToken}.\n     *\n     * @param buffer  the data buffer.\n     * @param columns column descriptors.\n     * @return the {@link RowToken}.\n     */\n    public static RowToken decode(ByteBuf buffer, Column[] columns) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n        Assert.requireNonNull(columns, \"List of Columns must not be null\");\n\n        return doDecode(buffer, columns);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link RowToken}.\n     *\n     * @param buffer  the data buffer.\n     * @param columns column descriptors.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a row.\n     */\n    public static boolean canDecode(ByteBuf buffer, Column[] columns) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n        Assert.requireNonNull(columns, \"List of Columns must not be null\");\n\n        int readerIndex = buffer.readerIndex();\n\n        try {\n\n            for (Column column : columns) {\n\n                if (!canDecodeColumn(buffer, column)) {\n                    return false;\n                }\n            }\n\n            return true;\n        } finally {\n            buffer.readerIndex(readerIndex);\n        }\n    }\n\n    static boolean canDecodeColumn(ByteBuf buffer, Column column) {\n\n        if (column.getType().getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n            return canDecodePlp(buffer, column);\n        }\n\n        return doCanDecode(buffer, column);\n    }\n\n    /**\n     * Returns whether the {@link Buffer} with a scalar size can be decoded.\n     *\n     * @param buffer\n     * @param column\n     * @return\n     */\n    private static boolean doCanDecode(ByteBuf buffer, Column column) {\n\n        if (!Length.canDecode(buffer, column.getType())) {\n            return false;\n        }\n\n        int startRead = buffer.readerIndex();\n        Length length = Length.decode(buffer, column.getType());\n        int endRead = buffer.readerIndex();\n\n        int descriptorLength = endRead - startRead;\n        int dataLength = descriptorLength + length.getLength();\n        int adjusted = dataLength - descriptorLength;\n\n        if (buffer.readableBytes() >= adjusted) {\n            buffer.skipBytes(adjusted);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Returns whether a PLP stream can be decoded where can be decoded means that we have received at least the PLP length header.\n     *\n     * @param buffer data buffer.\n     * @param column the related column.\n     * @return {@code true} if the PLP sream can be decoded.\n     * @see LengthStrategy#PARTLENTYPE\n     */\n    private static boolean canDecodePlp(ByteBuf buffer, Column column) {\n\n        if (!PlpLength.canDecode(buffer, column.getType())) {\n            return false;\n        }\n\n        PlpLength totalLength = PlpLength.decode(buffer, column.getType());\n\n        if (totalLength.isNull()) {\n            return true;\n        }\n\n        while (true) {\n\n            if (!Length.canDecode(buffer, column.getType())) {\n                return false;\n            }\n\n            Length chunkLength = Length.decode(buffer, column.getType());\n\n            if (chunkLength.getLength() == 0) {\n                return true;\n            }\n\n            if (buffer.readableBytes() >= chunkLength.getLength()) {\n                buffer.skipBytes(chunkLength.getLength());\n            } else {\n                return false;\n            }\n        }\n    }\n\n    private static RowToken doDecode(ByteBuf buffer, Column[] columns) {\n\n        ByteBuf[] data = new ByteBuf[columns.length];\n\n        for (int i = 0; i < columns.length; i++) {\n            data[i] = decodeColumnData(buffer, columns[i]);\n        }\n\n        return new RowToken(data);\n    }\n\n    /**\n     * Decode a {@link ByteBuf data buffer} for a single {@link Column}.\n     *\n     * @param buffer the data buffer.\n     * @param column the column.\n     * @return\n     */\n    @Nullable\n    static ByteBuf decodeColumnData(ByteBuf buffer, Column column) {\n\n        if (column.getType().getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n            buffer.markReaderIndex();\n            return doDecodePlp(buffer, column);\n        } else {\n            return doDecode(buffer, column);\n        }\n    }\n\n    /**\n     * Decode a scalar length value. Returns {@code null} if {@link Length#isNull()}.\n     *\n     * @param buffer the data buffer.\n     * @param column the column.\n     * @return\n     */\n    @Nullable\n    private static ByteBuf doDecode(ByteBuf buffer, Column column) {\n\n        int startRead = buffer.readerIndex();\n        Length length = Length.decode(buffer, column.getType());\n\n        if (length.isNull()) {\n            return null;\n        }\n\n        int endRead = buffer.readerIndex();\n        int descriptorLength = endRead - startRead;\n\n        buffer.readerIndex(startRead);\n        return buffer.readRetainedSlice(descriptorLength + length.getLength());\n    }\n\n    /**\n     * Decode a PLP stream value. Returns {@code null} if {@link Length#isNull()}. The decoded value contains an entire PLP token stream with chunk headers.\n     *\n     * @param buffer the data buffer.\n     * @param column the column.\n     * @return\n     */\n    @Nullable\n    private static ByteBuf doDecodePlp(ByteBuf buffer, Column column) {\n\n        PlpLength totalLength = PlpLength.decode(buffer, column.getType());\n\n        if (totalLength.isNull()) {\n            return null;\n        }\n\n        CompositeByteBuf plpData = buffer.alloc().compositeBuffer();\n        ByteBuf length = buffer.alloc().buffer(8);\n        totalLength.encode(length);\n\n        plpData.addComponent(true, length);\n\n        while (true) {\n\n            Length chunkLength = Length.decode(buffer, column.getType());\n\n            if (chunkLength.getLength() == 0) {\n                break;\n            }\n\n            length = buffer.alloc().buffer(4);\n            chunkLength.encode(length, column.getType());\n\n            plpData.addComponent(true, length);\n            plpData.addComponent(true, buffer.readRetainedSlice(chunkLength.getLength()));\n        }\n\n        return plpData;\n    }\n\n    /**\n     * Returns the {@link ByteBuf data} for the column at {@code index}.\n     *\n     * @param index the column {@code index}.\n     * @return the data buffer. Can be {@code null} if indicated by null-bit compression.\n     */\n    @Nullable\n    public ByteBuf getColumnData(int index) {\n        return this.data[index];\n    }\n\n    @Override\n    public byte getType() {\n        return TYPE;\n    }\n\n    @Override\n    public String getName() {\n        return \"ROW\";\n    }\n\n    @Override\n    public RowToken touch(Object hint) {\n        return this;\n    }\n\n    @Override\n    protected void deallocate() {\n\n        for (ByteBuf datum : this.data) {\n            ReferenceCountUtil.release(datum);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/RpcRequest.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.CompositeByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.codec.Encoded;\nimport io.r2dbc.mssql.codec.PlpEncoded;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.codec.RpcEncoding;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsPackets;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.util.Assert;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * RPC request to invoke stored procedures.\n *\n * @author Mark Paluch\n */\npublic final class RpcRequest implements ClientMessage, TokenStream {\n\n    static final HeaderOptions HEADER = HeaderOptions.create(Type.RPC, Status.empty());\n\n    /**\n     * Requests positioned updates. This procedure performs operations on one or more rows within a cursor's fetch buffer.\n     */\n    public static final short Sp_Cursor = 1;\n\n    /**\n     * Opens a cursor. sp_cursoropen defines the SQL statement associated with the cursor and cursor options, and then populates the cursor.\n     */\n    public static final short Sp_CursorOpen = 2;\n\n    /**\n     * Compiles the cursor statement or batch into an execution plan, but does not create the cursor. The compiled statement can later be used by {@link #Sp_CursorExecute}.\n     */\n    public static final short Sp_CursorPrepare = 3;\n\n    /**\n     * Creates and populates a cursor based upon the execution plan created by {@link #Sp_CursorPrepare}. This procedure, coupled with {@link #Sp_CursorPrepare}, has the same function as\n     * {@link #Sp_CursorOpen}, but is split into two phases.\n     */\n    public static final short Sp_CursorExecute = 4;\n\n    /**\n     * Compiles a plan for the submitted cursor statement or batch, then creates and populates the cursor. sp_cursorprepexec combines the functions of {@link #Sp_CursorPrepare} and\n     * {@link #Sp_CursorExecute}.\n     */\n    public static final short Sp_CursorPrepExec = 5;\n\n    /**\n     * Discards the execution plan developed in the sp_cursorprepare stored procedure.\n     */\n    public static final short Sp_CursorUnprepare = 6;\n\n    /**\n     * Fetches a buffer of one or more rows from the database. The group of rows in this buffer is called the cursor's fetch buffer.\n     */\n    public static final short Sp_CursorFetch = 7;\n\n    /**\n     * Sets cursor options or returns cursor information created by the {@link #Sp_CursorOpen} stored procedure.\n     */\n    public static final short Sp_CursorOption = 8;\n\n    /**\n     * Closes and de-allocates the cursor, as well as releases all associated resources; that is, it drops the temporary table used in support of {@literal KEYSET} or {@literal STATIC} cursor.\n     */\n    public static final short Sp_CursorClose = 9;\n\n    /**\n     * Executes a Transact-SQL statement or batch that can be reused many times, or one that has been built dynamically.\n     */\n    public static final short Sp_ExecuteSql = 10;\n\n    /**\n     * Prepares a parameterized Transact-SQL statement and returns a statement handle for execution.\n     */\n    public static final short Sp_Prepare = 11;\n\n    /**\n     * Executes a prepared Transact-SQL statement using a specified handle and optional parameter value.\n     */\n    public static final short Sp_Execute = 12;\n\n    /**\n     * Prepares and executes a parameterized Transact-SQL statement. {@link #Sp_PrepExec} combines the functions of {@link #Sp_Prepare} and {@link #Sp_Execute}.\n     */\n    public static final short Sp_PrepExec = 13;\n\n    /**\n     * Prepares and executes a parameterized stored procedure call that has been specified using an RPC identifier.\n     */\n    public static final short Sp_PrepExecRpc = 14;\n\n    /**\n     * Discards the execution plan created by the sp_prepare stored procedure.\n     */\n    public static final short Sp_Unprepare = 15;\n\n    private static final short PROC_ID_SWITCH = (short) 0xFFFF;\n\n    private final AllHeaders allHeaders;\n\n    @Nullable\n    private final String procName;\n\n    private final Integer procId;\n\n    private final OptionFlags optionFlags;\n\n    private final byte statusFlags;\n\n    private final List<ParameterDescriptor> parameterDescriptors;\n\n    private RpcRequest(AllHeaders allHeaders, @Nullable String procName, @Nullable Integer procId, OptionFlags optionFlags, byte statusFlags, List<ParameterDescriptor> parameterDescriptors) {\n\n        this.allHeaders = Assert.requireNonNull(allHeaders, \"AllHeaders must not be null\");\n        this.procName = procName;\n        this.procId = procId;\n        this.optionFlags = Assert.requireNonNull(optionFlags, \"Option flags must not be null\");\n        this.statusFlags = statusFlags;\n        this.parameterDescriptors = parameterDescriptors;\n    }\n\n    /**\n     * Creates a new {@link Builder} to build a {@link RpcRequest}.\n     *\n     * @return a new {@link Builder} to build a {@link RpcRequest}.\n     */\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    @Override\n    public Publisher<TdsFragment> encode(ByteBufAllocator allocator, int packetSize) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n\n        return Flux.defer(() -> {\n\n            int name = 2 + (this.procName != null ? this.procName.length() * 2 : 0);\n            int length = 4 + name + this.allHeaders.getLength();\n\n            for (ParameterDescriptor descriptor : this.parameterDescriptors) {\n                length += descriptor.estimateLength();\n            }\n\n            ByteBuf scalarBuffer = allocator.buffer(length);\n\n            encodeHeader(scalarBuffer);\n\n            boolean hasPlpSegments = false;\n\n            for (ParameterDescriptor descriptor : this.parameterDescriptors) {\n                if (descriptor instanceof EncodedRpcParameter) {\n                    if (((EncodedRpcParameter) descriptor).getValue() instanceof PlpEncoded) {\n                        hasPlpSegments = true;\n                    }\n                }\n            }\n\n            if (!hasPlpSegments) {\n\n                for (ParameterDescriptor descriptor : this.parameterDescriptors) {\n                    descriptor.encode(scalarBuffer);\n                }\n\n                return Flux.just(TdsPackets.create(HEADER, scalarBuffer));\n            }\n\n            AtomicReference<ByteBuf> firstBufferHolder = new AtomicReference<>(scalarBuffer);\n            AtomicBoolean first = new AtomicBoolean(true);\n            return Flux.fromIterable(this.parameterDescriptors).concatMap(it -> {\n\n                ByteBuf buffer = getByteBuf(firstBufferHolder, allocator, it);\n\n                if (it instanceof EncodedRpcParameter && ((EncodedRpcParameter) it).getValue() instanceof PlpEncoded) {\n\n                    EncodedRpcParameter parameter = (EncodedRpcParameter) it;\n                    PlpEncoded encoded = (PlpEncoded) parameter.getValue();\n\n                    parameter.encodeHeader(buffer);\n                    encoded.encodeHeader(buffer);\n\n                    AtomicReference<ByteBuf> firstChunk = new AtomicReference<>(buffer);\n\n                    Flux<ByteBuf> tdsFragments = encoded.chunked(() -> packetSize * 4, true).map(chunk -> {\n\n                        if (firstChunk.compareAndSet(buffer, null)) {\n\n                            CompositeByteBuf withInitialBuffer = allocator.compositeBuffer();\n\n                            withInitialBuffer.addComponent(true, buffer);\n                            withInitialBuffer.addComponent(true, chunk);\n\n                            return withInitialBuffer;\n                        }\n\n                        return chunk;\n                    });\n\n                    return tdsFragments.concatWith(Mono.create(sink -> {\n\n                        ByteBuf terminator = allocator.buffer();\n                        Encode.asInt(terminator, 0);\n\n                        sink.success(terminator);\n                    }));\n                }\n\n                it.encode(buffer);\n                return Mono.just(buffer);\n            }, 1).map(buf -> {\n\n                if (first.compareAndSet(true, false)) {\n                    return TdsPackets.first(HEADER, buf);\n                }\n\n                return TdsPackets.create(buf);\n\n            }).concatWith(Mono.create(sink -> {\n\n                ByteBuf firstBuffer = firstBufferHolder.getAndSet(null);\n\n                if (firstBuffer != null) {\n                    sink.success(TdsPackets.last(firstBuffer));\n                    return;\n                }\n\n                sink.success(TdsPackets.last(Unpooled.EMPTY_BUFFER));\n            }));\n        });\n    }\n\n    private ByteBuf getByteBuf(AtomicReference<ByteBuf> firstBufferHolder, ByteBufAllocator allocator, ParameterDescriptor it) {\n\n        ByteBuf firstBuffer = firstBufferHolder.getAndSet(null);\n\n        if (firstBuffer != null) {\n            return firstBuffer;\n        }\n\n        int estimatedLength = it.estimateLength();\n        return estimatedLength > 0 ? allocator.buffer(estimatedLength) : allocator.buffer();\n    }\n\n    private void encodeHeader(ByteBuf buffer) {\n\n        this.allHeaders.encode(buffer);\n\n        if (this.procId != null) {\n            Encode.uShort(buffer, PROC_ID_SWITCH);\n            Encode.uShort(buffer, this.procId);\n        } else {\n            Assert.state(this.procName != null, \"ProcName must not be null if ProcId is not set.\");\n            Encode.unicodeStream(buffer, this.procName);\n        }\n\n        Encode.asByte(buffer, this.optionFlags.getValue());\n        Encode.asByte(buffer, this.statusFlags);\n    }\n\n    @Nullable\n    public String getProcName() {\n        return this.procName;\n    }\n\n    public Integer getProcId() {\n        return this.procId;\n    }\n\n    @Override\n    public String getName() {\n        return \"RPCRequest\";\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof RpcRequest)) {\n            return false;\n        }\n        RpcRequest that = (RpcRequest) o;\n        return this.statusFlags == that.statusFlags &&\n            Objects.equals(this.allHeaders, that.allHeaders) &&\n            Objects.equals(this.procName, that.procName) &&\n            Objects.equals(this.procId, that.procId) &&\n            Objects.equals(this.optionFlags, that.optionFlags) &&\n            Objects.equals(this.parameterDescriptors, that.parameterDescriptors);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.allHeaders, this.procName, this.procId, this.optionFlags, this.statusFlags, this.parameterDescriptors);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getName());\n        sb.append(\" [procName='\").append(this.procName).append('\\'');\n        sb.append(\", procId=\").append(this.procId);\n        sb.append(\", optionFlags=\").append(this.optionFlags);\n        sb.append(\", statusFlags=\").append(this.statusFlags);\n        sb.append(\", parameterDescriptors=\").append(this.parameterDescriptors);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    /**\n     * Builder for {@link RpcRequest}.\n     */\n    public final static class Builder {\n\n        @Nullable\n        private String procName;\n\n        private Integer procId;\n\n        private OptionFlags optionFlags = OptionFlags.empty();\n\n        private byte statusFlags;\n\n        private TransactionDescriptor transactionDescriptor;\n\n        private final List<ParameterDescriptor> parameterDescriptors = new ArrayList<>();\n\n        /**\n         * Configure a procedure name.\n         *\n         * @param procName the name of the stored procedure to call.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withProcName(String procName) {\n\n            Assert.requireNonNull(procName, \"ProcName must not be null\");\n\n            this.procId = null;\n            this.procName = procName;\n\n            return this;\n        }\n\n        /**\n         * Configure a procedureId to call a pre-defined stored procedure.\n         *\n         * @param id the stored procedure Id. See {@link RpcRequest#Sp_Cursor} and other {@literal Sp_} constants.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withProcId(int id) {\n\n            this.procName = null;\n            this.procId = id;\n\n            return this;\n        }\n\n        /**\n         * Add a {@link String} parameter to this RPC call.\n         *\n         * @param direction RPC parameter direction (in/out).\n         * @param collation parameter encoding.\n         * @param value     the parameter value, can be {@code null}.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link RpcDirection} or {@link Collation} is {@code null}.\n         */\n        public Builder withParameter(RpcDirection direction, Collation collation, @Nullable String value) {\n\n            Assert.requireNonNull(direction, \"RPC direction (in/out) must not be null\");\n            Assert.requireNonNull(collation, \"Collation must not be null\");\n\n            this.parameterDescriptors.add(new RpcString(direction, null, collation, value));\n\n            return this;\n        }\n\n        /**\n         * Add a {@link Integer} parameter to this RPC call.\n         *\n         * @param direction RPC parameter direction (in/out).\n         * @param value     the parameter value, can be {@code null}.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link RpcDirection} is {@code null}.\n         */\n        public Builder withParameter(RpcDirection direction, @Nullable Integer value) {\n\n            Assert.requireNonNull(direction, \"RPC direction (in/out) must not be null\");\n\n            this.parameterDescriptors.add(new RpcInt(direction, null, value));\n\n            return this;\n        }\n\n        /**\n         * Add an {@link Encoded} parameter to this RPC call.\n         *\n         * @param direction RPC parameter direction (in/out).\n         * @param value     the parameter value.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link RpcDirection} or {@link Encoded} is {@code null}.\n         */\n        public Builder withParameter(RpcDirection direction, Encoded value) {\n\n            Assert.requireNonNull(direction, \"RPC direction (in/out) must not be null\");\n            Assert.requireNonNull(value, \"Encoded parameter name must not be null\");\n\n            this.parameterDescriptors.add(new EncodedRpcParameter(direction, null, value));\n\n            return this;\n        }\n\n        /**\n         * Add a {@link String} parameter to this RPC call.\n         *\n         * @param direction RPC parameter direction (in/out).\n         * @param name      the parameter name\n         * @param collation parameter encoding.\n         * @param value     the parameter value, can be {@code null}.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link RpcDirection}, {@code name} or {@link Collation} is {@code null}.\n         */\n        public Builder withNamedParameter(RpcDirection direction, String name, Collation collation, @Nullable String value) {\n\n            Assert.requireNonNull(direction, \"RPC direction (in/out) must not be null\");\n            Assert.requireNonNull(name, \"Parameter name must not be null\");\n            Assert.requireNonNull(collation, \"Collation must not be null\");\n\n            this.parameterDescriptors.add(new RpcString(direction, name, collation, value));\n\n            return this;\n        }\n\n        /**\n         * Add a {@link Integer} parameter to this RPC call.\n         *\n         * @param direction RPC parameter direction (in/out).\n         * @param name      the parameter name\n         * @param value     the parameter value, can be {@code null}.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link RpcDirection} or {@code name} is {@code null}.\n         */\n        public Builder withNamedParameter(RpcDirection direction, String name, @Nullable Integer value) {\n\n            Assert.requireNonNull(direction, \"RPC direction (in/out) must not be null\");\n            Assert.requireNonNull(name, \"Parameter name must not be null\");\n\n            this.parameterDescriptors.add(new RpcInt(direction, name, value));\n\n            return this;\n        }\n\n        /**\n         * Add an {@link Encoded} parameter to this RPC call.\n         *\n         * @param direction RPC parameter direction (in/out).\n         * @param name      the parameter name\n         * @param value     the parameter value.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link RpcDirection}, {@code name}, or {@link Encoded} is {@code null}.\n         */\n        public Builder withNamedParameter(RpcDirection direction, String name, Encoded value) {\n\n            Assert.requireNonNull(direction, \"RPC direction (in/out) must not be null\");\n            Assert.requireNonNull(name, \"Parameter name must not be null\");\n            Assert.requireNonNull(value, \"Encoded parameter name must not be null\");\n\n            this.parameterDescriptors.add(new EncodedRpcParameter(direction, name, value));\n\n            return this;\n        }\n\n        /**\n         * Configure a {@link TransactionDescriptor}.\n         *\n         * @param transactionDescriptor the transaction descriptor.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link TransactionDescriptor} is {@code null}.\n         */\n        public Builder withTransactionDescriptor(TransactionDescriptor transactionDescriptor) {\n\n            this.transactionDescriptor = Assert.requireNonNull(transactionDescriptor, \"TransactionDescriptor must not be null\");\n\n            return this;\n        }\n\n        /**\n         * Configure the {@link OptionFlags}.\n         *\n         * @param optionFlags the option flags to use.\n         * @return {@code this} {@link Builder}.\n         * @throws IllegalArgumentException when {@link OptionFlags} is {@code null}.\n         */\n        public Builder withOptionFlags(OptionFlags optionFlags) {\n\n            this.optionFlags = Assert.requireNonNull(optionFlags, \"OptionFlags must not be null\");\n\n            return this;\n        }\n\n        /**\n         * Build a {@link RpcRequest}.\n         *\n         * @return a new {@link RpcRequest}.\n         * @throws IllegalStateException when {@link TransactionDescriptor} or procedure name/id are not configured.\n         */\n        public RpcRequest build() {\n\n            Assert.state(this.transactionDescriptor != null, \"TransactionDescriptor is not configured\");\n            Assert.state(this.procName != null || this.procId != null, \"Either procedure name or procedure Id required\");\n\n            return new RpcRequest(AllHeaders.transactional(this.transactionDescriptor.toBytes(), 1), this.procName, this.procId, this.optionFlags, this.statusFlags,\n                new ArrayList<>(this.parameterDescriptors));\n        }\n\n    }\n\n    /**\n     * RPC option flags.\n     */\n    public final static class OptionFlags {\n\n        private static final OptionFlags EMPTY = new OptionFlags(0x00);\n\n        /**\n         * Recompile the called procedure.\n         */\n        static final byte RPC_OPTION_RECOMPILE = (byte) 0x01;\n\n        /**\n         * The server sends No Meta Data only if fNoMetadata is set to 1 in the request (i.e. suppressing Column Metadata).\n         */\n        static final byte RPC_OPTION_NO_METADATA = (byte) 0x02;\n\n        private final int optionByte;\n\n        private OptionFlags(int optionByte) {\n            this.optionByte = optionByte;\n        }\n\n        /**\n         * Creates an empty {@link OptionFlags}.\n         *\n         * @return a new {@link OptionFlags}.\n         */\n        public static OptionFlags empty() {\n            return EMPTY;\n        }\n\n        /**\n         * Enable procedure recompilation.\n         *\n         * @return new {@link OptionFlags} with the option applied.\n         */\n        public OptionFlags enableRecompile() {\n            return new OptionFlags(this.optionByte | RPC_OPTION_RECOMPILE);\n        }\n\n        /**\n         * Disable metadata.\n         *\n         * @return new {@link OptionFlags} with the option applied.\n         */\n        public OptionFlags disableMetadata() {\n            return new OptionFlags(this.optionByte | RPC_OPTION_NO_METADATA);\n        }\n\n        /**\n         * @return the combined option byte.\n         */\n        public byte getValue() {\n            return (byte) this.optionByte;\n        }\n\n    }\n\n    /**\n     * Abstract base class for RPC parameter implementations.\n     */\n    abstract static class ParameterDescriptor {\n\n        private final RpcDirection direction;\n\n        @Nullable\n        private final String name;\n\n        ParameterDescriptor(RpcDirection direction, @Nullable String name) {\n\n            this.direction = Assert.requireNonNull(direction, \"Direction must not be null\");\n            this.name = name;\n        }\n\n        /**\n         * Encode the parameter value.\n         *\n         * @param buffer the data buffer to use as encode target.\n         */\n        abstract void encode(ByteBuf buffer);\n\n        /**\n         * Estimate the encoded parameter length.\n         *\n         * @return the estimated parameter length in bytes.\n         */\n        abstract int estimateLength();\n\n        public RpcDirection getDirection() {\n            return this.direction;\n        }\n\n        @Nullable\n        public String getName() {\n            return this.name;\n        }\n\n    }\n\n    /**\n     * String RPC parameter.\n     */\n    static class RpcString extends ParameterDescriptor {\n\n        private final Collation collation;\n\n        @Nullable\n        private final String value;\n\n        RpcString(RpcDirection direction, @Nullable String name, Collation collation, @Nullable String value) {\n            super(direction, name);\n            this.value = value;\n            this.collation = collation;\n        }\n\n        @Override\n        void encode(ByteBuf buffer) {\n            RpcEncoding.encodeString(buffer, getName(), getDirection(), this.collation, this.value);\n        }\n\n        @Override\n        int estimateLength() {\n            return 16 + (this.value != null ? this.value.length() * 2 : 0);\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) {\n                return true;\n            }\n            if (!(o instanceof RpcString)) {\n                return false;\n            }\n            RpcString rpcString = (RpcString) o;\n            return Objects.equals(this.collation, rpcString.collation) &&\n                Objects.equals(this.value, rpcString.value);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(this.collation, this.value);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [name='\").append(getName()).append('\\'');\n            sb.append(\", value=\").append(this.value);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * Integer RPC parameter.\n     */\n    static class RpcInt extends ParameterDescriptor {\n\n        @Nullable\n        private final Integer value;\n\n        RpcInt(RpcDirection direction, @Nullable String name, @Nullable Integer value) {\n            super(direction, name);\n            this.value = value;\n        }\n\n        @Override\n        void encode(ByteBuf buffer) {\n            RpcEncoding.encodeInteger(buffer, getName(), getDirection(), this.value);\n        }\n\n        @Override\n        int estimateLength() {\n            return this.value != null ? 5 : 0;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) {\n                return true;\n            }\n            if (!(o instanceof RpcInt)) {\n                return false;\n            }\n            RpcInt rpcInt = (RpcInt) o;\n            return Objects.equals(this.value, rpcInt.value);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(this.value);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [name='\").append(getName()).append('\\'');\n            sb.append(\", value=\").append(this.value);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n    /**\n     * RPC parameter.\n     */\n    static class EncodedRpcParameter extends ParameterDescriptor {\n\n        private final Encoded value;\n\n        EncodedRpcParameter(RpcDirection direction, @Nullable String name, Encoded value) {\n            super(direction, name);\n            this.value = value;\n        }\n\n        public Encoded getValue() {\n            return this.value;\n        }\n\n        @Override\n        void encode(ByteBuf buffer) {\n            encodeHeader(buffer);\n            ByteBuf value = this.value.getValue();\n            buffer.writeBytes(value);\n            value.release();\n        }\n\n        void encodeHeader(ByteBuf buffer) {\n            RpcEncoding.encodeHeader(buffer, getName(), getDirection(), this.value.getDataType());\n        }\n\n        @Override\n        int estimateLength() {\n\n            int estimate = 2 + (getName() != null ? (getName().length() + 1) * 2 : 0);\n            estimate += this.value.estimateLength();\n\n            return estimate;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) {\n                return true;\n            }\n            if (!(o instanceof EncodedRpcParameter)) {\n                return false;\n            }\n            EncodedRpcParameter encodedRpcParameter = (EncodedRpcParameter) o;\n            return Objects.equals(this.value, encodedRpcParameter.value);\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(this.value);\n        }\n\n        @Override\n        public String toString() {\n            final StringBuffer sb = new StringBuffer();\n            sb.append(getClass().getSimpleName());\n            sb.append(\" [name='\").append(getName()).append('\\'');\n            sb.append(\", value=\").append(this.value);\n            sb.append(']');\n            return sb.toString();\n        }\n\n    }\n\n}\n\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/SqlBatch.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsPackets;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.Objects;\n\n/**\n * SQL batch token to execute simple SQL.\n *\n * @author Mark Paluch\n */\npublic final class SqlBatch implements ClientMessage, TokenStream {\n\n    private final HeaderOptions header;\n\n    private final AllHeaders allHeaders;\n\n    private final String sql;\n\n    /**\n     * Creates a new {@link SqlBatch} token.\n     *\n     * @param outstandingRequests   the number of outstanding requests.\n     * @param transactionDescriptor the transaction descriptor (8 byte).\n     * @param sql                   the SQL string.\n     */\n    private SqlBatch(int outstandingRequests, byte[] transactionDescriptor, String sql) {\n\n        Assert.requireNonNull(transactionDescriptor, \"Transaction descriptor must not be null\");\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n\n        this.header = HeaderOptions.create(Type.SQL_BATCH, Status.empty());\n        this.allHeaders = AllHeaders.transactional(transactionDescriptor, outstandingRequests);\n        this.sql = sql;\n    }\n\n    /**\n     * Creates a new {@link SqlBatch}.\n     *\n     * @param outstandingRequests   the number of outstanding requests.\n     * @param transactionDescriptor the transaction descriptor\n     * @param sql                   the SQL string.\n     * @return the {@link SqlBatch}.\n     */\n    public static SqlBatch create(int outstandingRequests, TransactionDescriptor transactionDescriptor, String sql) {\n\n        Assert.requireNonNull(transactionDescriptor, \"Transaction descriptor must not be null\");\n        Assert.requireNonNull(sql, \"SQL must not be null\");\n\n        return new SqlBatch(outstandingRequests, transactionDescriptor.toBytes(), sql);\n    }\n\n    @Override\n    public TdsFragment encode(ByteBufAllocator allocator, int packetSize) {\n\n        Assert.requireNonNull(allocator, \"ByteBufAllocator must not be null\");\n\n        int length = this.allHeaders.getLength() + (this.sql.length() * 2);\n\n        ByteBuf buffer = allocator.buffer(length);\n        encode(buffer);\n\n        return TdsPackets.create(this.header, buffer);\n    }\n\n    void encode(ByteBuf buffer) {\n\n        this.allHeaders.encode(buffer);\n        Encode.unicodeStream(buffer, this.sql);\n    }\n\n    public String getSql() {\n        return this.sql;\n    }\n\n    @Override\n    public String getName() {\n        return \"SQLBatch\";\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof SqlBatch)) {\n            return false;\n        }\n        SqlBatch batch = (SqlBatch) o;\n        return Objects.equals(this.allHeaders, batch.allHeaders) &&\n            Objects.equals(this.sql, batch.sql);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.allHeaders, this.sql);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getName());\n        sb.append(\" [sql=\\\"\").append(this.sql).append('\\\"');\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/TabnameToken.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Table name token. Used to send the table name to the client only when in browser mode or from cursors.\n *\n * @author Mark Paluch\n */\npublic class TabnameToken extends AbstractDataToken {\n\n    public static final byte TYPE = (byte) 0xA4;\n\n    private final List<Identifier> tableNames;\n\n    private TabnameToken(List<Identifier> tableNames) {\n\n        super(TYPE);\n        this.tableNames = tableNames;\n    }\n\n    /**\n     * Decode a {@link TabnameToken}.\n     *\n     * @param buffer the data buffer.\n     * @return the {@link TabnameToken}.\n     */\n    public static TabnameToken decode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        int length = Decode.uShort(buffer);\n\n        int readerIndex = buffer.readerIndex();\n\n        List<Identifier> tableNames = new ArrayList<>();\n\n        while (buffer.readerIndex() - readerIndex < length) {\n            tableNames.add(Identifier.decode(buffer));\n        }\n\n        return new TabnameToken(tableNames);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an entire {@link TabnameToken}.\n     *\n     * @param buffer the data buffer.\n     * @return {@code true} if the buffer contains sufficient data to entirely decode a {@link TabnameToken}.\n     */\n    public static boolean canDecode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        if (buffer.readableBytes() >= 5) {\n\n            Integer requiredLength = Decode.peekUShort(buffer);\n            return requiredLength != null && buffer.readableBytes() >= (requiredLength + /* length field */ 2);\n        }\n\n        return false;\n    }\n\n    public List<Identifier> getTableNames() {\n        return this.tableNames;\n    }\n\n    @Override\n    public String getName() {\n        return \"TABNAME\";\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getName());\n        sb.append(\" [names=\").append(this.tableNames);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/Tabular.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.core.publisher.SynchronousSink;\nimport reactor.util.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Predicate;\n\n/**\n * Tabular response.\n *\n * @author Mark Paluch\n * @see Type#TABULAR_RESULT\n */\npublic final class Tabular implements Message {\n\n    private final List<? extends DataToken> tokens;\n\n    private Tabular(List<? extends DataToken> tokens) {\n        this.tokens = tokens;\n    }\n\n    /**\n     * Creates a new {@link Tabular}.\n     *\n     * @param tokens the tokens.\n     * @return the tabular token.\n     */\n    public static Tabular create(DataToken... tokens) {\n\n        Assert.requireNonNull(tokens, \"Data tokens must not be null\");\n\n        return new Tabular(Arrays.asList(tokens));\n    }\n\n    /**\n     * Decode the {@link Tabular} response from a {@link ByteBuf}.\n     *\n     * @param buffer              must not be null.\n     * @param encryptionSupported whether encryption is supported.\n     * @return the decoded {@link Tabular} response {@link Message}.\n     */\n    public static Tabular decode(ByteBuf buffer, boolean encryptionSupported) {\n\n        Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n        return new Tabular(new TabularDecoder(encryptionSupported).decode(buffer));\n    }\n\n    /**\n     * Creates a new {@link TabularDecoder}.\n     *\n     * @param encryptionSupported {@code true} if table column encryption is supported.\n     * @return the decoder.\n     */\n    public static TabularDecoder createDecoder(boolean encryptionSupported) {\n        return new TabularDecoder(encryptionSupported);\n    }\n\n    /**\n     * Creates a new, stateful {@link DecodeFunction}.\n     *\n     * @param encryptionSupported {@code true} if table column encryption is supported.\n     * @return the decoder.\n     */\n    private static DecodeFunction decodeFunction(boolean encryptionSupported) {\n\n        AtomicReference<ColumnMetadataToken> columns = new AtomicReference<>();\n\n        return (type, buffer) -> {\n\n            if (type == EnvChangeToken.TYPE) {\n                return EnvChangeToken.canDecode(buffer) ? EnvChangeToken.decode(buffer) : DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == FeatureExtAckToken.TYPE) {\n                return FeatureExtAckToken.decode(buffer);\n            }\n\n            if (type == InfoToken.TYPE) {\n                return InfoToken.canDecode(buffer) ? InfoToken.decode(buffer) : DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == ErrorToken.TYPE) {\n                return ErrorToken.canDecode(buffer) ? ErrorToken.decode(buffer) : DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == LoginAckToken.TYPE) {\n                return LoginAckToken.decode(buffer);\n            }\n\n            if (type == ColumnMetadataToken.TYPE) {\n\n                if (!ColumnMetadataToken.canDecode(buffer, encryptionSupported)) {\n                    return DecodeFinished.UNABLE_TO_DECODE;\n                }\n\n                ColumnMetadataToken colMetadataToken = ColumnMetadataToken.decode(buffer, encryptionSupported);\n\n                if (columns.get() == null || colMetadataToken.hasColumns()) {\n                    columns.set(colMetadataToken);\n                }\n\n                return colMetadataToken;\n            }\n\n            if (type == ColInfoToken.TYPE) {\n                return ColInfoToken.canDecode(buffer) ? ColInfoToken.decode(buffer) : DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == TabnameToken.TYPE) {\n                return TabnameToken.canDecode(buffer) ? TabnameToken.decode(buffer) : DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == DoneToken.TYPE) {\n\n                if (DoneToken.canDecode(buffer)) {\n\n                    DoneToken decode = DoneToken.decode(buffer);\n\n                    if (!decode.hasMore()) {\n                        columns.set(null);\n                    }\n\n                    return decode;\n                }\n\n                return DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == DoneInProcToken.TYPE) {\n\n                if (DoneInProcToken.canDecode(buffer)) {\n                    return DoneInProcToken.decode(buffer);\n                }\n\n                return DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == DoneProcToken.TYPE) {\n\n                if (DoneProcToken.canDecode(buffer)) {\n                    return DoneProcToken.decode(buffer);\n                }\n\n                return DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == OrderToken.TYPE) {\n\n                if (!OrderToken.canDecode(buffer)) {\n                    return DecodeFinished.UNABLE_TO_DECODE;\n                }\n\n                return OrderToken.decode(buffer);\n            }\n\n            if (type == RowToken.TYPE) {\n\n                ColumnMetadataToken colMetadataToken = columns.get();\n\n                if (!RowToken.canDecode(buffer, colMetadataToken.getColumns())) {\n                    return DecodeFinished.UNABLE_TO_DECODE;\n                }\n\n                return RowToken.decode(buffer, colMetadataToken.getColumns());\n            }\n\n            if (type == NbcRowToken.TYPE) {\n\n                ColumnMetadataToken colMetadataToken = columns.get();\n\n                if (!NbcRowToken.canDecode(buffer, colMetadataToken.getColumns())) {\n                    return DecodeFinished.UNABLE_TO_DECODE;\n                }\n\n                return NbcRowToken.decode(buffer, colMetadataToken.getColumns());\n            }\n\n            if (type == ReturnStatus.TYPE) {\n                return ReturnStatus.canDecode(buffer) ? ReturnStatus.decode(buffer) : DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            if (type == ReturnValue.TYPE) {\n                return ReturnValue.canDecode(buffer, encryptionSupported) ? ReturnValue.decode(buffer, encryptionSupported) : DecodeFinished.UNABLE_TO_DECODE;\n            }\n\n            throw ProtocolException.invalidTds(String.format(\"Unable to decode unknown token type 0x%02X\", type));\n        };\n    }\n\n    /**\n     * @return the tokens.\n     */\n    public List<? extends DataToken> getTokens() {\n        return this.tokens;\n    }\n\n    /**\n     * Resolve a {@link Prelogin.Token} given its {@link Class type}.\n     *\n     * @param filter filter that the desired {@link DataToken} must match.\n     * @return the lookup result or {@code null} if no {@link DataToken} matches.\n     */\n    @Nullable\n    private DataToken findToken(Predicate<DataToken> filter) {\n\n        Assert.requireNonNull(filter, \"Filter must not be null\");\n\n        for (DataToken token : this.tokens) {\n            if (filter.test(token)) {\n                return token;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Find a {@link DataToken} by its {@link Class type} and a {@link Predicate}.\n     *\n     * @param tokenType type of the desired {@link DataToken}.\n     * @return the lookup result.\n     */\n    <T extends DataToken> Optional<T> getToken(Class<? extends T> tokenType) {\n\n        Assert.requireNonNull(tokenType, \"Token type must not be null\");\n\n        return Optional.ofNullable(findToken(tokenType::isInstance)).map(tokenType::cast);\n    }\n\n    /**\n     * Find a {@link DataToken} by its {@link Class type} and a {@link Predicate}.\n     *\n     * @param tokenType type of the desired {@link DataToken}.\n     * @param filter    filter that the desired {@link DataToken} must match.\n     * @return the lookup result.\n     */\n    <T extends DataToken> Optional<T> getToken(Class<? extends T> tokenType, Predicate<T> filter) {\n\n        Assert.requireNonNull(tokenType, \"Token type must not be null\");\n        Assert.requireNonNull(filter, \"Filter must not be null\");\n\n        Predicate<DataToken> predicate = tokenType::isInstance;\n        return Optional.ofNullable(findToken(predicate.and(dataToken -> filter.test(tokenType.cast(dataToken)))))\n            .map(tokenType::cast);\n    }\n\n    /**\n     * Find a {@link DataToken} by its {@link Class type} and a {@link Predicate}.\n     *\n     * @param tokenType type of the desired {@link DataToken}.\n     * @return\n     * @throws IllegalArgumentException if no token was found.\n     */\n    <T extends DataToken> T getRequiredToken(Class<? extends T> tokenType) {\n\n        return getToken(tokenType).orElseThrow(\n            () -> new IllegalArgumentException(String.format(\"No token of type [%s] available\", tokenType.getName())));\n    }\n\n    /**\n     * Find a {@link DataToken} by its {@link Class type} and a {@link Predicate}.\n     *\n     * @param tokenType type of the desired {@link DataToken}.\n     * @param filter    filter that the desired {@link DataToken} must match.\n     * @return\n     * @throws IllegalArgumentException if no token was found.\n     */\n    <T extends DataToken> T getRequiredToken(Class<? extends T> tokenType, Predicate<T> filter) {\n\n        return getToken(tokenType, filter).orElseThrow(\n            () -> new IllegalArgumentException(String.format(\"No token of type [%s] available\", tokenType.getName())));\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [tokens=\").append(this.tokens);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    /**\n     * Marker {@link DataToken} when decoding is finished.\n     */\n    enum DecodeFinished implements DataToken {\n\n        /**\n         * Decoding is finished.\n         */\n        FINISHED,\n\n        /**\n         * The {@link DecodeFunction} is not able to decode a {@link DataToken} from the given data buffer.\n         */\n        UNABLE_TO_DECODE;\n\n        @Override\n        public byte getType() {\n            return (byte) 0xFF;\n        }\n\n        @Override\n        public String getName() {\n            return \"DECODE_FINISHED\";\n        }\n    }\n\n    /**\n     * A stateful {@link TabularDecoder}. State is required to decode response chunks in multiple attempts/calls to a {@link DecodeFunction}. Typically, state is a previous\n     * {@link ColumnMetadataToken column description} for row results.\n     *\n     * @author Mark Paluch\n     */\n    public static class TabularDecoder {\n\n        private final DecodeFunction decodeFunction;\n\n        /**\n         * @param encryptionSupported whether encryption is supported.\n         */\n        TabularDecoder(boolean encryptionSupported) {\n            this.decodeFunction = Tabular.decodeFunction(encryptionSupported);\n        }\n\n        /**\n         * Decode the {@link Tabular} response from a {@link ByteBuf}.\n         *\n         * @param buffer must not be null.\n         * @return the decoded {@link Tabular} response {@link Message}.\n         */\n        public List<DataToken> decode(ByteBuf buffer) {\n\n            Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n            List<DataToken> tokens = new ArrayList<>();\n\n            while (true) {\n\n                if (buffer.readableBytes() == 0) {\n                    break;\n                }\n\n                int readerIndex = buffer.readerIndex();\n                byte type = Decode.asByte(buffer);\n\n                DataToken message = this.decodeFunction.tryDecode(type, buffer);\n\n                if (message == DecodeFinished.UNABLE_TO_DECODE) {\n                    buffer.readerIndex(readerIndex);\n                    break;\n                }\n\n                if (message == DecodeFinished.FINISHED) {\n                    break;\n                }\n\n                tokens.add(message);\n            }\n\n            return tokens;\n        }\n\n        /**\n         * Decode the {@link Tabular} response from a {@link ByteBuf}.\n         *\n         * @param buffer          must not be null.\n         * @param messageConsumer sink to consume decoded frames.\n         * @return the decoded {@link Tabular} response {@link Message}.\n         */\n        public boolean decode(ByteBuf buffer, SynchronousSink<Message> messageConsumer) {\n\n            Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n            boolean hasMessages = false;\n            while (true) {\n\n                if (buffer.readableBytes() == 0) {\n                    break;\n                }\n\n                int readerIndex = buffer.readerIndex();\n                byte type = Decode.asByte(buffer);\n\n                DataToken message = this.decodeFunction.tryDecode(type, buffer);\n\n                if (message == DecodeFinished.UNABLE_TO_DECODE) {\n                    buffer.readerIndex(readerIndex);\n                    break;\n                }\n\n                if (message == DecodeFinished.FINISHED) {\n                    break;\n                }\n\n                messageConsumer.next(message);\n                hasMessages = true;\n\n            }\n\n            return hasMessages;\n        }\n\n    }\n\n    /**\n     * Decode function for {@link Tabular} streams. Can be called incrementally until the {@link #tryDecode(byte, ByteBuf) decode method} returns {@link DecodeFinished}.\n     */\n    @FunctionalInterface\n    interface DecodeFunction {\n\n        /**\n         * Try to decode a {@link DataToken} from the {@link ByteBuf data buffer}.\n         *\n         * @param type   token type.\n         * @param buffer the data buffer.\n         * @return a decoded {@link DataToken} or a {@link DecodeFinished} marker.\n         */\n        DataToken tryDecode(byte type, ByteBuf buffer);\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/TokenStream.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\n/**\n * A token stream message.\n */\npublic interface TokenStream {\n\n    /**\n     * @return symbolic name of the {@link TokenStream}.\n     */\n    String getName();\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/token/package-info.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The infrastructure for exchanging messages with the server.\n * <p>\n * Token stream message structures.\n * <p>\n * Token stream message structures.\n * <p>\n * Token stream message structures.\n */\n\n/**\n * Token stream message structures.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.message.token;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/AbstractTypeDecoderStrategy.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n * Support class for {@link TypeDecoderStrategy} implementations.\n *\n * @author Mark Paluch\n */\nabstract class AbstractTypeDecoderStrategy implements TypeDecoderStrategy {\n\n    private final int typeDescriptorLength;\n\n    /**\n     * Creates a new {@link AbstractTypeDecoderStrategy}.\n     *\n     * @param typeDescriptorLength length in bytes.\n     */\n    AbstractTypeDecoderStrategy(int typeDescriptorLength) {\n        this.typeDescriptorLength = typeDescriptorLength;\n    }\n\n    @Override\n    public final boolean canDecode(ByteBuf buffer) {\n        return buffer.readableBytes() >= this.typeDescriptorLength;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/Collation.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.util.collection.LongObjectHashMap;\nimport io.netty.util.collection.LongObjectMap;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.io.UnsupportedEncodingException;\nimport java.nio.charset.Charset;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * A collation represents encoding and collation used for character data types. Collation is in the following BNF format\n * (see TDS spec for full details):\n * <ul>\n *     <li>LCID := 20 BIT</li>\n *     <li>fIgnoreCase := BIT</li>\n *     <li>fIgnoreAccent := BIT</li>\n *     <li>fIgnoreWidth := BIT</li>\n *     <li>fIgnoreKana := BIT</li>\n *     <li>fBinary := BIT</li>\n *     <li>ColFlags := fIgnoreCase, fIgnoreAccent, fIgnoreWidth, fIgnoreKana, fBinary, FRESERVEDBIT, FRESERVEDBIT, FRESERVEDBIT</li>\n *     <li>Version := 4 BIT</li>\n *     <li>SortId := BYTE</li>\n * </ul>\n *\n * @author Mark Paluch\n * @see ServerCharset\n */\n@SuppressWarnings(\"unused\")\npublic final class Collation {\n\n    private static final LongObjectMap<Collation> COLLATIONS = new LongObjectHashMap<>();\n\n    public static final Collation RAW = Collation.from(0, 0);\n\n    private static final int UTF8_IN_TDSCOLLATION = 0x4000000;\n\n    // Index from of windows locales by their LangIDs for fast lookup\n    // of encodings associated with various SQL collations\n    private static final Map<Integer, WindowsLocale> localeCache;\n\n    private static final Map<Integer, SortOrder> sortOrderCache;\n\n    static {\n\n        // Populate the windows locale and sort order indices\n\n        localeCache = new HashMap<>();\n        for (WindowsLocale locale : WindowsLocale.values()) {\n            localeCache.put(locale.langID, locale);\n        }\n\n        sortOrderCache = new HashMap<>();\n\n        for (SortOrder sortOrder : SortOrder.values()) {\n            sortOrderCache.put(sortOrder.sortId, sortOrder);\n        }\n    }\n\n    private final int lcid; // First 4 bytes of TDS collation.\n\n    private final int sortId; // 5th byte of TDS collation.\n\n    private final ServerCharset serverCharset;\n\n    private Collation(int lcid, int sortId) throws UnsupportedEncodingException {\n\n        this.lcid = lcid;\n        this.sortId = sortId;\n\n        if (lcid != 0 || sortId != 0) {\n\n            if (UTF8_IN_TDSCOLLATION == (lcid & UTF8_IN_TDSCOLLATION)) {\n                this.serverCharset = ServerCharset.UTF8;\n            } else {\n                // For a SortId==0 collation, the LCID bits correspond to a LocaleId\n                this.serverCharset = (0 == sortId) ? getEncodingFromLCID() : getEncodingFromSortId();\n            }\n        } else {\n            this.serverCharset = ServerCharset.CP1252;\n        }\n    }\n\n    /**\n     * Create a new {@link Collation} from LCID and {@code sortId}.\n     *\n     * @param lcid   locale Id\n     * @param sortId sort Id\n     * @return the {@link Collation}.\n     */\n    public static Collation from(int lcid, int sortId) {\n\n        Collation collation;\n        long cacheKey = lcid | (sortId << 4);\n\n        synchronized (COLLATIONS) {\n\n            collation = COLLATIONS.get(cacheKey);\n            if (collation == null) {\n\n                try {\n                    collation = new Collation(lcid, sortId);\n                } catch (UnsupportedEncodingException e) {\n                    throw ProtocolException.unsupported(e);\n                }\n\n                COLLATIONS.put(cacheKey, collation);\n            }\n\n            return collation;\n        }\n    }\n\n    /**\n     * Decode the {@link Collation}.\n     *\n     * @param buffer the buffer.\n     * @return the decoded Collation.\n     */\n    public static Collation decode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Buffer must not be null\");\n\n        int info = Decode.asInt(buffer); // 4 bytes, contains: LCID ColFlags Version\n        int sortId = Decode.uByte(buffer); // 1 byte, contains: SortId\n\n        return Collation.from(info, sortId);\n    }\n\n    static int getLength() {\n        return 5;\n    } // Length of collation in TDS (in bytes)\n\n    /**\n     * Encode the collation.\n     *\n     * @param buffer the data buffer.\n     */\n    public void encode(ByteBuf buffer) {\n\n        Assert.requireNonNull(buffer, \"Data buffer must not be null\");\n\n        Encode.asInt(buffer, this.lcid);\n        Encode.asByte(buffer, (byte) this.sortId);\n    }\n\n    // Utility methods for getting details of this collation's encoding\n    public Charset getCharset() {\n        return this.serverCharset.charset();\n    }\n\n    /**\n     * Returns the collation info.\n     *\n     * @return the LCID (collation info).\n     */\n    int getLCID() {\n        return this.lcid;\n    }\n\n    /**\n     * Returns the  sort Id\n     *\n     * @return the sort Od.\n     */\n    int getSortId() {\n        return this.sortId;\n    }\n\n    /**\n     * Returns whether the underlying encoding supports ASCII conversion.\n     *\n     * @return {@code true} if the underlying encoding supports ASCII conversion.\n     */\n    boolean supportsAsciiConversion() {\n        return this.serverCharset.supportsAsciiConversion();\n    }\n\n    /**\n     * Returns whether the underlying encoding allows fast-path ASCII conversion by filtering lower ASCII chars.\n     *\n     * @return {@code true} the underlying encoding allows fast-path ASCII.\n     */\n    boolean hasAsciiCompatibleSBCS() {\n        return this.serverCharset.hasAsciiCompatibleSBCS();\n    }\n\n    private int getLanguageId() {\n        return this.lcid & 0x0000FFFF;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof Collation)) {\n            return false;\n        }\n        Collation collation = (Collation) o;\n        return this.lcid == collation.lcid &&\n            this.sortId == collation.sortId;\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(this.lcid, this.sortId);\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [encoding=\").append(this.serverCharset);\n        sb.append(']');\n        return sb.toString();\n    }\n\n    private ServerCharset getEncodingFromLCID() throws UnsupportedEncodingException {\n\n        WindowsLocale locale = localeCache.get(getLanguageId());\n\n        if (locale == null) {\n            throw new UnsupportedEncodingException(String.format(\"Windows collation not supported: %s\", Integer.toHexString(getLanguageId()).toUpperCase()));\n        }\n\n        try {\n            return locale.getServerCharset();\n        } catch (RuntimeException inner) {\n\n            UnsupportedEncodingException e = new UnsupportedEncodingException(String.format(\"Windows collation not supported: %s\",\n                Integer.toHexString(getLanguageId()).toUpperCase()));\n            e.initCause(inner);\n\n            throw e;\n        }\n    }\n\n    private ServerCharset getEncodingFromSortId() throws UnsupportedEncodingException {\n\n        SortOrder sortOrder = sortOrderCache.get(this.sortId);\n\n        if (sortOrder == null) {\n            throw new UnsupportedEncodingException(String.format(\"SQL Server collation is not supported: %d\", this.sortId));\n        }\n\n        try {\n            return sortOrder.getServerCharset();\n        } catch (RuntimeException inner) {\n\n            UnsupportedEncodingException e = new UnsupportedEncodingException(String.format(\"SQL Server collation is not supported: %d\", this.sortId));\n            e.initCause(inner);\n            throw e;\n        }\n    }\n\n    /**\n     * Enumeration of Windows locales recognized by SQL Server.\n     * <p/>\n     * For our purposes in the driver, locales are only described by their LangID and character encodings.\n     * The set of locales is derived from the following resources:\n     * https://download.microsoft.com/download/9/5/e/95ef66af-9026-4bb0-a41d-a4f81802d92c/[MS-LCID].pdf Lists LCID values\n     * and their corresponding meanings (in RFC 3066 format). Used to derive the names for the various enumeration\n     * constants.\n     * <p/>\n     * Collectively, these two\n     * tables provide a mapping of collation-version specific encodings for every locale supported by SQL Server. Lang\n     * IDs are derived from locales' LCIDs.\n     */\n    enum WindowsLocale {\n        ar_SA(0x0401, ServerCharset.CP1256),\n        bg_BG(0x0402, ServerCharset.CP1251),\n        ca_ES(0x0403, ServerCharset.CP1252),\n        zh_TW(0x0404, ServerCharset.CP950),\n        cs_CZ(0x0405, ServerCharset.CP1250),\n        da_DK(0x0406, ServerCharset.CP1252),\n        de_DE(0x0407, ServerCharset.CP1252),\n        el_GR(0x0408, ServerCharset.CP1253),\n        en_US(0x0409, ServerCharset.CP1252),\n        es_ES_tradnl(0x040a, ServerCharset.CP1252),\n        fi_FI(0x040b, ServerCharset.CP1252),\n        fr_FR(0x040c, ServerCharset.CP1252),\n        he_IL(0x040d, ServerCharset.CP1255),\n        hu_HU(0x040e, ServerCharset.CP1250),\n        is_IS(0x040f, ServerCharset.CP1252),\n        it_IT(0x0410, ServerCharset.CP1252),\n        ja_JP(0x0411, ServerCharset.CP932),\n        ko_KR(0x0412, ServerCharset.CP949),\n        nl_NL(0x0413, ServerCharset.CP1252),\n        nb_NO(0x0414, ServerCharset.CP1252),\n        pl_PL(0x0415, ServerCharset.CP1250),\n        pt_BR(0x0416, ServerCharset.CP1252),\n        rm_CH(0x0417, ServerCharset.CP1252),\n        ro_RO(0x0418, ServerCharset.CP1250),\n        ru_RU(0x0419, ServerCharset.CP1251),\n        hr_HR(0x041a, ServerCharset.CP1250),\n        sk_SK(0x041b, ServerCharset.CP1250),\n        sq_AL(0x041c, ServerCharset.CP1250),\n        sv_SE(0x041d, ServerCharset.CP1252),\n        th_TH(0x041e, ServerCharset.CP874),\n        tr_TR(0x041f, ServerCharset.CP1254),\n        ur_PK(0x0420, ServerCharset.CP1256),\n        id_ID(0x0421, ServerCharset.CP1252),\n        uk_UA(0x0422, ServerCharset.CP1251),\n        be_BY(0x0423, ServerCharset.CP1251),\n        sl_SI(0x0424, ServerCharset.CP1250),\n        et_EE(0x0425, ServerCharset.CP1257),\n        lv_LV(0x0426, ServerCharset.CP1257),\n        lt_LT(0x0427, ServerCharset.CP1257),\n        tg_Cyrl_TJ(0x0428, ServerCharset.CP1251),\n        fa_IR(0x0429, ServerCharset.CP1256),\n        vi_VN(0x042a, ServerCharset.CP1258),\n        hy_AM(0x042b, ServerCharset.CP1252),\n        az_Latn_AZ(0x042c, ServerCharset.CP1254),\n        eu_ES(0x042d, ServerCharset.CP1252),\n        wen_DE(0x042e, ServerCharset.CP1252),\n        mk_MK(0x042f, ServerCharset.CP1251),\n        tn_ZA(0x0432, ServerCharset.CP1252),\n        xh_ZA(0x0434, ServerCharset.CP1252),\n        zu_ZA(0x0435, ServerCharset.CP1252),\n        Af_ZA(0x0436, ServerCharset.CP1252),\n        ka_GE(0x0437, ServerCharset.CP1252),\n        fo_FO(0x0438, ServerCharset.CP1252),\n        hi_IN(0x0439, ServerCharset.UNICODE),\n        mt_MT(0x043a, ServerCharset.UNICODE),\n        se_NO(0x043b, ServerCharset.CP1252),\n        ms_MY(0x043e, ServerCharset.CP1252),\n        kk_KZ(0x043f, ServerCharset.CP1251),\n        ky_KG(0x0440, ServerCharset.CP1251),\n        sw_KE(0x0441, ServerCharset.CP1252),\n        tk_TM(0x0442, ServerCharset.CP1250),\n        uz_Latn_UZ(0x0443, ServerCharset.CP1254),\n        tt_RU(0x0444, ServerCharset.CP1251),\n        bn_IN(0x0445, ServerCharset.UNICODE),\n        pa_IN(0x0446, ServerCharset.UNICODE),\n        gu_IN(0x0447, ServerCharset.UNICODE),\n        or_IN(0x0448, ServerCharset.UNICODE),\n        ta_IN(0x0449, ServerCharset.UNICODE),\n        te_IN(0x044a, ServerCharset.UNICODE),\n        kn_IN(0x044b, ServerCharset.UNICODE),\n        ml_IN(0x044c, ServerCharset.UNICODE),\n        as_IN(0x044d, ServerCharset.UNICODE),\n        mr_IN(0x044e, ServerCharset.UNICODE),\n        sa_IN(0x044f, ServerCharset.UNICODE),\n        mn_MN(0x0450, ServerCharset.CP1251),\n        bo_CN(0x0451, ServerCharset.UNICODE),\n        cy_GB(0x0452, ServerCharset.CP1252),\n        km_KH(0x0453, ServerCharset.UNICODE),\n        lo_LA(0x0454, ServerCharset.UNICODE),\n        gl_ES(0x0456, ServerCharset.CP1252),\n        kok_IN(0x0457, ServerCharset.UNICODE),\n        syr_SY(0x045a, ServerCharset.UNICODE),\n        si_LK(0x045b, ServerCharset.UNICODE),\n        iu_Cans_CA(0x045d, ServerCharset.CP1252),\n        am_ET(0x045e, ServerCharset.CP1252),\n        ne_NP(0x0461, ServerCharset.UNICODE),\n        fy_NL(0x0462, ServerCharset.CP1252),\n        ps_AF(0x0463, ServerCharset.UNICODE),\n        fil_PH(0x0464, ServerCharset.CP1252),\n        dv_MV(0x0465, ServerCharset.UNICODE),\n        ha_Latn_NG(0x0468, ServerCharset.CP1252),\n        yo_NG(0x046a, ServerCharset.CP1252),\n        quz_BO(0x046b, ServerCharset.CP1252),\n        nso_ZA(0x046c, ServerCharset.CP1252),\n        ba_RU(0x046d, ServerCharset.CP1251),\n        lb_LU(0x046e, ServerCharset.CP1252),\n        kl_GL(0x046f, ServerCharset.CP1252),\n        ig_NG(0x0470, ServerCharset.CP1252),\n        ii_CN(0x0478, ServerCharset.CP1252),\n        arn_CL(0x047a, ServerCharset.CP1252),\n        moh_CA(0x047c, ServerCharset.CP1252),\n        br_FR(0x047e, ServerCharset.CP1252),\n        ug_CN(0x0480, ServerCharset.CP1256),\n        mi_NZ(0x0481, ServerCharset.UNICODE),\n        oc_FR(0x0482, ServerCharset.CP1252),\n        co_FR(0x0483, ServerCharset.CP1252),\n        gsw_FR(0x0484, ServerCharset.CP1252),\n        sah_RU(0x0485, ServerCharset.CP1251),\n        qut_GT(0x0486, ServerCharset.CP1252),\n        rw_RW(0x0487, ServerCharset.CP1252),\n        wo_SN(0x0488, ServerCharset.CP1252),\n        prs_AF(0x048c, ServerCharset.CP1256),\n        ar_IQ(0x0801, ServerCharset.CP1256),\n        zh_CN(0x0804, ServerCharset.CP936),\n        de_CH(0x0807, ServerCharset.CP1252),\n        en_GB(0x0809, ServerCharset.CP1252),\n        es_MX(0x080a, ServerCharset.CP1252),\n        fr_BE(0x080c, ServerCharset.CP1252),\n        it_CH(0x0810, ServerCharset.CP1252),\n        nl_BE(0x0813, ServerCharset.CP1252),\n        nn_NO(0x0814, ServerCharset.CP1252),\n        pt_PT(0x0816, ServerCharset.CP1252),\n        sr_Latn_CS(0x081a, ServerCharset.CP1250),\n        sv_FI(0x081d, ServerCharset.CP1252),\n        Lithuanian_Classic(0x0827, ServerCharset.CP1257),\n        az_Cyrl_AZ(0x082c, ServerCharset.CP1251),\n        dsb_DE(0x082e, ServerCharset.CP1252),\n        se_SE(0x083b, ServerCharset.CP1252),\n        ga_IE(0x083c, ServerCharset.CP1252),\n        ms_BN(0x083e, ServerCharset.CP1252),\n        uz_Cyrl_UZ(0x0843, ServerCharset.CP1251),\n        bn_BD(0x0845, ServerCharset.UNICODE),\n        mn_Mong_CN(0x0850, ServerCharset.CP1251),\n        iu_Latn_CA(0x085d, ServerCharset.CP1252),\n        tzm_Latn_DZ(0x085f, ServerCharset.CP1252),\n        quz_EC(0x086b, ServerCharset.CP1252),\n        ar_EG(0x0c01, ServerCharset.CP1256),\n        zh_HK(0x0c04, ServerCharset.CP950),\n        de_AT(0x0c07, ServerCharset.CP1252),\n        en_AU(0x0c09, ServerCharset.CP1252),\n        es_ES(0x0c0a, ServerCharset.CP1252),\n        fr_CA(0x0c0c, ServerCharset.CP1252),\n        sr_Cyrl_CS(0x0c1a, ServerCharset.CP1251),\n        se_FI(0x0c3b, ServerCharset.CP1252),\n        quz_PE(0x0c6b, ServerCharset.CP1252),\n        ar_LY(0x1001, ServerCharset.CP1256),\n        zh_SG(0x1004, ServerCharset.CP936),\n        de_LU(0x1007, ServerCharset.CP1252),\n        en_CA(0x1009, ServerCharset.CP1252),\n        es_GT(0x100a, ServerCharset.CP1252),\n        fr_CH(0x100c, ServerCharset.CP1252),\n        hr_BA(0x101a, ServerCharset.CP1250),\n        smj_NO(0x103b, ServerCharset.CP1252),\n        ar_DZ(0x1401, ServerCharset.CP1256),\n        zh_MO(0x1404, ServerCharset.CP950),\n        de_LI(0x1407, ServerCharset.CP1252),\n        en_NZ(0x1409, ServerCharset.CP1252),\n        es_CR(0x140a, ServerCharset.CP1252),\n        fr_LU(0x140c, ServerCharset.CP1252),\n        bs_Latn_BA(0x141a, ServerCharset.CP1250),\n        smj_SE(0x143b, ServerCharset.CP1252),\n        ar_MA(0x1801, ServerCharset.CP1256),\n        en_IE(0x1809, ServerCharset.CP1252),\n        es_PA(0x180a, ServerCharset.CP1252),\n        fr_MC(0x180c, ServerCharset.CP1252),\n        sr_Latn_BA(0x181a, ServerCharset.CP1250),\n        sma_NO(0x183b, ServerCharset.CP1252),\n        ar_TN(0x1c01, ServerCharset.CP1256),\n        en_ZA(0x1c09, ServerCharset.CP1252),\n        es_DO(0x1c0a, ServerCharset.CP1252),\n        sr_Cyrl_BA(0x1c1a, ServerCharset.CP1251),\n        sma_SB(0x1c3b, ServerCharset.CP1252),\n        ar_OM(0x2001, ServerCharset.CP1256),\n        en_JM(0x2009, ServerCharset.CP1252),\n        es_VE(0x200a, ServerCharset.CP1252),\n        bs_Cyrl_BA(0x201a, ServerCharset.CP1251),\n        sms_FI(0x203b, ServerCharset.CP1252),\n        ar_YE(0x2401, ServerCharset.CP1256),\n        en_CB(0x2409, ServerCharset.CP1252),\n        es_CO(0x240a, ServerCharset.CP1252),\n        smn_FI(0x243b, ServerCharset.CP1252),\n        ar_SY(0x2801, ServerCharset.CP1256),\n        en_BZ(0x2809, ServerCharset.CP1252),\n        es_PE(0x280a, ServerCharset.CP1252),\n        ar_JO(0x2c01, ServerCharset.CP1256),\n        en_TT(0x2c09, ServerCharset.CP1252),\n        es_AR(0x2c0a, ServerCharset.CP1252),\n        ar_LB(0x3001, ServerCharset.CP1256),\n        en_ZW(0x3009, ServerCharset.CP1252),\n        es_EC(0x300a, ServerCharset.CP1252),\n        ar_KW(0x3401, ServerCharset.CP1256),\n        en_PH(0x3409, ServerCharset.CP1252),\n        es_CL(0x340a, ServerCharset.CP1252),\n        ar_AE(0x3801, ServerCharset.CP1256),\n        es_UY(0x380a, ServerCharset.CP1252),\n        ar_BH(0x3c01, ServerCharset.CP1256),\n        es_PY(0x3c0a, ServerCharset.CP1252),\n        ar_QA(0x4001, ServerCharset.CP1256),\n        en_IN(0x4009, ServerCharset.CP1252),\n        es_BO(0x400a, ServerCharset.CP1252),\n        en_MY(0x4409, ServerCharset.CP1252),\n        es_SV(0x440a, ServerCharset.CP1252),\n        en_SG(0x4809, ServerCharset.CP1252),\n        es_HN(0x480a, ServerCharset.CP1252),\n        es_NI(0x4c0a, ServerCharset.CP1252),\n        es_PR(0x500a, ServerCharset.CP1252),\n        es_US(0x540a, ServerCharset.CP1252);\n\n        private final int langID;\n\n        private final ServerCharset serverCharset;\n\n        WindowsLocale(int langID, ServerCharset serverCharset) {\n            this.langID = langID;\n            this.serverCharset = serverCharset;\n        }\n\n        ServerCharset getServerCharset() {\n            this.serverCharset.charset();\n            return this.serverCharset;\n        }\n    }\n\n    /**\n     * Enumeration of original SQL Server sort orders recognized by SQL Server.\n     */\n    enum SortOrder {\n        BIN_CP437(30, \"SQL_Latin1_General_CP437_BIN\", ServerCharset.CP437),\n        DICTIONARY_437(31, \"SQL_Latin1_General_CP437_CS_AS\", ServerCharset.CP437),\n        NOCASE_437(32, \"SQL_Latin1_General_CP437_CI_AS\", ServerCharset.CP437),\n        NOCASEPREF_437(33, \"SQL_Latin1_General_Pref_CP437_CI_AS\", ServerCharset.CP437),\n        NOACCENTS_437(34, \"SQL_Latin1_General_CP437_CI_AI\", ServerCharset.CP437),\n        BIN2_CP437(35, \"SQL_Latin1_General_CP437_BIN2\", ServerCharset.CP437),\n\n        BIN_CP850(40, \"SQL_Latin1_General_CP850_BIN\", ServerCharset.CP850),\n        DICTIONARY_850(41, \"SQL_Latin1_General_CP850_CS_AS\", ServerCharset.CP850),\n        NOCASE_850(42, \"SQL_Latin1_General_CP850_CI_AS\", ServerCharset.CP850),\n        NOCASEPREF_850(43, \"SQL_Latin1_General_Pref_CP850_CI_AS\", ServerCharset.CP850),\n        NOACCENTS_850(44, \"SQL_Latin1_General_CP850_CI_AI\", ServerCharset.CP850),\n        BIN2_CP850(45, \"SQL_Latin1_General_CP850_BIN2\", ServerCharset.CP850),\n\n        CASELESS_34(49, \"SQL_1xCompat_CP850_CI_AS\", ServerCharset.CP850),\n        BIN_ISO_1(50, \"bin_iso_1\", ServerCharset.CP1252),\n        DICTIONARY_ISO(51, \"SQL_Latin1_General_CP1_CS_AS\", ServerCharset.CP1252),\n        NOCASE_ISO(52, \"SQL_Latin1_General_CP1_CI_AS\", ServerCharset.CP1252),\n        NOCASEPREF_ISO(53, \"SQL_Latin1_General_Pref_CP1_CI_AS\", ServerCharset.CP1252),\n        NOACCENTS_ISO(54, \"SQL_Latin1_General_CP1_CI_AI\", ServerCharset.CP1252),\n        ALT_DICTIONARY(55, \"SQL_AltDiction_CP850_CS_AS\", ServerCharset.CP850),\n        ALT_NOCASEPREF(56, \"SQL_AltDiction_Pref_CP850_CI_AS\", ServerCharset.CP850),\n        ALT_NOACCENTS(57, \"SQL_AltDiction_CP850_CI_AI\", ServerCharset.CP850),\n        SCAND_NOCASEPREF(58, \"SQL_Scandinavian_Pref_CP850_CI_AS\", ServerCharset.CP850),\n        SCAND_DICTIONARY(59, \"SQL_Scandinavian_CP850_CS_AS\", ServerCharset.CP850),\n        SCAND_NOCASE(60, \"SQL_Scandinavian_CP850_CI_AS\", ServerCharset.CP850),\n        ALT_NOCASE(61, \"SQL_AltDiction_CP850_CI_AS\", ServerCharset.CP850),\n\n        DICTIONARY_1252(71, \"dictionary_1252\", ServerCharset.CP1252),\n        NOCASE_1252(72, \"nocase_1252\", ServerCharset.CP1252),\n        DNK_NOR_DICTIONARY(73, \"dnk_nor_dictionary\", ServerCharset.CP1252),\n        FIN_SWE_DICTIONARY(74, \"fin_swe_dictionary\", ServerCharset.CP1252),\n        ISL_DICTIONARY(75, \"isl_dictionary\", ServerCharset.CP1252),\n\n        BIN_CP1250(80, \"bin_cp1250\", ServerCharset.CP1250),\n        DICTIONARY_1250(81, \"SQL_Latin1_General_CP1250_CS_AS\", ServerCharset.CP1250),\n        NOCASE_1250(82, \"SQL_Latin1_General_CP1250_CI_AS\", ServerCharset.CP1250),\n        CSYDIC(83, \"SQL_Czech_CP1250_CS_AS\", ServerCharset.CP1250),\n        CSYNC(84, \"SQL_Czech_CP1250_CI_AS\", ServerCharset.CP1250),\n        HUNDIC(85, \"SQL_Hungarian_CP1250_CS_AS\", ServerCharset.CP1250),\n        HUNNC(86, \"SQL_Hungarian_CP1250_CI_AS\", ServerCharset.CP1250),\n        PLKDIC(87, \"SQL_Polish_CP1250_CS_AS\", ServerCharset.CP1250),\n        PLKNC(88, \"SQL_Polish_CP1250_CI_AS\", ServerCharset.CP1250),\n        ROMDIC(89, \"SQL_Romanian_CP1250_CS_AS\", ServerCharset.CP1250),\n        ROMNC(90, \"SQL_Romanian_CP1250_CI_AS\", ServerCharset.CP1250),\n        SHLDIC(91, \"SQL_Croatian_CP1250_CS_AS\", ServerCharset.CP1250),\n        SHLNC(92, \"SQL_Croatian_CP1250_CI_AS\", ServerCharset.CP1250),\n        SKYDIC(93, \"SQL_Slovak_CP1250_CS_AS\", ServerCharset.CP1250),\n        SKYNC(94, \"SQL_Slovak_CP1250_CI_AS\", ServerCharset.CP1250),\n        SLVDIC(95, \"SQL_Slovenian_CP1250_CS_AS\", ServerCharset.CP1250),\n        SLVNC(96, \"SQL_Slovenian_CP1250_CI_AS\", ServerCharset.CP1250),\n        POLISH_CS(97, \"polish_cs\", ServerCharset.CP1250),\n        POLISH_CI(98, \"polish_ci\", ServerCharset.CP1250),\n\n        BIN_CP1251(104, \"bin_cp1251\", ServerCharset.CP1251),\n        DICTIONARY_1251(105, \"SQL_Latin1_General_CP1251_CS_AS\", ServerCharset.CP1251),\n        NOCASE_1251(106, \"SQL_Latin1_General_CP1251_CI_AS\", ServerCharset.CP1251),\n        UKRDIC(107, \"SQL_Ukrainian_CP1251_CS_AS\", ServerCharset.CP1251),\n        UKRNC(108, \"SQL_Ukrainian_CP1251_CI_AS\", ServerCharset.CP1251),\n\n        BIN_CP1253(112, \"bin_cp1253\", ServerCharset.CP1253),\n        DICTIONARY_1253(113, \"SQL_Latin1_General_CP1253_CS_AS\", ServerCharset.CP1253),\n        NOCASE_1253(114, \"SQL_Latin1_General_CP1253_CI_AS\", ServerCharset.CP1253),\n\n        GREEK_MIXEDDICTIONARY(120, \"SQL_MixDiction_CP1253_CS_AS\", ServerCharset.CP1253),\n        GREEK_ALTDICTIONARY(121, \"SQL_AltDiction_CP1253_CS_AS\", ServerCharset.CP1253),\n        GREEK_ALTDICTIONARY2(122, \"SQL_AltDiction2_CP1253_CS_AS\", ServerCharset.CP1253),\n        GREEK_NOCASEDICT(124, \"SQL_Latin1_General_CP1253_CI_AI\", ServerCharset.CP1253),\n        BIN_CP1254(128, \"bin_cp1254\", ServerCharset.CP1254),\n        DICTIONARY_1254(129, \"SQL_Latin1_General_CP1254_CS_AS\", ServerCharset.CP1254),\n        NOCASE_1254(130, \"SQL_Latin1_General_CP1254_CI_AS\", ServerCharset.CP1254),\n\n        BIN_CP1255(136, \"bin_cp1255\", ServerCharset.CP1255),\n        DICTIONARY_1255(137, \"SQL_Latin1_General_CP1255_CS_AS\", ServerCharset.CP1255),\n        NOCASE_1255(138, \"SQL_Latin1_General_CP1255_CI_AS\", ServerCharset.CP1255),\n\n        BIN_CP1256(144, \"bin_cp1256\", ServerCharset.CP1256),\n        DICTIONARY_1256(145, \"SQL_Latin1_General_CP1256_CS_AS\", ServerCharset.CP1256),\n        NOCASE_1256(146, \"SQL_Latin1_General_CP1256_CI_AS\", ServerCharset.CP1256),\n\n        BIN_CP1257(152, \"bin_cp1257\", ServerCharset.CP1257),\n        DICTIONARY_1257(153, \"SQL_Latin1_General_CP1257_CS_AS\", ServerCharset.CP1257),\n        NOCASE_1257(154, \"SQL_Latin1_General_CP1257_CI_AS\", ServerCharset.CP1257),\n        ETIDIC(155, \"SQL_Estonian_CP1257_CS_AS\", ServerCharset.CP1257),\n        ETINC(156, \"SQL_Estonian_CP1257_CI_AS\", ServerCharset.CP1257),\n        LVIDIC(157, \"SQL_Latvian_CP1257_CS_AS\", ServerCharset.CP1257),\n        LVINC(158, \"SQL_Latvian_CP1257_CI_AS\", ServerCharset.CP1257),\n        LTHDIC(159, \"SQL_Lithuanian_CP1257_CS_AS\", ServerCharset.CP1257),\n        LTHNC(160, \"SQL_Lithuanian_CP1257_CI_AS\", ServerCharset.CP1257),\n\n        DANNO_NOCASEPREF(183, \"SQL_Danish_Pref_CP1_CI_AS\", ServerCharset.CP1252),\n        SVFI1_NOCASEPREF(184, \"SQL_SwedishPhone_Pref_CP1_CI_AS\", ServerCharset.CP1252),\n        SVFI2_NOCASEPREF(185, \"SQL_SwedishStd_Pref_CP1_CI_AS\", ServerCharset.CP1252),\n        ISLAN_NOCASEPREF(186, \"SQL_Icelandic_Pref_CP1_CI_AS\", ServerCharset.CP1252),\n\n        BIN_CP932(192, \"bin_cp932\", ServerCharset.CP932),\n        NLS_CP932(193, \"nls_cp932\", ServerCharset.CP932),\n        BIN_CP949(194, \"bin_cp949\", ServerCharset.CP949),\n        NLS_CP949(195, \"nls_cp949\", ServerCharset.CP949),\n        BIN_CP950(196, \"bin_cp950\", ServerCharset.CP950),\n        NLS_CP950(197, \"nls_cp950\", ServerCharset.CP950),\n        BIN_CP936(198, \"bin_cp936\", ServerCharset.CP936),\n        NLS_CP936(199, \"nls_cp936\", ServerCharset.CP936),\n        NLS_CP932_CS(200, \"nls_cp932_cs\", ServerCharset.CP932),\n        NLS_CP949_CS(201, \"nls_cp949_cs\", ServerCharset.CP949),\n        NLS_CP950_CS(202, \"nls_cp950_cs\", ServerCharset.CP950),\n        NLS_CP936_CS(203, \"nls_cp936_cs\", ServerCharset.CP936),\n        BIN_CP874(204, \"bin_cp874\", ServerCharset.CP874),\n        NLS_CP874(205, \"nls_cp874\", ServerCharset.CP874),\n        NLS_CP874_CS(206, \"nls_cp874_cs\", ServerCharset.CP874),\n\n        EBCDIC_037(210, \"SQL_EBCDIC037_CP1_CS_AS\", ServerCharset.CP1252),\n        EBCDIC_273(211, \"SQL_EBCDIC273_CP1_CS_AS\", ServerCharset.CP1252),\n        EBCDIC_277(212, \"SQL_EBCDIC277_CP1_CS_AS\", ServerCharset.CP1252),\n        EBCDIC_278(213, \"SQL_EBCDIC278_CP1_CS_AS\", ServerCharset.CP1252),\n        EBCDIC_280(214, \"SQL_EBCDIC280_CP1_CS_AS\", ServerCharset.CP1252),\n        EBCDIC_284(215, \"SQL_EBCDIC284_CP1_CS_AS\", ServerCharset.CP1252),\n        EBCDIC_285(216, \"SQL_EBCDIC285_CP1_CS_AS\", ServerCharset.CP1252),\n        EBCDIC_297(217, \"SQL_EBCDIC297_CP1_CS_AS\", ServerCharset.CP1252);\n\n        private final int sortId;\n\n        private final String name;\n\n        private final ServerCharset serverCharset;\n\n        SortOrder(int sortId, String name, ServerCharset serverCharset) {\n            this.sortId = sortId;\n            this.name = name;\n            this.serverCharset = serverCharset;\n        }\n\n        ServerCharset getServerCharset() {\n            this.serverCharset.charset();\n            return this.serverCharset;\n        }\n\n        public final String toString() {\n            return this.name;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/Length.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\n\n/**\n * Descriptor for data length in row results.\n * Use {@link Length} to encode/decode length headers of a PLP chunk and {@link PlpLength} to\n * encode/decode the the total PLP stream length.\n *\n * @author Mark Paluch\n * @see PlpLength\n */\npublic final class Length {\n\n    public static final int USHORT_NULL = 65535;\n\n    public static final int UNKNOWN_STREAM_LENGTH = -1;\n\n    private static final int CACHE_ENTRIES = 1024;\n\n    private static final Length NULL;\n\n    private static final Length[] CACHE;\n\n    private static final Length UNKNOWN_NULL;\n\n    private static final Length UNKNOWN;\n\n    static {\n\n        CACHE = new Length[CACHE_ENTRIES];\n\n        for (int i = 0; i < CACHE_ENTRIES; i++) {\n            CACHE[i] = new Length(i, false);\n        }\n\n        NULL = new Length(0, true);\n        UNKNOWN = new Length(UNKNOWN_STREAM_LENGTH, false);\n        UNKNOWN_NULL = new Length(UNKNOWN_STREAM_LENGTH, true);\n    }\n\n    private final int length;\n\n    private final boolean isNull;\n\n    private Length(int length, boolean isNull) {\n        this.length = length;\n        this.isNull = isNull;\n    }\n\n    /**\n     * Creates a {@link Length} that indicates the value is {@code null}.\n     *\n     * @return a {@link Length} for {@code null}.\n     */\n    public static Length nullLength() {\n        return of(0, true);\n    }\n\n    /**\n     * Creates a {@link Length} with a given {@code length}.\n     *\n     * @param length value length.\n     * @return a {@link Length} for a non-{@code null} value of the given {@code length}.\n     */\n    public static Length of(int length) {\n        return of(length, false);\n    }\n\n    /**\n     * Creates a {@link Length}.\n     *\n     * @param length value length.\n     * @param isNull {@code true} if the value is {@code null}.\n     * @return the {@link Length}.\n     */\n    public static Length of(int length, boolean isNull) {\n\n        if (length == UNKNOWN_STREAM_LENGTH) {\n            return isNull ? UNKNOWN_NULL : UNKNOWN;\n        }\n\n        if (isNull) {\n            return NULL;\n        }\n\n        if (length < 0) {\n            throw new IllegalArgumentException(\"length must be greater or equal to zero\");\n        }\n\n        if (length > (CACHE_ENTRIES - 1)) {\n            return new Length(length, isNull);\n        }\n\n        return CACHE[length];\n    }\n\n    /**\n     * Decode a {@link Length} for a {@link TypeInformation}.\n     *\n     * @param buffer the data buffer.\n     * @param type   {@link TypeInformation}.\n     * @return the {@link Length}.\n     */\n    public static Length decode(ByteBuf buffer, TypeInformation type) {\n\n        switch (type.getLengthStrategy()) {\n\n            case PARTLENTYPE: {\n                int length = Decode.asInt(buffer);\n                return Length.of(length, false);\n            }\n\n            case FIXEDLENTYPE:\n                return Length.of(type.getMaxLength(), type.getMaxLength() == 0);\n\n            case BYTELENTYPE: {\n                int length = Decode.uByte(buffer);\n                return Length.of(length, length == 0);\n            }\n\n            case USHORTLENTYPE: {\n                int length = Decode.uShort(buffer);\n                return Length.of(length == USHORT_NULL ? 0 : length, length == USHORT_NULL);\n            }\n\n            case LONGLENTYPE: {\n\n                SqlServerType serverType = type.getServerType();\n\n                if (serverType == SqlServerType.TEXT || serverType == SqlServerType.IMAGE\n                    || serverType == SqlServerType.NTEXT) {\n\n                    int nullMarker = Decode.uByte(buffer);\n\n                    if (nullMarker == 0) {\n                        return new Length(0, true);\n                    }\n\n                    // skip(24) is to skip the textptr and timestamp fields\n                    buffer.skipBytes(24);\n                    int valueLength = Decode.asLong(buffer);\n\n                    return Length.of(valueLength, false);\n                }\n\n                if (serverType == SqlServerType.SQL_VARIANT) {\n                    int valueLength = Decode.asInt(buffer);\n                    return Length.of(valueLength, valueLength == 0);\n                }\n\n                int length = Decode.uShort(buffer);\n                return Length.of(length == USHORT_NULL ? 0 : length, length == USHORT_NULL);\n            }\n        }\n\n        throw ProtocolException.invalidTds(\"Cannot parse value LengthDescriptor\");\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an {@link Length}.\n     *\n     * @param buffer the data buffer.\n     * @param type   {@link TypeInformation}.\n     * @return {@code true} if the buffer contains sufficient data to decode a {@link Length}.\n     */\n    public static boolean canDecode(ByteBuf buffer, TypeInformation type) {\n\n        int readerIndex = buffer.readerIndex();\n        try {\n            return doCanDecode(buffer, type);\n        } finally {\n            buffer.readerIndex(readerIndex);\n        }\n    }\n\n    private static boolean doCanDecode(ByteBuf buffer, TypeInformation type) {\n\n        switch (type.getLengthStrategy()) {\n\n            case PARTLENTYPE:\n                return buffer.readableBytes() >= 4;\n\n            case FIXEDLENTYPE:\n                return true;\n\n            case BYTELENTYPE:\n            case USHORTLENTYPE:\n                return buffer.readableBytes() >= 2;\n\n            case LONGLENTYPE: {\n\n                SqlServerType serverType = type.getServerType();\n\n                if (serverType == SqlServerType.TEXT || serverType == SqlServerType.IMAGE\n                    || serverType == SqlServerType.NTEXT) {\n\n                    if (buffer.readableBytes() == 0) {\n                        return false;\n                    }\n\n                    int nullMarker = Decode.uByte(buffer);\n\n                    if (nullMarker == 0) {\n                        return true;\n                    }\n\n                    // skip(24) is to skip the textptr and timestamp fields\n                    return buffer.readableBytes() >= 24 + /* int */ 4;\n                }\n\n                if (serverType == SqlServerType.SQL_VARIANT) {\n                    return buffer.readableBytes() >= 4;\n                }\n\n                return buffer.readableBytes() >= 2;\n            }\n        }\n\n        throw ProtocolException.invalidTds(\"Cannot parse value LengthDescriptor\");\n    }\n\n    public void encode(ByteBuf buffer, TypeInformation type) {\n\n        LengthStrategy lengthStrategy = type.getLengthStrategy();\n\n        if (lengthStrategy == LengthStrategy.LONGLENTYPE) {\n\n            SqlServerType serverType = type.getServerType();\n\n            if (serverType == SqlServerType.TEXT || serverType == SqlServerType.IMAGE\n                || serverType == SqlServerType.NTEXT) {\n\n                if (isNull()) {\n                    Encode.asByte(buffer, (byte) 0);\n                    return;\n                }\n\n                // skip(24) is to skip the textptr and timestamp fields\n                buffer.skipBytes(24);\n                buffer.writeLong(getLength());\n                return;\n            }\n\n            if (serverType == SqlServerType.SQL_VARIANT) {\n\n                Encode.intBigEndian(buffer, getLength());\n                return;\n            }\n\n            if (isNull()) {\n                Encode.uShortBE(buffer, USHORT_NULL);\n            } else {\n                Encode.uShortBE(buffer, getLength());\n            }\n\n            return;\n        }\n\n        encode(buffer, lengthStrategy);\n    }\n\n    public void encode(ByteBuf buffer, LengthStrategy lengthStrategy) {\n\n        switch (lengthStrategy) {\n\n            case PARTLENTYPE:\n                Encode.asInt(buffer, getLength());\n                return;\n\n            case FIXEDLENTYPE:\n                return;\n\n            case BYTELENTYPE:\n\n                if (isNull()) {\n                    Encode.asByte(buffer, (byte) 0);\n                } else {\n                    Encode.asByte(buffer, (byte) getLength());\n                }\n                return;\n\n            case USHORTLENTYPE:\n\n                if (isNull()) {\n                    Encode.uShort(buffer, USHORT_NULL);\n                } else {\n                    Encode.uShort(buffer, getLength());\n                }\n\n                return;\n        }\n\n        throw ProtocolException.invalidTds(\"Cannot encode value LengthDescriptor for \" + lengthStrategy);\n    }\n\n    public int getLength() {\n        return this.length;\n    }\n\n    public boolean isNull() {\n        return this.isNull;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [length=\").append(this.length);\n        sb.append(\", isNull=\").append(this.isNull);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/LengthStrategy.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\n/**\n * SQL Server length strategies.\n */\npublic enum LengthStrategy {\n\n    /**\n     * Fixed-length type such as {@code NULL}, {@code INTn}, {@code MONEY}.\n     */\n    FIXEDLENTYPE,\n\n    /**\n     * Variable length type such as {@code NUMERICN} using a single {@code byte} as length\n     * descriptor (0-255).\n     */\n    BYTELENTYPE,\n\n    /**\n     * Variable length type such as {@code VARCHAR}, {@code VARBINARY} (2 bytes) as length\n     * descriptor (0-65534), {@code -1} represents {@code null}\n     */\n    USHORTLENTYPE,\n\n    /**\n     * Variable length type such as {@code TEXT} and  {@code IMAGE} using a {@code long} (4 bytes) as length\n     * descriptor (0-2GB), {@code -1} represents {@code null}.\n     */\n    LONGLENTYPE,\n\n    /**\n     * Partially length type such as {@code BIGVARCHARTYPE}, {@code UDTTYYPE}, {@code NVARCHARTYPE} using a {@code short} as length\n     * descriptor (0-8000).\n     */\n    PARTLENTYPE\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/MutableTypeInformation.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.charset.Charset;\n\n/**\n * Mutable {@link TypeInformation} information for a column.\n *\n * @author Mark Paluch\n */\nfinal class MutableTypeInformation implements TypeInformation {\n\n    int maxLength;\n\n    LengthStrategy lengthStrategy; // Length type (FIXEDLENTYPE, PARTLENTYPE, etc.)\n\n    int precision;\n\n    int displaySize;\n\n    int scale;\n\n    int flags;\n\n    SqlServerType serverType;\n\n    int userType;\n\n    @Nullable\n    String udtTypeName;\n\n    // Collation (will be null for non-textual types).\n    @Nullable\n    Collation collation;\n\n    @Nullable\n    Charset charset;\n\n    @Override\n    public int getMaxLength() {\n        return this.maxLength;\n    }\n\n    @Override\n    public LengthStrategy getLengthStrategy() {\n        return this.lengthStrategy;\n    }\n\n    @Override\n    public int getPrecision() {\n        return this.precision;\n    }\n\n    @Override\n    public int getDisplaySize() {\n        return this.displaySize;\n    }\n\n    @Override\n    public int getScale() {\n        return this.scale;\n    }\n\n    @Override\n    public SqlServerType getServerType() {\n        return this.serverType;\n    }\n\n    @Override\n    public int getUserType() {\n        return this.userType;\n    }\n\n    @Override\n    @Nullable\n    public String getUdtTypeName() {\n        return this.udtTypeName;\n    }\n\n    @Override\n    @Nullable\n    public Collation getCollation() {\n        return this.collation;\n    }\n\n    @Override\n    @Nullable\n    public Charset getCharset() {\n        return this.charset;\n    }\n\n    @Override\n    public String getServerTypeName() {\n        return (SqlServerType.UDT == this.serverType) ? this.udtTypeName : this.serverType.toString();\n    }\n\n    @Override\n    public boolean isNullable() {\n        return 0x0001 == (this.flags & 0x0001);\n    }\n\n    @Override\n    public boolean isCaseSensitive() {\n        return 0x0002 == (this.flags & 0x0002);\n    }\n\n    @Override\n    public boolean isSparseColumnSet() {\n        return 0x0400 == (this.flags & 0x0400);\n    }\n\n    @Override\n    public boolean isEncrypted() {\n        return 0x0800 == (this.flags & 0x0800);\n    }\n\n    @Override\n    public Updatability getUpdatability() {\n\n        int value = (this.flags >> 2) & 0x0003;\n\n        if (value == 0) {\n            return Updatability.READ_ONLY;\n        }\n\n        if (value == 1) {\n            return Updatability.READ_WRITE;\n        }\n\n        return Updatability.UNKNOWN;\n    }\n\n    @Override\n    public boolean isIdentity() {\n        return 0x0010 == (this.flags & 0x0010);\n    }\n\n    private byte[] getFlags() {\n        byte[] f = new byte[2];\n        f[0] = (byte) (this.flags & 0xFF);\n        f[1] = (byte) ((this.flags >> 8) & 0xFF);\n        return f;\n    }\n\n    /**\n     * Returns true if this type is a textual type with a single-byte character set that is compatible with the 7-bit\n     * US-ASCII character set.\n     */\n    public boolean supportsFastAsciiConversion() {\n\n        switch (this.serverType) {\n            case CHAR:\n            case VARCHAR:\n            case VARCHARMAX:\n            case TEXT:\n                return this.collation != null && this.collation.hasAsciiCompatibleSBCS();\n\n            default:\n                return false;\n        }\n    }\n\n    @Override\n    public String toString() {\n\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [maxLength=\").append(this.maxLength);\n        sb.append(\", lengthStrategy=\").append(this.lengthStrategy);\n        sb.append(\", precision=\").append(this.precision);\n        sb.append(\", displaySize=\").append(this.displaySize);\n        sb.append(\", scale=\").append(this.scale);\n        sb.append(\", flags=\").append(this.flags);\n        sb.append(\", serverType=\").append(this.serverType);\n        sb.append(\", userType=\").append(this.userType);\n        sb.append(\", udtTypeName=\\\"\").append(this.udtTypeName).append('\\\"');\n        sb.append(\", collation=\").append(this.collation);\n        sb.append(\", charset=\").append(this.charset);\n        sb.append(']');\n\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/PlpLength.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\n\n/**\n * Descriptor for PLP data length in row results.\n * This class encapsulates the total length of a value using a 8 byte unsigned long counter.\n * Use {@link Length} to encode/decode length headers of a PLP chunk and {@link PlpLength} to\n * encode/decode the the total PLP stream length\n *\n * @author Mark Paluch\n * @see Length\n */\npublic final class PlpLength {\n\n    public static final long PLP_NULL = 0xFF_FF_FF_FF_FF_FF_FF_FFL;\n\n    public static final long UNKNOWN_PLP_LEN = 0xFF_FF_FF_FF_FF_FF_FF_FEL;\n\n    private final long length;\n\n    private final boolean isNull;\n\n    private PlpLength(long length, boolean isNull) {\n        this.length = length;\n        this.isNull = isNull;\n    }\n\n    /**\n     * Creates a {@link PlpLength} that indicates the value length is unknown.\n     *\n     * @return a {@link PlpLength} with unknown length.\n     */\n    public static PlpLength unknown() {\n        return of(UNKNOWN_PLP_LEN, false);\n    }\n\n    /**\n     * Creates a {@link PlpLength} that indicates the value is {@code null}.\n     *\n     * @return a {@link PlpLength} for {@code null}.\n     */\n    public static PlpLength nullLength() {\n        return of(0, true);\n    }\n\n    /**\n     * Creates a {@link PlpLength} with a given {@code length}.\n     *\n     * @param length value length.\n     * @return a {@link PlpLength} for a non-{@code null} value of the given {@code length}.\n     */\n    public static PlpLength of(long length) {\n        return of(length, false);\n    }\n\n    /**\n     * Creates a {@link PlpLength}.\n     *\n     * @param length value length.\n     * @param isNull {@code true} if the value is {@code null}.\n     * @return the {@link PlpLength}.\n     */\n    public static PlpLength of(long length, boolean isNull) {\n        return new PlpLength(length, isNull);\n    }\n\n    /**\n     * Decode a {@link PlpLength} for a {@link TypeInformation}.\n     *\n     * @param buffer the data buffer.\n     * @param type   {@link TypeInformation}.\n     * @return the {@link PlpLength}.\n     */\n    public static PlpLength decode(ByteBuf buffer, TypeInformation type) {\n\n        if (type.getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n\n            long length = Decode.uLongLong(buffer);\n            return PlpLength.of(length == PLP_NULL ? 0 : length, length == PLP_NULL);\n        }\n\n        throw ProtocolException.invalidTds(\"Cannot parse using \" + type.getLengthStrategy());\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} can be decoded into an {@link PlpLength}.\n     *\n     * @param buffer the data buffer.\n     * @param type   {@link TypeInformation}.\n     * @return {@code true} if the buffer contains sufficient data to decode a {@link PlpLength}.\n     */\n    public static boolean canDecode(ByteBuf buffer, TypeInformation type) {\n\n        int readerIndex = buffer.readerIndex();\n        try {\n            return doCanDecode(buffer, type);\n        } finally {\n            buffer.readerIndex(readerIndex);\n        }\n    }\n\n    /**\n     * Encode length or PLP_NULL.\n     *\n     * @param buffer the data buffer.\n     */\n    public void encode(ByteBuf buffer) {\n\n        if (isNull()) {\n            Encode.uLongLong(buffer, PLP_NULL);\n        } else {\n            Encode.uLongLong(buffer, getLength());\n        }\n    }\n\n    private static boolean doCanDecode(ByteBuf buffer, TypeInformation type) {\n\n        if (type.getLengthStrategy() == LengthStrategy.PARTLENTYPE) {\n            return buffer.readableBytes() >= 8;\n        }\n\n        throw ProtocolException.invalidTds(\"Cannot parse value LengthDescriptor\");\n    }\n\n    public long getLength() {\n        return this.length;\n    }\n\n    public boolean isNull() {\n        return this.isNull;\n    }\n\n    public boolean isUnknown() {\n        return this.length == UNKNOWN_PLP_LEN;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer();\n        sb.append(getClass().getSimpleName());\n        sb.append(\" [length=\").append(this.length);\n        sb.append(\", isNull=\").append(this.isNull);\n        sb.append(']');\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/SqlServerType.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.spi.Clob;\nimport io.r2dbc.spi.R2dbcType;\nimport io.r2dbc.spi.Type;\nimport reactor.util.annotation.Nullable;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.OffsetDateTime;\nimport java.util.UUID;\n\n/**\n * Enumeration of SQL server data types.\n */\npublic enum SqlServerType implements Type {\n\n    // @formatter:off\n    UNKNOWN(Category.UNKNOWN,               Object.class,  \"unknown\"),\n    TINYINT(Category.NUMERIC,               Byte.class,    \"tinyint\",          1, TdsDataType.INTN, TdsDataType.INT1),\n    BIT(Category.NUMERIC,                   Byte.class,    \"bit\",              1, TdsDataType.INTN, TdsDataType.INT1),\n    SMALLINT(Category.NUMERIC,              Short.class,   \"smallint\",         2, TdsDataType.INTN, TdsDataType.INT2),\n    INTEGER(Category.NUMERIC,               Integer.class, \"int\",              4, TdsDataType.INTN, TdsDataType.INT4),\n    BIGINT(Category.NUMERIC,                Long.class,    \"bigint\",           8, TdsDataType.INTN, TdsDataType.INT8),\n    FLOAT(Category.NUMERIC,                 Double.class,  \"float\",            8, TdsDataType.FLOATN, TdsDataType.FLOAT8),\n    REAL(Category.NUMERIC,                  Float.class,   \"real\",             4, TdsDataType.FLOATN, TdsDataType.FLOAT4),\n    SMALLDATETIME(Category.DATETIME,        LocalDateTime.class, \"smalldatetime\",    4, TdsDataType.DATETIMEN, TdsDataType.DATETIME4),\n    DATETIME(Category.DATETIME,             LocalDateTime.class, \"datetime\",         8, TdsDataType.DATETIMEN, TdsDataType.DATETIME8),\n    DATE(Category.DATE,                     LocalDate.class, \"date\",             3, TdsDataType.DATEN),\n    TIME(Category.TIME,                     LocalTime.class, \"time\",             7, TdsDataType.TIMEN),\n    DATETIME2(Category.DATETIME2,           LocalDateTime.class, \"datetime2\",        7, TdsDataType.DATETIME2N),\n    DATETIMEOFFSET(Category.DATETIMEOFFSET, OffsetDateTime.class, \"datetimeoffset\", 7, TdsDataType.DATETIMEOFFSETN),\n    SMALLMONEY(Category.NUMERIC,            BigDecimal.class, \"smallmoney\",       4, TdsDataType.MONEYN, TdsDataType.MONEY4),\n    MONEY(Category.NUMERIC,                 BigDecimal.class, \"money\",            8, TdsDataType.MONEYN, TdsDataType.MONEY8),\n    CHAR(Category.CHARACTER,                String.class,  \"char\"),\n    VARCHAR(Category.CHARACTER,             String.class,  \"varchar\", 8000, TdsDataType.BIGVARCHAR),\n    VARCHARMAX(Category.LONG_CHARACTER,     String.class,  \"varchar\", TdsDataType.BIGVARCHAR),\n    TEXT(Category.LONG_CHARACTER,           Clob.class,    \"text\",                 TdsDataType.TEXT),\n    NCHAR(Category.NCHARACTER,              String.class,  \"nchar\"),\n    NVARCHAR(Category.NCHARACTER,           String.class,  \"nvarchar\",         4000, TdsDataType.NVARCHAR),\n    NVARCHARMAX(Category.LONG_NCHARACTER,   String.class,  \"nvarchar\", TdsDataType.NVARCHAR),\n    NTEXT(Category.LONG_NCHARACTER,         String.class,  \"ntext\", TdsDataType.NTEXT),\n    BINARY(Category.BINARY,                 ByteBuffer.class,\"binary\"),\n    VARBINARY(Category.BINARY,              ByteBuffer.class,\"varbinary\", 8000, TdsDataType.BIGVARBINARY),\n    VARBINARYMAX(Category.LONG_BINARY,      ByteBuffer.class,\"varbinary\", TdsDataType.BIGVARBINARY),\n    IMAGE(Category.LONG_BINARY,             ByteBuffer.class,\"image\", TdsDataType.IMAGE),\n    DECIMAL(Category.NUMERIC,               BigDecimal.class,\"decimal\",          38, TdsDataType.DECIMALN),\n    NUMERIC(Category.NUMERIC,               BigDecimal.class,\"numeric\",          38, TdsDataType.NUMERICN),\n    GUID(Category.GUID,                     UUID.class,    \"uniqueidentifier\", 16, TdsDataType.GUID),\n    SQL_VARIANT(Category.SQL_VARIANT,       Object.class,  \"sql_variant\", TdsDataType.SQL_VARIANT),\n    UDT(Category.UDT,                       Object.class,  \"udt\"),\n    XML(Category.XML,                       Object.class,  \"xml\"),\n    TIMESTAMP(Category.TIMESTAMP,           byte[].class,  \"timestamp\", 8, TdsDataType.BIGBINARY),\n    GEOMETRY(Category.UDT,                  Object.class,  \"geometry\"),\n    GEOGRAPHY(Category.UDT,                 Object.class,  \"geography\");\n    // @formatter:on\n\n    private final Category category;\n\n    private final Class<?> defaultJavaType;\n\n    private final String name;\n\n    private final int maxLength;\n\n    @Nullable\n    private final TdsDataType nullableType;\n\n    private final TdsDataType[] fixedTypes;\n\n    /**\n     * @param category        type category.\n     * @param defaultJavaType default Java type.\n     * @param name            SQL server type name.\n     * @param nullableType    the nullable {@link TdsDataType}.\n     * @param fixedTypes      zero or many fixed-length {@link TdsDataType}s.\n     */\n    SqlServerType(Category category, Class<?> defaultJavaType, String name, TdsDataType nullableType, TdsDataType... fixedTypes) {\n        this(category, defaultJavaType, name, 0, nullableType, fixedTypes);\n    }\n\n    /**\n     * @param category        type category.\n     * @param defaultJavaType default Java type.\n     * @param name            SQL server type name.\n     * @param maxLength       maximal type length.\n     * @param nullableType    the nullable {@link TdsDataType}.\n     * @param fixedTypes      zero or many fixed-length {@link TdsDataType}s.\n     */\n    SqlServerType(Category category, Class<?> defaultJavaType, String name, int maxLength, TdsDataType nullableType, TdsDataType... fixedTypes) {\n\n        Assert.isTrue(nullableType.getLengthStrategy() != LengthStrategy.FIXEDLENTYPE, String.format(\"Type [%s] specified a fixed-length strategy in its nullable type\", name));\n\n        for (TdsDataType fixedType : fixedTypes) {\n            Assert.isTrue(fixedType.getLengthStrategy() == LengthStrategy.FIXEDLENTYPE, String.format(\"Type [%s] specified [%s] in its fixed length type [%s] \", name,\n                fixedType.getLengthStrategy(), fixedType));\n        }\n\n        this.category = category;\n        this.defaultJavaType = defaultJavaType;\n        this.name = name;\n        this.maxLength = maxLength;\n        this.nullableType = nullableType;\n        this.fixedTypes = fixedTypes;\n    }\n\n    SqlServerType(Category category, Class<?> defaultJavaType, String name) {\n\n        this.category = category;\n        this.defaultJavaType = defaultJavaType;\n        this.name = name;\n        this.maxLength = 0;\n        this.nullableType = null;\n        this.fixedTypes = new TdsDataType[0];\n    }\n\n    /**\n     * Resolve a {@link SqlServerType} by its {@code typeName}. Name comparison is case-insensitive.\n     *\n     * @param typeName the type name.\n     * @return the resolved {@link SqlServerType}.\n     * @throws IllegalArgumentException if the type name cannot be resolved to a {@link SqlServerType}\n     */\n    public static SqlServerType of(String typeName) {\n\n        for (SqlServerType type : SqlServerType.values())\n            if (type.name.equalsIgnoreCase(typeName)) {\n                return type;\n            }\n\n        throw new IllegalArgumentException(String.format(\"Unknown type: %s\", typeName));\n    }\n\n    /**\n     * Resolve a {@link SqlServerType} by its {@link R2dbcType}.\n     *\n     * @param type the R2DBC type.\n     * @return the resolved {@link SqlServerType}.\n     * @throws IllegalArgumentException if the type name cannot be resolved to a {@link SqlServerType}\n     */\n    public static SqlServerType of(R2dbcType type) {\n\n        switch (type) {\n\n            case CHAR:\n                return SqlServerType.CHAR;\n            case VARCHAR:\n                return SqlServerType.VARCHAR;\n            case NCHAR:\n                return SqlServerType.NCHAR;\n            case NVARCHAR:\n                return SqlServerType.NVARCHAR;\n            case CLOB:\n                return SqlServerType.TEXT;\n            case NCLOB:\n                return SqlServerType.NTEXT;\n            case BOOLEAN:\n                return SqlServerType.TINYINT;\n            case BINARY:\n                return SqlServerType.BINARY;\n            case VARBINARY:\n                return SqlServerType.VARBINARY;\n            case BLOB:\n                return SqlServerType.IMAGE;\n            case INTEGER:\n                return SqlServerType.INTEGER;\n            case TINYINT:\n                return SqlServerType.TINYINT;\n            case SMALLINT:\n                return SqlServerType.SMALLINT;\n            case BIGINT:\n                return SqlServerType.BIGINT;\n            case NUMERIC:\n                return SqlServerType.NUMERIC;\n            case DECIMAL:\n                return SqlServerType.DECIMAL;\n            case FLOAT:\n            case DOUBLE:\n                return SqlServerType.FLOAT;\n            case REAL:\n                return SqlServerType.REAL;\n            case DATE:\n                return SqlServerType.DATE;\n            case TIME:\n                return SqlServerType.TIME;\n            case TIMESTAMP:\n                return SqlServerType.TIMESTAMP;\n        }\n\n        throw new IllegalArgumentException(String.format(\"Unsupported type: %s\", type));\n    }\n\n    public Category getCategory() {\n        return this.category;\n    }\n\n    @Override\n    public Class<?> getJavaType() {\n        return this.defaultJavaType;\n    }\n\n    public int getMaxLength() {\n        return this.maxLength;\n    }\n\n    @Override\n    public String getName() {\n        return this.name;\n    }\n\n    @Nullable\n    public TdsDataType getNullableType() {\n        return this.nullableType;\n    }\n\n    public TdsDataType[] getFixedTypes() {\n        return this.fixedTypes;\n    }\n\n    /**\n     * Returns the type name.\n     *\n     * @return the type name.\n     */\n    @Override\n    public String toString() {\n        return this.name;\n    }\n\n    /**\n     * Type categories.\n     */\n    public enum Category {\n        // @formatter:off\n        BINARY,\n        CHARACTER,\n        DATE,\n        DATETIME,\n        DATETIME2,\n        DATETIMEOFFSET,\n        GUID,\n        LONG_BINARY,\n        LONG_CHARACTER,\n        LONG_NCHARACTER,\n        NCHARACTER,\n        NUMERIC,\n        UNKNOWN,\n        TIME,\n        TIMESTAMP,\n        UDT,\n        SQL_VARIANT,\n        XML\n        // @formatter:on\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/TdsDataType.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\n/**\n * SQL Server data types as represented within TDS.\n */\npublic enum TdsDataType {\n\n    // @formatter:off\n    // FIXEDLEN types\n    BIT1(0x32, LengthStrategy.FIXEDLENTYPE), // 50\n    INT8(0x7F, LengthStrategy.FIXEDLENTYPE), // 127\n    INT4(0x38, LengthStrategy.FIXEDLENTYPE), // 56\n    INT2(0x34, LengthStrategy.FIXEDLENTYPE), // 52\n    INT1(0x30, LengthStrategy.FIXEDLENTYPE), // 48\n    FLOAT4(0x3B, LengthStrategy.FIXEDLENTYPE), // 59\n    FLOAT8(0x3E, LengthStrategy.FIXEDLENTYPE), // 62\n    DATETIME4(0x3A, LengthStrategy.FIXEDLENTYPE), // 58\n    DATETIME8(0x3D, LengthStrategy.FIXEDLENTYPE), // 61\n    MONEY4(0x7A, LengthStrategy.FIXEDLENTYPE), // 122\n    MONEY8(0x3C, LengthStrategy.FIXEDLENTYPE), // 60\n\n    // BYTELEN types\n    BITN(0x68, LengthStrategy.BYTELENTYPE), // 104\n    INTN(0x26, LengthStrategy.BYTELENTYPE), // 38\n    DECIMALN(0x6A, LengthStrategy.BYTELENTYPE), // 106\n    NUMERICN(0x6C, LengthStrategy.BYTELENTYPE), // 108\n    FLOATN(0x6D, LengthStrategy.BYTELENTYPE), // 109\n    MONEYN(0x6E, LengthStrategy.BYTELENTYPE), // 110\n    DATETIMEN(0x6F, LengthStrategy.BYTELENTYPE), // 111\n    GUID(0x24, LengthStrategy.BYTELENTYPE), // 36\n    DATEN(0x28, LengthStrategy.BYTELENTYPE), // 40\n    TIMEN(0x29, LengthStrategy.BYTELENTYPE), // 41\n    DATETIME2N(0x2a, LengthStrategy.BYTELENTYPE), // 42\n    DATETIMEOFFSETN(0x2b, LengthStrategy.BYTELENTYPE), // 43\n\n    // USHORTLEN type\n    BIGCHAR(0xAF, LengthStrategy.USHORTLENTYPE), // -81\n    BIGVARCHAR(0xA7, LengthStrategy.USHORTLENTYPE), // -89\n    BIGBINARY(0xAD, LengthStrategy.USHORTLENTYPE), // -83\n    BIGVARBINARY(0xA5, LengthStrategy.USHORTLENTYPE), // -91\n    NCHAR(0xEF, LengthStrategy.USHORTLENTYPE), // -17\n    NVARCHAR(0xE7, LengthStrategy.USHORTLENTYPE), // -15\n\n    // PARTLEN types\n    IMAGE(0x22, LengthStrategy.PARTLENTYPE), // 34\n    TEXT(0x23, LengthStrategy.PARTLENTYPE), // 35\n    NTEXT(0x63, LengthStrategy.PARTLENTYPE), // 99\n    UDT(0xF0, LengthStrategy.PARTLENTYPE), // -16\n    XML(0xF1, LengthStrategy.PARTLENTYPE), // -15\n\n    // LONGLEN types\n    SQL_VARIANT(0x62, LengthStrategy.LONGLENTYPE); // 98\n\n    // @formatter:on\n\n    private static final int MAXELEMENTS = 256;\n\n    private static final TdsDataType[] cache = new TdsDataType[MAXELEMENTS];\n\n    private final int value;\n\n    private final LengthStrategy lengthStrategy;\n\n    static {\n        for (TdsDataType s : values())\n            cache[s.value] = s;\n    }\n\n    TdsDataType(int value, LengthStrategy lengthStrategy) {\n        this.value = value;\n        this.lengthStrategy = lengthStrategy;\n    }\n\n    public byte getValue() {\n        return (byte) this.value;\n    }\n\n    public LengthStrategy getLengthStrategy() {\n        return this.lengthStrategy;\n    }\n\n    /**\n     * Return the {@link TdsDataType} by looking it up using the given type value.\n     *\n     * @param value the data type.\n     * @return the {@link TdsDataType}.\n     */\n    static TdsDataType valueOf(int value) {\n\n        if (value > MAXELEMENTS || value < 0 || cache[value] == null) {\n            throw new IllegalArgumentException(String.format(\"Invalid TDS type: %d\", value));\n        }\n\n        return cache[value];\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/TypeBuilder.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.util.Assert;\n\nimport java.util.EnumMap;\nimport java.util.Map;\n\n/**\n * Builders to parse {@link TypeInformation}.\n *\n * @author Mark Paluch\n */\n@SuppressWarnings(\"unused\")\nenum TypeBuilder {\n\n    BIT(TdsDataType.BIT1, TypeDecoderStrategies.create(SqlServerType.BIT, 1, // TDS length (bytes)\n        1, // precision (max numeric precision, in decimal digits)\n        \"1\".length(), // column display size\n        0) // scale\n    ),\n\n    BIGINT(TdsDataType.INT8, TypeDecoderStrategies.create(SqlServerType.BIGINT, 8, // TDS length (bytes)\n        Long.toString(Long.MAX_VALUE).length(), // precision (max numeric precision, in decimal digits)\n        (\"-\" + Long.MAX_VALUE).length(), // column display size (includes sign)\n        0) // scale\n    ),\n\n    INTEGER(TdsDataType.INT4, TypeDecoderStrategies.create(SqlServerType.INTEGER, 4, // TDS length (bytes)\n        Integer.toString(Integer.MAX_VALUE).length(), // precision (max numeric precision, in decimal digits)\n        (\"-\" + Integer.MAX_VALUE).length(), // column display size (includes sign)\n        0) // scale\n    ),\n\n    SMALLINT(TdsDataType.INT2, TypeDecoderStrategies.create(SqlServerType.SMALLINT, 2, // TDS length (bytes)\n        Short.toString(Short.MAX_VALUE).length(), // precision (max numeric precision, in decimal digits)\n        (\"-\" + Short.MAX_VALUE).length(), // column display size (includes sign)\n        0) // scale\n    ),\n\n    TINYINT(TdsDataType.INT1, TypeDecoderStrategies.create(SqlServerType.TINYINT, 1, // TDS length (bytes)\n        Byte.toString(Byte.MAX_VALUE).length(), // precision (max numeric precision, in decimal digits)\n        Byte.toString(Byte.MAX_VALUE).length(), // column display size (no sign - TINYINT is unsigned)\n        0) // scale\n    ),\n\n    REAL(TdsDataType.FLOAT4, TypeDecoderStrategies.create(SqlServerType.REAL, 4, // TDS length (bytes)\n        7, // precision (max numeric precision, in bits)\n        13, // column display size\n        0) // scale\n    ),\n\n    FLOAT(TdsDataType.FLOAT8, TypeDecoderStrategies.create(SqlServerType.FLOAT, 8, // TDS length (bytes)\n        15, // precision (max numeric precision, in bits)\n        22, // column display size\n        0) // scale\n    ),\n\n    SMALLDATETIME(TdsDataType.DATETIME4, TypeDecoderStrategies.create(SqlServerType.SMALLDATETIME, 4, // TDS length (bytes)\n        \"yyyy-mm-dd hh:mm\".length(), // precision (formatted length, in characters, assuming max fractional\n        // seconds precision (0))\n        \"yyyy-mm-dd hh:mm\".length(), // column display size\n        0) // scale\n    ),\n\n    DATETIME(TdsDataType.DATETIME8, TypeDecoderStrategies.create(SqlServerType.DATETIME, 8, // TDS length (bytes)\n        \"yyyy-mm-dd hh:mm:ss.fff\".length(), // precision (formatted length, in characters, assuming max\n        // fractional seconds precision)\n        \"yyyy-mm-dd hh:mm:ss.fff\".length(), // column display size\n        3) // scale\n    ),\n\n    SMALLMONEY(TdsDataType.MONEY4, TypeDecoderStrategies.create(SqlServerType.SMALLMONEY, 4, // TDS length (bytes)\n        Integer.toString(Integer.MAX_VALUE).length(), // precision (max unscaled numeric precision, in decimal\n        // digits)\n        (\"-\" + \".\" + Integer.MAX_VALUE).length(), // column display size (includes sign and\n        // decimal for scale)\n        4) // scale\n    ),\n\n    MONEY(TdsDataType.MONEY8, TypeDecoderStrategies.create(SqlServerType.MONEY, 8, // TDS length (bytes)\n        Long.toString(Long.MAX_VALUE).length(), // precision (max unscaled numeric precision, in decimal digits)\n        (\"-\" + \".\" + Long.MAX_VALUE).length(), // column display size (includes sign and decimal\n        // for scale)\n        4) // scale\n    ),\n\n    BITN(TdsDataType.BITN, new AbstractTypeDecoderStrategy(1) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            if (1 != Decode.uByte(buffer)) {\n                throw ProtocolException.invalidTds(\"Invalid mutability for BITN\");\n            }\n\n            BIT.build(typeInfo, buffer);\n            typeInfo.lengthStrategy = LengthStrategy.BYTELENTYPE;\n        }\n    }\n    ),\n\n    INTN(TdsDataType.INTN, new AbstractTypeDecoderStrategy(1) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            int intType = Decode.uByte(buffer);\n\n            switch (intType) {\n                case 8:\n                    BIGINT.build(typeInfo, buffer);\n                    break;\n                case 4:\n                    INTEGER.build(typeInfo, buffer);\n                    break;\n                case 2:\n                    SMALLINT.build(typeInfo, buffer);\n                    break;\n                case 1:\n                    TINYINT.build(typeInfo, buffer);\n                    break;\n                default:\n                    throw ProtocolException.invalidTds(String.format(\"Unsupported INTN type %s\", intType));\n            }\n\n            typeInfo.lengthStrategy = LengthStrategy.BYTELENTYPE;\n        }\n    }\n    ),\n\n    DECIMAL(TdsDataType.DECIMALN, TypeDecoderStrategies.decimalNumeric(SqlServerType.DECIMAL)),\n    NUMERIC(TdsDataType.NUMERICN, TypeDecoderStrategies.decimalNumeric(SqlServerType.NUMERIC)),\n    FLOATN(TdsDataType.FLOATN, TypeDecoderStrategies.bigOrSmall(FLOAT, REAL)),\n    MONEYN(TdsDataType.MONEYN, TypeDecoderStrategies.bigOrSmall(MONEY, SMALLMONEY)),\n    DATETIMEN(TdsDataType.DATETIMEN, TypeDecoderStrategies.bigOrSmall(DATETIME, SMALLDATETIME)),\n\n    TIME(TdsDataType.TIMEN, TypeDecoderStrategies.temporal(SqlServerType.TIME)),\n    DATETIME2(TdsDataType.DATETIME2N, TypeDecoderStrategies.temporal(SqlServerType.DATETIME2)),\n    DATETIMEOFFSET(TdsDataType.DATETIMEOFFSETN, TypeDecoderStrategies.temporal(SqlServerType.DATETIMEOFFSET)),\n\n    DATE(TdsDataType.DATEN, new AbstractTypeDecoderStrategy(0) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n            typeInfo.serverType = SqlServerType.DATE;\n            typeInfo.lengthStrategy = LengthStrategy.BYTELENTYPE;\n            typeInfo.maxLength = 3;\n            typeInfo.displaySize = typeInfo.precision = \"yyyy-mm-dd\".length();\n        }\n    }),\n\n    BIGBINARY(TdsDataType.BIGBINARY, new AbstractTypeDecoderStrategy(2) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.lengthStrategy = LengthStrategy.USHORTLENTYPE;\n            typeInfo.maxLength = Decode.uShort(buffer);\n            if (typeInfo.maxLength > TypeUtils.SHORT_VARTYPE_MAX_BYTES) {\n                throw ProtocolException.invalidTds(\"Max length exceeds short VARBINARY/VARCHAR type\");\n            }\n            typeInfo.precision = typeInfo.maxLength;\n            typeInfo.displaySize = 2 * typeInfo.maxLength;\n            typeInfo.serverType = (TypeUtils.UDT_TIMESTAMP == typeInfo.userType) ? SqlServerType.TIMESTAMP : SqlServerType.BINARY;\n        }\n    }),\n\n    BIGVARBINARY(TdsDataType.BIGVARBINARY, new AbstractTypeDecoderStrategy(2) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.maxLength = Decode.uShort(buffer);\n\n            if (TypeUtils.MAXTYPE_LENGTH == typeInfo.maxLength)// for PLP types\n            {\n                typeInfo.lengthStrategy = LengthStrategy.PARTLENTYPE;\n                typeInfo.serverType = SqlServerType.VARBINARYMAX;\n                typeInfo.displaySize = typeInfo.precision = TypeUtils.MAX_VARTYPE_MAX_BYTES;\n            } else if (typeInfo.maxLength <= TypeUtils.SHORT_VARTYPE_MAX_BYTES)// for non-PLP types\n            {\n                typeInfo.lengthStrategy = LengthStrategy.USHORTLENTYPE;\n                typeInfo.serverType = SqlServerType.VARBINARY;\n                typeInfo.precision = typeInfo.maxLength;\n                typeInfo.displaySize = 2 * typeInfo.maxLength;\n            } else {\n                throw ProtocolException.invalidTds(\"Cannot parse BIGVARBINARY type info\");\n            }\n        }\n    }),\n\n    IMAGE(TdsDataType.IMAGE, new AbstractTypeDecoderStrategy(4) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.lengthStrategy = LengthStrategy.LONGLENTYPE;\n            typeInfo.maxLength = Decode.asLong(buffer);\n\n            if (typeInfo.maxLength < 0) {\n                throw ProtocolException.invalidTds(\"Negative IMAGE type length\");\n            }\n\n            typeInfo.serverType = SqlServerType.IMAGE;\n            typeInfo.displaySize = typeInfo.precision = Integer.MAX_VALUE;\n        }\n    }),\n\n    BIGCHAR(TdsDataType.BIGCHAR, new AbstractTypeDecoderStrategy(7) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.lengthStrategy = LengthStrategy.USHORTLENTYPE;\n            typeInfo.maxLength = Decode.uShort(buffer);\n\n            if (typeInfo.maxLength > TypeUtils.SHORT_VARTYPE_MAX_BYTES) {\n                throw ProtocolException.invalidTds(String.format(\"BIGCHAR max length exceeded: %d\", typeInfo.maxLength));\n            }\n\n            typeInfo.displaySize = typeInfo.precision = typeInfo.maxLength;\n            typeInfo.serverType = SqlServerType.CHAR;\n            typeInfo.collation = Collation.decode(buffer);\n            typeInfo.charset = typeInfo.collation.getCharset();\n        }\n    }),\n\n    BIGVARCHAR(TdsDataType.BIGVARCHAR, new AbstractTypeDecoderStrategy(7) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.maxLength = Decode.uShort(buffer);\n\n            if (TypeUtils.MAXTYPE_LENGTH == typeInfo.maxLength)// for PLP types\n            {\n                typeInfo.lengthStrategy = LengthStrategy.PARTLENTYPE;\n                typeInfo.serverType = SqlServerType.VARCHARMAX;\n                typeInfo.displaySize = typeInfo.precision = TypeUtils.MAX_VARTYPE_MAX_BYTES;\n            } else if (typeInfo.maxLength <= TypeUtils.SHORT_VARTYPE_MAX_BYTES)// for non-PLP types\n            {\n                typeInfo.lengthStrategy = LengthStrategy.USHORTLENTYPE;\n                typeInfo.serverType = SqlServerType.VARCHAR;\n                typeInfo.displaySize = typeInfo.precision = typeInfo.maxLength;\n            } else {\n                throw ProtocolException.invalidTds(\"Cannot parse BIGVARCHAR type info\");\n            }\n\n            typeInfo.collation = Collation.decode(buffer);\n            typeInfo.charset = typeInfo.collation.getCharset();\n        }\n    }),\n\n    TEXT(TdsDataType.TEXT, new AbstractTypeDecoderStrategy(9) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n            typeInfo.lengthStrategy = LengthStrategy.LONGLENTYPE;\n            typeInfo.maxLength = Decode.asLong(buffer);\n            if (typeInfo.maxLength < 0) {\n                throw ProtocolException.invalidTds(\"Negative TEXT type length\");\n            }\n            typeInfo.serverType = SqlServerType.TEXT;\n            typeInfo.displaySize = typeInfo.precision = Integer.MAX_VALUE;\n            typeInfo.collation = Collation.decode(buffer);\n            typeInfo.charset = typeInfo.collation.getCharset();\n        }\n    }),\n\n    NCHAR(TdsDataType.NCHAR, new AbstractTypeDecoderStrategy(7) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.lengthStrategy = LengthStrategy.USHORTLENTYPE;\n            typeInfo.maxLength = Decode.uShort(buffer);\n            if (typeInfo.maxLength > TypeUtils.SHORT_VARTYPE_MAX_BYTES || 0 != typeInfo.maxLength % 2) {\n                throw ProtocolException.invalidTds(\"Invalid NCHAR length\");\n            }\n            typeInfo.displaySize = typeInfo.precision = typeInfo.maxLength / 2;\n            typeInfo.serverType = SqlServerType.NCHAR;\n            typeInfo.collation = Collation.decode(buffer);\n            typeInfo.charset = ServerCharset.UNICODE.charset();\n        }\n    }),\n\n    NVARCHAR(TdsDataType.NVARCHAR, new AbstractTypeDecoderStrategy(7) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.maxLength = Decode.uShort(buffer);\n            if (TypeUtils.MAXTYPE_LENGTH == typeInfo.maxLength)// for PLP types\n            {\n                typeInfo.lengthStrategy = LengthStrategy.PARTLENTYPE;\n                typeInfo.serverType = SqlServerType.NVARCHARMAX;\n                typeInfo.displaySize = typeInfo.precision = TypeUtils.MAX_VARTYPE_MAX_CHARS;\n            } else if (typeInfo.maxLength <= TypeUtils.SHORT_VARTYPE_MAX_BYTES && 0 == typeInfo.maxLength % 2)// for\n            // non-PLP\n            // types\n            {\n                typeInfo.lengthStrategy = LengthStrategy.USHORTLENTYPE;\n                typeInfo.serverType = SqlServerType.NVARCHAR;\n                typeInfo.displaySize = typeInfo.precision = typeInfo.maxLength / 2;\n            } else {\n                throw ProtocolException.invalidTds(\"Invalid NVARCHAR length\");\n            }\n            typeInfo.collation = Collation.decode(buffer);\n            typeInfo.charset = ServerCharset.UNICODE.charset();\n        }\n    }),\n\n    NTEXT(TdsDataType.NTEXT, new AbstractTypeDecoderStrategy(9) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.lengthStrategy = LengthStrategy.LONGLENTYPE;\n            typeInfo.maxLength = Decode.asLong(buffer);\n\n            if (typeInfo.maxLength < 0) {\n                throw ProtocolException.invalidTds(\"Negative TEXT type length\");\n            }\n\n            typeInfo.serverType = SqlServerType.NTEXT;\n            typeInfo.displaySize = typeInfo.precision = Integer.MAX_VALUE / 2;\n            typeInfo.collation = Collation.decode(buffer);\n            typeInfo.charset = ServerCharset.UNICODE.charset();\n        }\n    }),\n\n    GUID(TdsDataType.GUID, new AbstractTypeDecoderStrategy(1) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            int maxLength = Decode.uByte(buffer);\n            if (maxLength != 16 && maxLength != 0) {\n                throw ProtocolException.invalidTds(\"Negative GUID type length\");\n            }\n\n            typeInfo.lengthStrategy = LengthStrategy.BYTELENTYPE;\n            typeInfo.serverType = SqlServerType.GUID;\n            typeInfo.maxLength = maxLength;\n            typeInfo.displaySize = 36;\n            typeInfo.precision = 36;\n        }\n    }),\n\n    // TODO: UDT, XML\n\n    SQL_VARIANT(TdsDataType.SQL_VARIANT, new AbstractTypeDecoderStrategy(4) {\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n            typeInfo.lengthStrategy = LengthStrategy.LONGLENTYPE; // sql_variant type should be LONGLENTYPE length.\n            typeInfo.maxLength = Decode.asLong(buffer);\n            typeInfo.serverType = SqlServerType.SQL_VARIANT;\n        }\n    });\n\n    private final TdsDataType tdsType;\n\n    private final TypeDecoderStrategy strategy;\n\n    private static final Map<TdsDataType, TypeBuilder> builderMap = new EnumMap<>(TdsDataType.class);\n\n    static {\n        for (TypeBuilder builder : TypeBuilder.values()) {\n            builderMap.put(builder.getTdsDataType(), builder);\n        }\n    }\n\n    TypeBuilder(TdsDataType tdsType, TypeDecoderStrategy strategy) {\n        this.tdsType = tdsType;\n        this.strategy = strategy;\n    }\n\n    /**\n     * Decode {@link TypeInformation} from the {@code ByteBuf}.\n     *\n     * @param buffer    the data {@link ByteBuf buffer}.\n     * @param readFlags {@code true} to decode {@code flags} (typically used when not using encryption).\n     * @return the decoded {@link TypeInformation}.\n     */\n    static TypeInformation decode(ByteBuf buffer, boolean readFlags) {\n\n        MutableTypeInformation typeInfo = new MutableTypeInformation();\n\n        // UserType is USHORT in TDS 7.1 and earlier; ULONG in TDS 7.2 and later.\n        typeInfo.userType = Decode.intBigEndian(buffer);\n\n        if (readFlags) {\n            // Flags (2 bytes)\n            typeInfo.flags = Decode.uShort(buffer);\n        }\n\n        TdsDataType tdsType = TdsDataType.valueOf(Decode.uByte(buffer));\n\n        TypeBuilder typeBuilder = builderMap.get(tdsType);\n\n        if (typeBuilder == null) {\n            throw new IllegalStateException(\"TypeBuilder for \" + tdsType + \" not available\");\n        }\n\n        return typeBuilder.build(typeInfo, buffer);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} contains sufficient readable bytes to decode the {@link TypeInformation}.\n     *\n     * @param buffer  the data buffer.\n     * @param boolean {@code true} to parse type flags.\n     * @return {@code true} if the data buffer contains sufficient readable bytes to decode the {@link TypeInformation}.\n     */\n    static boolean canDecode(ByteBuf buffer, boolean readFlags) {\n\n        int length = 4 /* user type */ + (readFlags ? 2 : 0);\n        int readerIndex = buffer.readerIndex();\n\n        try {\n            if (buffer.readableBytes() >= length + 1) {\n\n                buffer.skipBytes(length);\n\n                TdsDataType tdsType = TdsDataType.valueOf(Decode.uByte(buffer));\n\n                TypeBuilder typeBuilder = builderMap.get(tdsType);\n                return typeBuilder.strategy.canDecode(buffer);\n            }\n\n            return false;\n        } finally {\n            buffer.readerIndex(readerIndex);\n        }\n    }\n\n    TdsDataType getTdsDataType() {\n        return this.tdsType;\n    }\n\n    /**\n     * Build the {@link TypeInformation} by parsing details from {@link ByteBuf}.\n     *\n     * @param typeInfo the type builder.\n     * @param buffer   the data buffer.\n     * @return the built {@link TypeInformation}.\n     * @throws ProtocolException\n     */\n    MutableTypeInformation build(MutableTypeInformation typeInfo, ByteBuf buffer) throws ProtocolException {\n\n        this.strategy.decode(typeInfo, buffer);\n\n        // Postcondition: SqlServerType and SqlServerLength are initialized\n\n        Assert.state(typeInfo.serverType != null, \"Server type must not be null\");\n        Assert.state(typeInfo.lengthStrategy != null, \"Length type must not be null\");\n\n        return typeInfo;\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/TypeDecoderStrategies.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Decode;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\n\n/**\n * Typical type parsing strategies.\n *\n * @author Mark Paluch\n */\ninterface TypeDecoderStrategies {\n\n    /**\n     * Strategy using a byte length ({@code 8} or {@code 4}) for big or small type parsing.\n     *\n     * @param big   big type.\n     * @param small small type.\n     * @return the decoder strategy.\n     */\n    static TypeDecoderStrategy bigOrSmall(TypeBuilder big, TypeBuilder small) {\n        return new BigOrSmallByteLenStrategy(big, small);\n    }\n\n    /**\n     * Strategy for {@link LengthStrategy#FIXEDLENTYPE}.\n     *\n     * @param serverType  the server data type.\n     * @param maxLength   actual length of the type.\n     * @param precision   the precision.\n     * @param displaySize\n     * @param scale\n     * @return the decoder strategy.\n     */\n    static TypeDecoderStrategy create(SqlServerType serverType, int maxLength, int precision, int displaySize, int scale) {\n        return new FixedLenStrategy(serverType, maxLength, precision, displaySize, scale);\n    }\n\n    /**\n     * Strategy for decimal numbers using {@link LengthStrategy#BYTELENTYPE}.\n     *\n     * @param serverType the server data type.\n     * @return the decoder strategy.\n     */\n    static TypeDecoderStrategy decimalNumeric(SqlServerType serverType) {\n        return new DecimalNumericStrategy(serverType);\n    }\n\n    /**\n     * Strategy for temporal types.\n     *\n     * @param serverType the server data type.\n     * @return the decoder strategy.\n     */\n    static TypeDecoderStrategy temporal(SqlServerType serverType) {\n        return new KatmaiScaledTemporalStrategy(serverType);\n    }\n\n    /**\n     * Strategy for {@link LengthStrategy#FIXEDLENTYPE}.\n     */\n    class FixedLenStrategy extends AbstractTypeDecoderStrategy {\n\n        private final SqlServerType serverType;\n\n        private final int maxLength;\n\n        private final int precision;\n\n        private final int displaySize;\n\n        private final int scale;\n\n        FixedLenStrategy(SqlServerType serverType, int maxLength, int precision, int displaySize, int scale) {\n            super(0);\n            this.serverType = serverType;\n            this.maxLength = maxLength;\n            this.precision = precision;\n            this.displaySize = displaySize;\n            this.scale = scale;\n        }\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.lengthStrategy = LengthStrategy.FIXEDLENTYPE;\n            typeInfo.serverType = this.serverType;\n            typeInfo.maxLength = this.maxLength;\n            typeInfo.precision = this.precision;\n            typeInfo.displaySize = this.displaySize;\n            typeInfo.scale = this.scale;\n        }\n\n    }\n\n    /**\n     * Strategy for decimal numbers using {@link LengthStrategy#BYTELENTYPE}.\n     */\n    class DecimalNumericStrategy extends AbstractTypeDecoderStrategy {\n\n        private final SqlServerType serverType;\n\n        DecimalNumericStrategy(SqlServerType serverType) {\n            super(3);\n            this.serverType = serverType;\n        }\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            int maxLength = Decode.uByte(buffer);\n            int precision = Decode.uByte(buffer);\n            int scale = Decode.uByte(buffer);\n\n            if (maxLength > 17) {\n                throw ProtocolException.invalidTds(String.format(\"Invalid maximal length for decimal number type: %d\", maxLength));\n            }\n\n            typeInfo.lengthStrategy = LengthStrategy.BYTELENTYPE;\n            typeInfo.serverType = this.serverType;\n            typeInfo.maxLength = maxLength;\n            typeInfo.precision = precision;\n            typeInfo.displaySize = precision + 2;\n            typeInfo.scale = scale;\n        }\n\n    }\n\n    /**\n     * Strategy using a byte length ({@code 8} or {@code 4}) for big or small type parsing.\n     */\n    class BigOrSmallByteLenStrategy extends AbstractTypeDecoderStrategy {\n\n        private final TypeBuilder bigBuilder;\n\n        private final TypeBuilder smallBuilder;\n\n        BigOrSmallByteLenStrategy(TypeBuilder bigBuilder, TypeBuilder smallBuilder) {\n            super(1);\n            this.bigBuilder = bigBuilder;\n            this.smallBuilder = smallBuilder;\n        }\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            int length = Decode.uByte(buffer);\n\n            switch (length) // maxLength\n            {\n                case 8:\n                    this.bigBuilder.build(typeInfo, buffer);\n                    break;\n                case 4:\n                    this.smallBuilder.build(typeInfo, buffer);\n                    break;\n                default:\n                    throw ProtocolException.invalidTds(String.format(\"Unsupported length for Big/Small strategy: %d\", length));\n            }\n\n            typeInfo.lengthStrategy = LengthStrategy.BYTELENTYPE;\n        }\n\n    }\n\n    /**\n     * Strategy for temporal types.\n     */\n    class KatmaiScaledTemporalStrategy extends AbstractTypeDecoderStrategy {\n\n        private final SqlServerType serverType;\n\n        KatmaiScaledTemporalStrategy(SqlServerType serverType) {\n            super(1);\n            this.serverType = serverType;\n        }\n\n        private int getPrecision(String baseFormat, int scale) {\n            // For 0-scale temporal, there is no '.' after the seconds component because there are no sub-seconds.\n            // Example: 12:34:56.12134 includes a '.', but 12:34:56 doesn't\n            return baseFormat.length() + ((scale > 0) ? (1 + scale) : 0);\n        }\n\n        @Override\n        public void decode(MutableTypeInformation typeInfo, ByteBuf buffer) {\n\n            typeInfo.scale = Decode.uByte(buffer);\n            if (typeInfo.scale > TypeUtils.MAX_FRACTIONAL_SECONDS_SCALE) {\n                throw ProtocolException.invalidTds(String.format(\"Unsupported temporal scale: %d\", typeInfo.scale));\n            }\n\n            switch (this.serverType) {\n                case TIME:\n                    typeInfo.precision = getPrecision(\"hh:mm:ss\", typeInfo.scale);\n                    typeInfo.maxLength = TypeUtils.getTimeValueLength(typeInfo.scale);\n                    break;\n\n                case DATETIME2:\n                    typeInfo.precision = getPrecision(\"yyyy-mm-dd hh:mm:ss\", typeInfo.scale);\n                    typeInfo.maxLength = TypeUtils.getDateTimeValueLength(typeInfo.scale);\n                    break;\n\n                case DATETIMEOFFSET:\n                    typeInfo.precision = getPrecision(\"yyyy-mm-dd hh:mm:ss +HH:MM\", typeInfo.scale);\n                    typeInfo.maxLength = TypeUtils.getDatetimeoffsetValueLength(typeInfo.scale);\n                    break;\n\n                default:\n                    throw ProtocolException.invalidTds(String.format(\"Unexpected SQL Server type: %s\", this.serverType));\n            }\n\n            typeInfo.lengthStrategy = LengthStrategy.BYTELENTYPE;\n            typeInfo.serverType = this.serverType;\n            typeInfo.displaySize = typeInfo.precision;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/TypeDecoderStrategy.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n * Interface typically implemented by type decoder implementations that can decode a {@link TypeInformation}.\n */\npublic interface TypeDecoderStrategy {\n\n    /**\n     * Check whether the {@link ByteBuf} contains sufficient readable bytes to decode the {@link TypeInformation}.\n     *\n     * @param buffer the data buffer.\n     * @return {@code true} if the data buffer contains sufficient readable bytes to decode the {@link TypeInformation}.\n     */\n    boolean canDecode(ByteBuf buffer);\n\n    /**\n     * Decode the type information and enhance {@link MutableTypeInformation}.\n     *\n     * @param typeInfo the mutable {@link TypeInformation}.\n     * @param buffer   the data buffer.\n     */\n    void decode(MutableTypeInformation typeInfo, ByteBuf buffer);\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/TypeInformation.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.Assert;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.charset.Charset;\n\n/**\n * Type information for a column following the {@code TYPE_INFO} rule\n *\n * @author Mark Paluch\n * @see Collation\n * @see SqlServerType\n * @see Updatability\n */\npublic interface TypeInformation {\n\n    /**\n     * Decode {@link TypeInformation} from the {@code ByteBuf}.\n     *\n     * @param buffer    the data {@link ByteBuf buffer}.\n     * @param readFlags {@code true} to decode {@code flags} (typically used when not using encryption).\n     * @return the decoded {@link TypeInformation}.\n     */\n    static TypeInformation decode(ByteBuf buffer, boolean readFlags) {\n        return TypeBuilder.decode(buffer, readFlags);\n    }\n\n    /**\n     * Check whether the {@link ByteBuf} contains sufficient readable bytes to decode the {@link TypeInformation}.\n     *\n     * @param buffer    the data buffer.\n     * @param readFlags {@code true} to parse type flags.\n     * @return {@code true} if the data buffer contains sufficient readable bytes to decode the {@link TypeInformation}.\n     */\n    static boolean canDecode(ByteBuf buffer, boolean readFlags) {\n        return TypeBuilder.canDecode(buffer, readFlags);\n    }\n\n    /**\n     * Returns the maximal length.\n     *\n     * @return the maximal length.\n     */\n    int getMaxLength();\n\n    /**\n     * Returns the length {@link LengthStrategy strategy}.\n     *\n     * @return the length {@link LengthStrategy strategy}.\n     */\n    LengthStrategy getLengthStrategy();\n\n    /**\n     * Returns the precision.\n     *\n     * @return the precision.\n     */\n    int getPrecision();\n\n    /**\n     * Returns the display size.\n     *\n     * @return the display size.\n     */\n    int getDisplaySize();\n\n    /**\n     * Returns the scale.\n     *\n     * @return the scale.\n     */\n    int getScale();\n\n    /**\n     * Returns the  {@link SqlServerType} base type.\n     *\n     * @return the {@link SqlServerType} base type\n     */\n    SqlServerType getServerType();\n\n    /**\n     * Returns the user type.\n     *\n     * @return the user type.\n     */\n    int getUserType();\n\n    /**\n     * Returns the user type name. Can be {@code null} if this type information is not related to a user type.\n     *\n     * @return the user type name.\n     */\n    @Nullable\n    String getUdtTypeName();\n\n    /**\n     * Returns the {@link Collation}. Can be {@code null} if this type information has no collation details.\n     *\n     * @return the {@link Collation}.\n     */\n    @Nullable\n    Collation getCollation();\n\n    /**\n     * Returns the {@link Charset}. Can be {@code null} if this type information has no collation details.\n     *\n     * @return the {@link Charset}.\n     * @see #getCollation()\n     */\n    @Nullable\n    Charset getCharset();\n\n    /**\n     * Returns the server type name.\n     *\n     * @return the server type name.\n     */\n    String getServerTypeName();\n\n    /**\n     * Returns whether the type is nullable.\n     *\n     * @return {@code true} if the type is nullable.\n     */\n    boolean isNullable();\n\n    /**\n     * Returns whether the type is case-sensitive.\n     *\n     * @return {@code true} if the type is case-sensitive.\n     */\n    boolean isCaseSensitive();\n\n    boolean isSparseColumnSet();\n\n    /**\n     * Returns whether the type is encrypted.\n     *\n     * @return {@code true} if the type is encrypted.\n     */\n    boolean isEncrypted();\n\n    /**\n     * Returns the type {@link Updatability}.\n     *\n     * @return the type {@link Updatability}.\n     */\n    Updatability getUpdatability();\n\n    /**\n     * Returns whether the type is an identity type.\n     *\n     * @return {@code true} if the type is an identity type.\n     */\n    boolean isIdentity();\n\n    /**\n     * Creates a {@link Builder} for {@link TypeInformation}.\n     *\n     * @return a new {@link Builder} to build {@link TypeInformation}.\n     */\n    static Builder builder() {\n        return new Builder();\n    }\n\n    /**\n     * Builder for {@link TypeInformation}.\n     */\n    final class Builder {\n\n        private Charset charset;\n\n        private Collation collation;\n\n        private int displaySize;\n\n        private int flags;\n\n        private LengthStrategy lengthStrategy;\n\n        private int maxLength;\n\n        private int precision;\n\n        private int scale;\n\n        private SqlServerType serverType;\n\n        private String udtTypeName;\n\n        private int userType;\n\n        private Builder() {\n        }\n\n        /**\n         * Configure the {@link Charset}.\n         *\n         * @param charset the charset to use.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withCharset(Charset charset) {\n            this.charset = Assert.requireNonNull(charset, \"Charset must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the {@link Collation}.\n         *\n         * @param collation the collation to use.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withCollation(Collation collation) {\n            this.collation = Assert.requireNonNull(collation, \"Collation must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the display size.\n         *\n         * @param displaySize the display size.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withDisplaySize(int displaySize) {\n            this.displaySize = displaySize;\n            return this;\n        }\n\n        /**\n         * Configure flags.\n         *\n         * @param flags type flags.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withFlags(int flags) {\n            this.flags = flags;\n            return this;\n        }\n\n        /**\n         * Configure the {@link LengthStrategy}.\n         *\n         * @param lengthStrategy the display size.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withLengthStrategy(LengthStrategy lengthStrategy) {\n            this.lengthStrategy = Assert.requireNonNull(lengthStrategy, \"LengthStrategy must not be null\");\n            return this;\n        }\n\n        /**\n         * Configure the maximal maxLength.\n         *\n         * @param maxLength max length.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withMaxLength(int maxLength) {\n            this.maxLength = maxLength;\n            return this;\n        }\n\n        /**\n         * Configure the precision.\n         *\n         * @param precision the precision.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withPrecision(int precision) {\n            this.precision = precision;\n            return this;\n        }\n\n        /**\n         * Configure the scale.\n         *\n         * @param scale the scale.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withScale(int scale) {\n            this.scale = scale;\n            return this;\n        }\n\n        /**\n         * Configure the {@link SqlServerType}.\n         *\n         * @param serverType the server type.\n         * @return {@code this} {@link Builder}.\n         */\n        public Builder withServerType(SqlServerType serverType) {\n            this.serverType = Assert.requireNonNull(serverType, \"SqlServerType must not be null\");\n\n            return this;\n        }\n\n        /**\n         * Build a new {@link TypeInformation}.\n         *\n         * @return a new {@link TypeInformation}.\n         */\n        public TypeInformation build() {\n\n            MutableTypeInformation mutableTypeInformation = new MutableTypeInformation();\n            mutableTypeInformation.lengthStrategy = this.lengthStrategy;\n            mutableTypeInformation.serverType = this.serverType;\n            mutableTypeInformation.flags = this.flags;\n            mutableTypeInformation.maxLength = this.maxLength;\n            mutableTypeInformation.charset = this.charset;\n            mutableTypeInformation.scale = this.scale;\n            mutableTypeInformation.userType = this.userType;\n            mutableTypeInformation.precision = this.precision;\n            mutableTypeInformation.displaySize = this.displaySize;\n            mutableTypeInformation.udtTypeName = this.udtTypeName;\n            mutableTypeInformation.collation = this.collation;\n\n            return mutableTypeInformation;\n        }\n\n    }\n\n    /**\n     * Enumeration of updatability constants.\n     */\n    enum Updatability {\n\n        READ_ONLY(0), READ_WRITE(1), UNKNOWN(2);\n\n        private final byte value;\n\n        Updatability(int value) {\n            this.value = (byte) value;\n        }\n\n        public byte getValue() {\n            return this.value;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/TypeUtils.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.r2dbc.mssql.util.Assert;\n\n/**\n * Helper methods for Type-specific calculations.\n *\n * @author Mark Paluch\n */\npublic final class TypeUtils {\n\n    /* System defined UDTs */\n    static final int UDT_TIMESTAMP = 0x0050;\n\n    /**\n     * Max length in Unicode characters allowed by the \"short\" NVARCHAR type. Values longer than this must use\n     * NVARCHAR(max) (Yukon or later) or NTEXT (Shiloh)\n     */\n    static final int SHORT_VARTYPE_MAX_CHARS = 4000;\n\n    /**\n     * Max length in bytes allowed by the \"short\" VARBINARY/VARCHAR types. Values longer than this must use\n     * VARBINARY(max)/VARCHAR(max) (Yukon or later) or IMAGE/TEXT (Shiloh)\n     */\n    public static final int SHORT_VARTYPE_MAX_BYTES = 8000;\n\n    /**\n     * A type with unlimited max size, known as varchar(max), varbinary(max) and nvarchar(max), which has a max size of\n     * 0xFFFF, defined by PARTLENTYPE.\n     */\n    static final int SQL_USHORTVARMAXLEN = 65535; // 0xFFFF\n\n    /**\n     * From SQL Server 2005 Books Online : ntext, text, and image (Transact-SQL)\n     * https://msdn.microsoft.com/en-us/library/ms187993.aspx\n     * <p>\n     * image \"... through 2^31 - 1 (2,147,483,687) bytes.\"\n     * <p>\n     * text \"... maximum length of 2^31 - 1 (2,147,483,687) characters.\"\n     * <p>\n     * ntext \"... maximum length of 2^30 - 1 (1,073,741,823) characters.\"\n     */\n    static final int NTEXT_MAX_CHARS = 0x3FFFFFFF;\n\n    public static final int IMAGE_TEXT_MAX_BYTES = 0x7FFFFFFF;\n\n    /**\n     * Transact-SQL Data Types: https://msdn.microsoft.com/en-us/library/ms179910.aspx\n     * <p/>\n     * {@literal varbinary(max)} \"max indicates that the maximum storage size is 2<sup>31</sup> - 1 bytes. The storage size is the actual\n     * length of the data entered + 2 bytes.\"\n     * <p/>\n     * {@literal varchar(max)} \"max indicates that the maximum storage size is 2<sup>31</sup> - 1 bytes. The storage size is the actual\n     * length of the data entered + 2 bytes.\"\n     * <p/>\n     * {@literal nvarchar(max)} \"max indicates that the maximum storage size is 2<sup>31</sup> - 1 bytes. The storage size, in bytes, is two\n     * times the number of characters entered + 2 bytes.\"\n     * <p/>\n     * Normally, that would mean that the maximum length of {@literal nvarchar(max)} data is 0x3FFFFFFE characters and that the\n     * maximum length of {@literal varchar(max)} or {@literal varbinary(max)} data is 0x3FFFFFFD bytes. Despite the documentation,\n     * SQL Server returns 2<sup>30</sup> - 1 and 2<sup>31</sup> - 1 respectively as the PRECISION of these types, so use that instead.\n     */\n    static final int MAX_VARTYPE_MAX_CHARS = 0x3FFFFFFF;\n\n    static final int MAX_VARTYPE_MAX_BYTES = 0x7FFFFFFF;\n\n    // Special length indicator for varchar(max), nvarchar(max) and varbinary(max).\n    static final int MAXTYPE_LENGTH = 0xFFFF;\n\n    public static final int UNKNOWN_STREAM_LENGTH = -1;\n\n    public static final int MAX_FRACTIONAL_SECONDS_SCALE = 7;\n\n    public static final int DAYS_INTO_CE_LENGTH = 3;\n\n    public static final int MINUTES_OFFSET_LENGTH = 2;\n\n    // Number of days in a \"normal\" (non-leap) year according to SQL Server.\n    static final int DAYS_PER_YEAR = 365;\n\n    private static final int[] SCALED_TIME_LENGTHS = new int[]{3, 3, 3, 4, 4, 5, 5, 5};\n\n    /**\n     * Returns the length of time values using {@code scale}.\n     *\n     * @param scale the time scale.\n     * @return length of a time value.\n     */\n    public static int getTimeValueLength(int scale) {\n        return getNanosSinceMidnightLength(scale);\n    }\n\n    /**\n     * Returns the length of Date-Time2 values using {@code scale}.\n     *\n     * @param scale the time scale.\n     * @return length of a time value.\n     */\n    public static int getDateTimeValueLength(int scale) {\n        return DAYS_INTO_CE_LENGTH + getNanosSinceMidnightLength(scale);\n    }\n\n    /**\n     * Returns the length of Date-Time offset values using {@code scale}.\n     *\n     * @param scale the scale.\n     * @return length of Date-Time offset values.\n     */\n    static int getDatetimeoffsetValueLength(int scale) {\n        return DAYS_INTO_CE_LENGTH + MINUTES_OFFSET_LENGTH + getNanosSinceMidnightLength(scale);\n    }\n\n    /**\n     * Returns the length of Time offset values using {@code scale}.\n     *\n     * @param scale the scale.\n     * @return\n     */\n    private static int getNanosSinceMidnightLength(int scale) {\n\n        Assert.isTrue(scale >= 0 && scale <= MAX_FRACTIONAL_SECONDS_SCALE, \"Scale must be between 0 and 7\");\n\n        return SCALED_TIME_LENGTHS[scale];\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/message/type/package-info.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Classes to read and build type information.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.message.type;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/package-info.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * An implementation of the Reactive Relational Database Connection API for Microsoft SQL Servers.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/Assert.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport reactor.util.annotation.Nullable;\n\nimport java.util.function.Supplier;\n\n/**\n * Assertion utility class that assists in validating arguments.\n * <p>\n * Useful for identifying programmer errors early and clearly at runtime.\n * <p>\n * For example, if the contract of a public method states it does not allow {@code null} arguments, {@code Assert} can\n * be used to validate that contract. Doing this clearly indicates a contract violation when it occurs and protects the\n * class's invariants.\n * <p>\n * Typically used to validate method arguments rather than configuration properties, to check for cases that are usually\n * programmer errors rather than configuration errors. In contrast to configuration initialization code, there is\n * usually no point in falling back to defaults in such methods.\n * <p>\n * This class is similar to JUnit's assertion library. If an argument value is deemed invalid, an\n * {@link IllegalArgumentException} is thrown (typically). For example:\n *\n * <pre class=\"code\">\n * Assert.notNull(clazz, \"The class must not be null\");\n * Assert.isTrue(i > 0, \"The value must be greater than zero\");\n * </pre>\n * <p>\n * Mainly for internal use within the framework; consider\n * <a href=\"https://commons.apache.org/proper/commons-lang/\">Apache's Commons Lang</a> for a more comprehensive suite of\n * {@code String} utilities.\n *\n * @author Mark Paluch\n */\npublic final class Assert {\n\n    /**\n     * Assert a boolean expression, throwing an {@code IllegalStateException} if the expression evaluates to\n     * {@code false}.\n     * <p>\n     * Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} on an assertion failure.\n     *\n     * <pre class=\"code\">\n     * Assert.state(id == null, \"The id property must not already be initialized\");\n     * </pre>\n     *\n     * @param expression a boolean expression\n     * @param message    the exception message to use if the assertion fails\n     * @throws IllegalStateException if {@code expression} is {@code false}\n     */\n    public static void state(boolean expression, String message) {\n        if (!expression) {\n            throw new IllegalStateException(message);\n        }\n    }\n\n    /**\n     * Assert a boolean expression, throwing an {@code IllegalStateException} if the expression evaluates to\n     * {@code false}.\n     * <p>\n     * Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} on an assertion failure.\n     *\n     * <pre class=\"code\">\n     * Assert.state(id == null, () -&gt; \"ID for \" + entity.getName() + \" must not already be initialized\");\n     * </pre>\n     *\n     * @param expression      a boolean expression\n     * @param messageSupplier a supplier for the exception message to use if the assertion fails\n     * @throws IllegalStateException if {@code expression} is {@code false}\n     */\n    public static void state(boolean expression, Supplier<String> messageSupplier) {\n        if (!expression) {\n            throw new IllegalStateException(nullSafeGet(messageSupplier));\n        }\n    }\n\n    /**\n     * Assert a boolean expression, throwing an {@code IllegalArgumentException} if the expression evaluates to\n     * {@code false}.\n     *\n     * <pre class=\"code\">\n     * Assert.isTrue(i &gt; 0, \"The value must be greater than zero\");\n     * </pre>\n     *\n     * @param expression a boolean expression\n     * @param message    the exception message to use if the assertion fails\n     * @throws IllegalArgumentException if {@code expression} is {@code false}\n     */\n    public static void isTrue(boolean expression, String message) {\n        if (!expression) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    /**\n     * Assert a boolean expression, throwing an {@code IllegalArgumentException} if the expression evaluates to\n     * {@code false}.\n     *\n     * <pre class=\"code\">\n     * Assert.isTrue(i &gt; 0, () -&gt; \"The value '\" + i + \"' must be greater than zero\");\n     * </pre>\n     *\n     * @param expression      a boolean expression\n     * @param messageSupplier a supplier for the exception message to use if the assertion fails\n     * @throws IllegalArgumentException if {@code expression} is {@code false}\n     */\n    public static void isTrue(boolean expression, Supplier<String> messageSupplier) {\n        if (!expression) {\n            throw new IllegalArgumentException(nullSafeGet(messageSupplier));\n        }\n    }\n\n    /**\n     * Assert that an object is {@code null}.\n     *\n     * <pre class=\"code\">\n     * Assert.isNull(value, \"The value must be null\");\n     * </pre>\n     *\n     * @param object  the object to check\n     * @param message the exception message to use if the assertion fails\n     * @throws IllegalArgumentException if the object is not {@code null}\n     */\n    public static void isNull(@Nullable Object object, String message) {\n        if (object != null) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    /**\n     * Assert that an object is {@code null}.\n     *\n     * <pre class=\"code\">\n     * Assert.isNull(value, () -&gt; \"The value '\" + value + \"' must be null\");\n     * </pre>\n     *\n     * @param object          the object to check\n     * @param messageSupplier a supplier for the exception message to use if the assertion fails\n     * @throws IllegalArgumentException if the object is not {@code null}\n     */\n    public static void isNull(@Nullable Object object, Supplier<String> messageSupplier) {\n        if (object != null) {\n            throw new IllegalArgumentException(nullSafeGet(messageSupplier));\n        }\n    }\n\n    /**\n     * Assert that an object is not {@code null}.\n     *\n     * <pre class=\"code\">\n     * Assert.notNull(clazz, \"The class must not be null\");\n     * </pre>\n     *\n     * @param object  the object to check\n     * @param message the exception message to use if the assertion fails\n     * @throws IllegalArgumentException if the object is {@code null}\n     */\n    public static void notNull(@Nullable Object object, String message) {\n        if (object == null) {\n            throw new IllegalArgumentException(message);\n        }\n    }\n\n    /**\n     * Assert that an object is not {@code null}.\n     *\n     * <pre class=\"code\">\n     * Assert.notNull(clazz, () -&gt; \"The class '\" + clazz.getName() + \"' must not be null\");\n     * </pre>\n     *\n     * @param object          the object to check\n     * @param messageSupplier a supplier for the exception message to use if the assertion fails\n     * @throws IllegalArgumentException if the object is {@code null}\n     */\n    public static void notNull(@Nullable Object object, Supplier<String> messageSupplier) {\n        if (object == null) {\n            throw new IllegalArgumentException(nullSafeGet(messageSupplier));\n        }\n    }\n\n    /**\n     * Assert that an object is not {@code null} and return the non-null instance.\n     *\n     * <pre class=\"code\">\n     * Class&lt?&gt; nonNullObject = Assert.requireNonNull(clazz, \"The class must not be null\");\n     * </pre>\n     *\n     * @param object  the object to check\n     * @param message the exception message to use if the assertion fails\n     * @return the non-null {@code object}\n     * @throws IllegalArgumentException if the object is {@code null}\n     */\n    public static <T> T requireNonNull(@Nullable T object, String message) {\n        notNull(object, message);\n        return object;\n    }\n\n    /**\n     * Assert that an object is not {@code null} and return the non-null instance.\n     *\n     * <pre class=\"code\">\n     * Class&lt?&gt; nonNullObject = Assert.requireNonNull(clazz, () -&gt; \"The class '\" + clazz.getName() + \"' must not be null\");\n     * </pre>\n     *\n     * @param object          the object to check\n     * @param messageSupplier a supplier for the exception message to use if the assertion fails\n     * @return the non-null {@code object}\n     * @throws IllegalArgumentException if the object is {@code null}\n     */\n    public static <T> T requireNonNull(@Nullable T object, Supplier<String> messageSupplier) {\n        notNull(object, messageSupplier);\n        return object;\n    }\n\n    /**\n     * Assert that the provided object is an instance of the provided class.\n     * <pre class=\"code\">Assert.instanceOf(Foo.class, foo, \"Foo expected\");</pre>\n     *\n     * @param type    the type to check against\n     * @param obj     the object to check\n     * @param message a message which will be prepended to provide further context.\n     *                If it is empty or ends in \":\" or \";\" or \",\" or \".\", a full exception message\n     *                will be appended. If it ends in a space, the name of the offending object's\n     *                type will be appended. In any other case, a \":\" with a space and the name\n     *                of the offending object's type will be appended.\n     * @throws IllegalArgumentException if the object is not an instance of type\n     */\n    public static void isInstanceOf(Class<?> type, @Nullable Object obj, String message) {\n        notNull(type, \"Type to check against must not be null\");\n        if (!type.isInstance(obj)) {\n            instanceCheckFailed(type, obj, message);\n        }\n    }\n\n    /**\n     * Assert that the provided object is an instance of the provided class.\n     * <pre class=\"code\">\n     * Assert.instanceOf(Foo.class, foo, () -&gt; \"Processing \" + Foo.class.getSimpleName() + \":\");\n     * </pre>\n     *\n     * @param type            the type to check against\n     * @param obj             the object to check\n     * @param messageSupplier a supplier for the exception message to use if the\n     *                        assertion fails. See {@link #isInstanceOf(Class, Object, String)} for details.\n     * @throws IllegalArgumentException if the object is not an instance of type\n     */\n    public static void isInstanceOf(Class<?> type, @Nullable Object obj, Supplier<String> messageSupplier) {\n        notNull(type, \"Type to check against must not be null\");\n        if (!type.isInstance(obj)) {\n            instanceCheckFailed(type, obj, nullSafeGet(messageSupplier));\n        }\n    }\n\n    /**\n     * Assert that the provided object is an instance of the provided class.\n     * <pre class=\"code\">Assert.instanceOf(Foo.class, foo);</pre>\n     *\n     * @param type the type to check against\n     * @param obj  the object to check\n     * @throws IllegalArgumentException if the object is not an instance of type\n     */\n    public static void isInstanceOf(Class<?> type, @Nullable Object obj) {\n        isInstanceOf(type, obj, \"\");\n    }\n\n    /**\n     * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.\n     * <pre class=\"code\">Assert.isAssignable(Number.class, myClass, \"Number expected\");</pre>\n     *\n     * @param superType the super type to check against\n     * @param subType   the sub type to check\n     * @param message   a message which will be prepended to provide further context.\n     *                  If it is empty or ends in \":\" or \";\" or \",\" or \".\", a full exception message\n     *                  will be appended. If it ends in a space, the name of the offending sub type\n     *                  will be appended. In any other case, a \":\" with a space and the name of the\n     *                  offending sub type will be appended.\n     * @throws IllegalArgumentException if the classes are not assignable\n     */\n    public static void isAssignable(Class<?> superType, @Nullable Class<?> subType, String message) {\n        notNull(superType, \"Super type to check against must not be null\");\n        if (subType == null || !superType.isAssignableFrom(subType)) {\n            assignableCheckFailed(superType, subType, message);\n        }\n    }\n\n    /**\n     * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.\n     * <pre class=\"code\">\n     * Assert.isAssignable(Number.class, myClass, () -&gt; \"Processing \" + myAttributeName + \":\");\n     * </pre>\n     *\n     * @param superType       the super type to check against\n     * @param subType         the sub type to check\n     * @param messageSupplier a supplier for the exception message to use if the\n     *                        assertion fails. See {@link #isAssignable(Class, Class, String)} for details.\n     * @throws IllegalArgumentException if the classes are not assignable\n     */\n    public static void isAssignable(Class<?> superType, @Nullable Class<?> subType, Supplier<String> messageSupplier) {\n        notNull(superType, \"Super type to check against must not be null\");\n        if (subType == null || !superType.isAssignableFrom(subType)) {\n            assignableCheckFailed(superType, subType, nullSafeGet(messageSupplier));\n        }\n    }\n\n    /**\n     * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.\n     * <pre class=\"code\">Assert.isAssignable(Number.class, myClass);</pre>\n     *\n     * @param superType the super type to check\n     * @param subType   the sub type to check\n     * @throws IllegalArgumentException if the classes are not assignable\n     */\n    public static void isAssignable(Class<?> superType, Class<?> subType) {\n        isAssignable(superType, subType, \"\");\n    }\n\n    private static void instanceCheckFailed(Class<?> type, @Nullable Object obj, @Nullable String msg) {\n        String className = (obj != null ? obj.getClass().getName() : \"null\");\n        String result = \"\";\n        boolean defaultMessage = true;\n        if (StringUtils.hasLength(msg)) {\n            if (endsWithSeparator(msg)) {\n                result = msg + \" \";\n            } else {\n                result = messageWithTypeName(msg, className);\n                defaultMessage = false;\n            }\n        }\n        if (defaultMessage) {\n            result = result + (\"Object of class [\" + className + \"] must be an instance of \" + type);\n        }\n        throw new IllegalArgumentException(result);\n    }\n\n    private static void assignableCheckFailed(Class<?> superType, @Nullable Class<?> subType, @Nullable String msg) {\n        String result = \"\";\n        boolean defaultMessage = true;\n        if (StringUtils.hasLength(msg)) {\n            if (endsWithSeparator(msg)) {\n                result = msg + \" \";\n            } else {\n                result = messageWithTypeName(msg, subType);\n                defaultMessage = false;\n            }\n        }\n        if (defaultMessage) {\n            result = result + (subType + \" is not assignable to \" + superType);\n        }\n        throw new IllegalArgumentException(result);\n    }\n\n    private static boolean endsWithSeparator(String msg) {\n        return (msg.endsWith(\":\") || msg.endsWith(\";\") || msg.endsWith(\",\") || msg.endsWith(\".\"));\n    }\n\n    private static String messageWithTypeName(String msg, @Nullable Object typeName) {\n        return msg + (msg.endsWith(\" \") ? \"\" : \": \") + typeName;\n    }\n\n    @Nullable\n    private static String nullSafeGet(@Nullable Supplier<String> messageSupplier) {\n        return (messageSupplier != null ? messageSupplier.get() : null);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/DriverVersion.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport reactor.util.annotation.Nullable;\n\n/**\n * Class that exposes the driver version. Fetches the \"Implementation-Version\" manifest attribute from the jar file.\n * <p>\n * Note that some ClassLoaders do not expose the package metadata, hence this class might not be able to determine the\n * driver version in all environments.\n */\npublic final class DriverVersion {\n\n    private DriverVersion() {\n    }\n\n    /**\n     * Return the full version string of the present Spring codebase, or {@code null} if it cannot be determined.\n     *\n     * @see Package#getImplementationVersion()\n     */\n    @Nullable\n    public static Version getVersion() {\n        Package pkg = DriverVersion.class.getPackage();\n        return (pkg != null && pkg.getImplementationVersion() != null ? Version.parse(pkg.getImplementationVersion())\n            : null);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/FluxDiscardOnCancel.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport org.reactivestreams.Subscriber;\nimport org.reactivestreams.Subscription;\nimport reactor.core.CoreSubscriber;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.FluxOperator;\nimport reactor.core.publisher.Operators;\nimport reactor.util.Logger;\nimport reactor.util.Loggers;\nimport reactor.util.context.Context;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * A decorating operator that replays signals from its source to a {@link Subscriber} and drains the source upon {@link Subscription#cancel() cancel} and drops data signals until termination.\n * Draining data is required to complete a particular request/response window and clear the protocol state as client code expects to start a request/response conversation without any previous\n * response state.\n *\n * @param <T> produced type\n * @author Mark Paluch\n */\nclass FluxDiscardOnCancel<T> extends FluxOperator<T, T> {\n\n    private static final Logger logger = Loggers.getLogger(FluxDiscardOnCancel.class);\n\n    private final Runnable cancelConsumer;\n\n    FluxDiscardOnCancel(Flux<? extends T> source, Runnable cancelConsumer) {\n        super(source);\n        this.cancelConsumer = cancelConsumer;\n    }\n\n    @Override\n    public void subscribe(CoreSubscriber<? super T> actual) {\n        this.source.subscribe(new FluxDiscardOnCancelSubscriber<>(actual, this.cancelConsumer));\n    }\n\n    static class FluxDiscardOnCancelSubscriber<T> extends AtomicBoolean implements CoreSubscriber<T>, Subscription {\n\n        final CoreSubscriber<T> actual;\n\n        final Context ctx;\n\n        final Runnable cancelConsumer;\n\n        Subscription s;\n\n        FluxDiscardOnCancelSubscriber(CoreSubscriber<T> actual, Runnable cancelConsumer) {\n\n            this.actual = actual;\n            this.ctx = actual.currentContext();\n            this.cancelConsumer = cancelConsumer;\n        }\n\n        @Override\n        public void onSubscribe(Subscription s) {\n\n            if (Operators.validate(this.s, s)) {\n                this.s = s;\n                this.actual.onSubscribe(this);\n            }\n        }\n\n        @Override\n        public Context currentContext() {\n            return this.ctx;\n        }\n\n        @Override\n        public void onNext(T t) {\n\n            if (this.get()) {\n                Operators.onDiscard(t, this.ctx);\n                return;\n            }\n\n            this.actual.onNext(t);\n        }\n\n        @Override\n        public void onError(Throwable t) {\n            if (this.get()) {\n                Operators.onErrorDropped(t, this.ctx);\n            } else {\n                this.actual.onError(t);\n            }\n        }\n\n        @Override\n        public void onComplete() {\n            if (!this.get()) {\n                this.actual.onComplete();\n            }\n        }\n\n        @Override\n        public void request(long n) {\n            this.s.request(n);\n        }\n\n        @Override\n        public void cancel() {\n\n            if (compareAndSet(false, true)) {\n                if (logger.isDebugEnabled()) {\n                    logger.debug(\"received cancel signal\");\n                }\n                try {\n                    this.cancelConsumer.run();\n                } catch (Exception e) {\n                    Operators.onErrorDropped(e, this.ctx);\n                }\n                this.s.request(Long.MAX_VALUE);\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/Operators.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport org.reactivestreams.Subscription;\nimport reactor.core.publisher.Flux;\n\n/**\n * Operator utility.\n *\n * @author Mark Paluch\n */\npublic final class Operators {\n\n    private Operators() {\n    }\n\n    /**\n     * Replay signals from {@link Flux the source} until cancellation. Drains the source for data signals if the subscriber cancels the subscription.\n     * <p>\n     * Draining data is required to complete a particular request/response window and clear the protocol state as client code expects to start a request/response conversation without leaving\n     * previous frames on the stack.\n     *\n     * @param source the source to decorate.\n     * @param <T>    The type of values in both source and output sequences.\n     * @return decorated {@link Flux}.\n     */\n    public static <T> Flux<T> discardOnCancel(Flux<? extends T> source) {\n        return new FluxDiscardOnCancel<>(source, () -> {\n        });\n    }\n\n    /**\n     * Replay signals from {@link Flux the source} until cancellation. Drains the source for data signals if the subscriber cancels the subscription.\n     * <p>\n     * Draining data is required to complete a particular request/response window and clear the protocol state as client code expects to start a request/response conversation without leaving\n     * previous frames on the stack.\n     * <p>Propagates the {@link Subscription#cancel()}  signal to a {@link Runnable consumer}.\n     *\n     * @param source         the source to decorate.\n     * @param cancelConsumer {@link Runnable} notified when the resulting {@link Flux} receives a {@link Subscription#cancel() cancel} signal.\n     * @param <T>            The type of values in both source and output sequences.\n     * @return decorated {@link Flux}.\n     */\n    public static <T> Flux<T> discardOnCancel(Flux<? extends T> source, Runnable cancelConsumer) {\n        return new FluxDiscardOnCancel<>(source, cancelConsumer);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/PredicateUtils.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport java.util.Arrays;\nimport java.util.function.Predicate;\n\n/**\n * Utilities for working with {@link Predicate}s.\n */\npublic final class PredicateUtils {\n\n    private PredicateUtils() {\n    }\n\n    /**\n     * Negates a {@link Predicate}.  Exists primarily to enable negation of method references that are {@link Predicate}s.\n     *\n     * @param t   the predicate to negate\n     * @param <T> the type of element being tested\n     * @return a negated predicate\n     * @throws IllegalArgumentException when {@link Predicate} is {@code null}.\n     * @see Predicate#negate()\n     */\n    public static <T> Predicate<T> not(Predicate<T> t) {\n        Assert.requireNonNull(t, \"t must not be null\");\n\n        return t.negate();\n    }\n\n    /**\n     * Logical OR a collection of {@link Predicate}s.  Exists primarily to enable the logical OR of method references that are {@link Predicate}s.\n     *\n     * @param ts  the predicates to logical OR\n     * @param <T> the type of element being tested\n     * @return a local ORd collection of predicates\n     * @throws IllegalArgumentException when {@link Predicate predicates} is {@code null}.\n     */\n    @SafeVarargs\n    @SuppressWarnings(\"varargs\")\n    public static <T> Predicate<T> or(Predicate<T>... ts) {\n        Assert.requireNonNull(ts, \"ts must not be null\");\n\n        return Arrays.stream(ts).reduce(Predicate::or).orElseThrow(() -> new IllegalStateException(\"Unable to combine predicates together via logical OR\"));\n    }\n\n    /**\n     * Logical AND a collection of {@link Predicate}s.  Exists primarily to enable the logical AND of method references that are {@link Predicate}s.\n     *\n     * @param ts  the predicates to logical AND\n     * @param <T> the type of element being tested\n     * @return a local ANDd collection of predicates\n     * @throws IllegalArgumentException when {@link Predicate predicates} is {@code null}.\n     */\n    @SafeVarargs\n    @SuppressWarnings(\"varargs\")\n    public static <T> Predicate<T> and(Predicate<T>... ts) {\n        Assert.requireNonNull(ts, \"ts must not be null\");\n\n        return Arrays.stream(ts).reduce(Predicate::and).orElseThrow(() -> new IllegalStateException(\"Unable to combine predicates together via logical AND\"));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/ReferenceCountUtil.java",
    "content": "/*\n * Copyright 2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport io.netty.util.ReferenceCounted;\nimport reactor.util.annotation.Nullable;\n\n/**\n * Collection of methods to handle objects that may implement {@link ReferenceCounted}.\n *\n * @author Mark Paluch\n * @since 1.0.3\n */\npublic class ReferenceCountUtil {\n\n    /**\n     * Try to call {@link ReferenceCounted#release()} if the specified object implements {@link ReferenceCounted} and its reference count is greater than zero.\n     * If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing.\n     */\n    public static void maybeRelease(@Nullable Object obj) {\n        if (obj instanceof ReferenceCounted && ((ReferenceCounted) obj).refCnt() > 0) {\n            ((ReferenceCounted) obj).release();\n        }\n    }\n\n    /**\n     * Try to call {@link ReferenceCounted#release()} if the specified object implements {@link ReferenceCounted} and its reference count is greater than zero.\n     * If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing.\n     */\n    public static void maybeSafeRelease(@Nullable Object obj) {\n        if (obj instanceof ReferenceCounted && ((ReferenceCounted) obj).refCnt() > 0) {\n            io.netty.util.ReferenceCountUtil.safeRelease(obj);\n        }\n    }\n\n    // Utility constructor\n    private ReferenceCountUtil() {\n    }\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/StringUtils.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport reactor.util.annotation.Nullable;\n\n/**\n * Miscellaneous {@link String} utility methods.\n *\n * @author Mark Paluch\n */\npublic class StringUtils {\n\n    //---------------------------------------------------------------------\n    // General convenience methods for working with Strings\n    //---------------------------------------------------------------------\n\n    /**\n     * Check whether the given {@code String} is empty.\n     * <p>This method accepts any Object as an argument, comparing it to\n     * {@code null} and the empty String. As a consequence, this method\n     * will never return {@code true} for a non-null non-String object.\n     * <p>The Object signature is useful for general attribute handling code\n     * that commonly deals with Strings but generally has to iterate over\n     * Objects since attributes may e.g. be primitive value objects as well.\n     *\n     * @param str the candidate String\n     */\n    public static boolean isEmpty(@Nullable Object str) {\n        return (str == null || \"\".equals(str));\n    }\n\n    /**\n     * Check that the given {@code CharSequence} is neither {@code null} nor\n     * of length 0.\n     * <p>Note: this method returns {@code true} for a {@code CharSequence}\n     * that purely consists of whitespace.\n     * <p><pre class=\"code\">\n     * StringUtils.hasLength(null) = false\n     * StringUtils.hasLength(\"\") = false\n     * StringUtils.hasLength(\" \") = true\n     * StringUtils.hasLength(\"Hello\") = true\n     * </pre>\n     *\n     * @param str the {@code CharSequence} to check (may be {@code null})\n     * @return {@code true} if the {@code CharSequence} is not {@code null} and has length\n     * @see #hasText(String)\n     */\n    public static boolean hasLength(@Nullable CharSequence str) {\n        return (str != null && str.length() > 0);\n    }\n\n    /**\n     * Check that the given {@code String} is neither {@code null} nor of length 0.\n     * <p>Note: this method returns {@code true} for a {@code String} that\n     * purely consists of whitespace.\n     *\n     * @param str the {@code String} to check (may be {@code null})\n     * @return {@code true} if the {@code String} is not {@code null} and has length\n     * @see #hasLength(CharSequence)\n     * @see #hasText(String)\n     */\n    public static boolean hasLength(@Nullable String str) {\n        return (str != null && !str.isEmpty());\n    }\n\n    /**\n     * Check whether the given {@code CharSequence} contains actual <em>text</em>.\n     * <p>More specifically, this method returns {@code true} if the\n     * {@code CharSequence} is not {@code null}, its length is greater than\n     * 0, and it contains at least one non-whitespace character.\n     * <p><pre class=\"code\">\n     * StringUtils.hasText(null) = false\n     * StringUtils.hasText(\"\") = false\n     * StringUtils.hasText(\" \") = false\n     * StringUtils.hasText(\"12345\") = true\n     * StringUtils.hasText(\" 12345 \") = true\n     * </pre>\n     *\n     * @param str the {@code CharSequence} to check (may be {@code null})\n     * @return {@code true} if the {@code CharSequence} is not {@code null},\n     * its length is greater than 0, and it does not contain whitespace only\n     * @see Character#isWhitespace\n     */\n    public static boolean hasText(@Nullable CharSequence str) {\n        return (str != null && str.length() > 0 && containsText(str));\n    }\n\n    /**\n     * Check whether the given {@code String} contains actual <em>text</em>.\n     * <p>More specifically, this method returns {@code true} if the\n     * {@code String} is not {@code null}, its length is greater than 0,\n     * and it contains at least one non-whitespace character.\n     *\n     * @param str the {@code String} to check (may be {@code null})\n     * @return {@code true} if the {@code String} is not {@code null}, its\n     * length is greater than 0, and it does not contain whitespace only\n     * @see #hasText(CharSequence)\n     */\n    public static boolean hasText(@Nullable String str) {\n        return (str != null && !str.isEmpty() && containsText(str));\n    }\n\n    private static boolean containsText(CharSequence str) {\n        int strLen = str.length();\n        for (int i = 0; i < strLen; i++) {\n            if (!Character.isWhitespace(str.charAt(i))) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/Version.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\n/**\n * Value object representing Version consisting of major, minor and bugfix part.\n */\npublic class Version implements Comparable<Version> {\n\n    private static final String VERSION_PARSE_ERROR = \"Invalid version string! Could not parse segment [%s] within [%s].\";\n\n    private final int major;\n\n    private final int minor;\n\n    private final int bugfix;\n\n    private final int build;\n\n    /**\n     * Creates a new {@link Version} from the given integer values. At least one value has to be given but a maximum of 4.\n     *\n     * @param parts must not be {@code null} or empty.\n     */\n    public Version(int... parts) {\n\n        Assert.notNull(parts, \"Parts must not be null!\");\n        Assert.isTrue(parts.length > 0 && parts.length < 5, \"Parts must contain 1 to 5 segments!\");\n\n        this.major = parts[0];\n        this.minor = parts.length > 1 ? parts[1] : 0;\n        this.bugfix = parts.length > 2 ? parts[2] : 0;\n        this.build = parts.length > 3 ? parts[3] : 0;\n\n        Assert.isTrue(this.major >= 0, \"Major version must be greater or equal zero!\");\n        Assert.isTrue(this.minor >= 0, \"Minor version must be greater or equal zero!\");\n        Assert.isTrue(this.bugfix >= 0, \"Bugfix version must be greater or equal zero!\");\n        Assert.isTrue(this.build >= 0, \"Build version must be greater or equal zero!\");\n    }\n\n    /**\n     * Parses the given string representation of a version into a {@link Version} object.\n     *\n     * @param version must not be {@code null} or empty.\n     * @return\n     */\n    public static Version parse(String version) {\n\n        String[] parts = version.trim().split(\"\\\\.\");\n        int[] intParts = new int[parts.length];\n\n        for (int i = 0; i < parts.length; i++) {\n\n            String input = i == parts.length - 1 ? parts[i].replaceAll(\"\\\\D.*\", \"\") : parts[i];\n\n            if (!input.isEmpty()) {\n                try {\n                    intParts[i] = Integer.parseInt(input);\n                } catch (IllegalArgumentException o_O) {\n                    throw new IllegalArgumentException(String.format(VERSION_PARSE_ERROR, input, version), o_O);\n                }\n            }\n        }\n\n        return new Version(intParts);\n    }\n\n    public int getMajor() {\n        return this.major;\n    }\n\n    public int getMinor() {\n        return this.minor;\n    }\n\n    public int getBugfix() {\n        return this.bugfix;\n    }\n\n    /**\n     * Returns whether the current {@link Version} is greater (newer) than the given one.\n     *\n     * @param version\n     * @return\n     */\n    public boolean isGreaterThan(Version version) {\n        return compareTo(version) > 0;\n    }\n\n    /**\n     * Returns whether the current {@link Version} is greater (newer) or the same as the given one.\n     *\n     * @param version\n     * @return\n     */\n    public boolean isGreaterThanOrEqualTo(Version version) {\n        return compareTo(version) >= 0;\n    }\n\n    /**\n     * Returns whether the current {@link Version} is the same as the given one.\n     *\n     * @param version\n     * @return\n     */\n    public boolean is(Version version) {\n        return equals(version);\n    }\n\n    /**\n     * Returns whether the current {@link Version} is less (older) than the given one.\n     *\n     * @param version\n     * @return\n     */\n    public boolean isLessThan(Version version) {\n        return compareTo(version) < 0;\n    }\n\n    /**\n     * Returns whether the current {@link Version} is less (older) or equal to the current one.\n     *\n     * @param version\n     * @return\n     */\n    public boolean isLessThanOrEqualTo(Version version) {\n        return compareTo(version) <= 0;\n    }\n\n    /*\n     * (non-Javadoc)\n     *\n     * @see java.lang.Comparable#compareTo(java.lang.Object)\n     */\n    public int compareTo(Version that) {\n\n        if (that == null) {\n            return 1;\n        }\n\n        if (this.major != that.major) {\n            return this.major - that.major;\n        }\n\n        if (this.minor != that.minor) {\n            return this.minor - that.minor;\n        }\n\n        if (this.bugfix != that.bugfix) {\n            return this.bugfix - that.bugfix;\n        }\n\n        if (this.build != that.build) {\n            return this.build - that.build;\n        }\n\n        return 0;\n    }\n\n    /*\n     * (non-Javadoc)\n     *\n     * @see java.lang.Object#equals(java.lang.Object)\n     */\n    @Override\n    public boolean equals(Object obj) {\n\n        if (this == obj) {\n            return true;\n        }\n\n        if (!(obj instanceof Version)) {\n            return false;\n        }\n\n        Version that = (Version) obj;\n\n        return this.major == that.major && this.minor == that.minor && this.bugfix == that.bugfix\n            && this.build == that.build;\n    }\n\n    /*\n     * (non-Javadoc)\n     *\n     * @see java.lang.Object#hashCode()\n     */\n    @Override\n    public int hashCode() {\n\n        int result = 17;\n        result += 31 * this.major;\n        result += 31 * this.minor;\n        result += 31 * this.bugfix;\n        result += 31 * this.build;\n        return result;\n    }\n\n    /*\n     * (non-Javadoc)\n     *\n     * @see java.lang.Object#toString()\n     */\n    @Override\n    public String toString() {\n\n        StringBuilder builder = new StringBuilder();\n        builder.append(this.major).append(\".\").append(((this.minor <= 9) ? \"0\" : \"\")).append(this.minor);\n\n        if (this.build != 0 || this.bugfix != 0) {\n            builder.append('.').append(this.bugfix);\n        }\n\n        if (this.build != 0) {\n            builder.append('.').append(this.build);\n        }\n\n        return builder.toString();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/io/r2dbc/mssql/util/package-info.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Utility code used throughout the project.\n */\n\n@NonNullApi\npackage io.r2dbc.mssql.util;\n\nimport reactor.util.annotation.NonNullApi;\n"
  },
  {
    "path": "src/main/resources/META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider",
    "content": "io.r2dbc.mssql.MssqlConnectionFactoryProvider\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/BindingUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.codec.Encoded;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.message.type.TdsDataType;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link Binding}.\n *\n * @author Mark Paluch\n */\nclass BindingUnitTests {\n\n    @Test\n    void shouldReportFormalParameters() {\n\n        Binding binding = new Binding();\n        binding.add(\"foo\", RpcDirection.IN, Encoded.of(TdsDataType.INT8, Unpooled.EMPTY_BUFFER));\n\n        assertThat(binding.getFormalParameters()).isEqualTo(\"@foo bigint\");\n    }\n\n    @Test\n    void shouldReportMultipleFormalParameters() {\n\n        Binding binding = new Binding();\n        binding.add(\"foo\", RpcDirection.IN, Encoded.of(TdsDataType.INT8, Unpooled.EMPTY_BUFFER));\n        binding.add(\"bar\", RpcDirection.IN, Encoded.of(TdsDataType.MONEY8, Unpooled.EMPTY_BUFFER));\n\n        assertThat(binding.getFormalParameters()).isEqualTo(\"@foo bigint,@bar money\");\n    }\n\n    @Test\n    void shouldReportCorrectIntermediateFormalParameters() {\n\n        Binding binding = new Binding();\n\n        binding.add(\"foo\", RpcDirection.IN, Encoded.of(TdsDataType.INT8, Unpooled.EMPTY_BUFFER));\n        assertThat(binding.getFormalParameters()).isEqualTo(\"@foo bigint\");\n\n        binding.add(\"bar\", RpcDirection.IN, Encoded.of(TdsDataType.MONEY8, Unpooled.EMPTY_BUFFER));\n        assertThat(binding.getFormalParameters()).isEqualTo(\"@foo bigint,@bar money\");\n    }\n\n    @Test\n    void shouldBindParameters() {\n\n        Binding binding = new Binding();\n\n        binding.add(\"foo\", RpcDirection.IN, Encoded.of(TdsDataType.INT8, Unpooled.EMPTY_BUFFER));\n\n        assertThat(binding.getParameters()).isNotEmpty();\n        assertThat(binding.isEmpty()).isFalse();\n        assertThat(binding.size()).isEqualTo(1);\n    }\n\n    @Test\n    void shouldClearBindings() {\n\n        Binding binding = new Binding();\n\n        ByteBuf buffer = Unpooled.buffer();\n        binding.add(\"foo\", RpcDirection.IN, Encoded.of(TdsDataType.INT8, buffer));\n        binding.clear();\n\n        assertThat(binding.isEmpty()).isTrue();\n        assertThat(buffer.refCnt()).isZero();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/CodecIntegrationTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.Blob;\nimport io.r2dbc.spi.Clob;\nimport io.r2dbc.spi.ConnectionFactories;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport io.r2dbc.spi.Parameters;\nimport io.r2dbc.spi.R2dbcType;\nimport io.r2dbc.spi.Result;\nimport io.r2dbc.spi.Type;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Hooks;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\nimport reactor.util.annotation.Nullable;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.nio.ByteBuffer;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.OffsetDateTime;\nimport java.time.ZonedDateTime;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.function.Consumer;\nimport java.util.stream.IntStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration tests for {@link DefaultCodecs} testing all known codecs with pre-defined values and {@code null} values.\n *\n * @author Mark Paluch\n */\nclass CodecIntegrationTests extends IntegrationTestSupport {\n\n    static {\n        Hooks.onOperatorDebug();\n    }\n\n    @Test\n    void shouldEncodeBooleanAsBit() {\n        testType(connection, \"BIT\", true);\n    }\n\n    @Test\n    void shouldEncodeBooleanAsTinyint() {\n        testType(connection, \"TINYINT\", true, Boolean.class, (byte) 1);\n    }\n\n    @Test\n    void shouldEncodeByteAsTinyint() {\n        testType(connection, \"TINYINT\", (byte) 0x42);\n    }\n\n    @Test\n    void shouldEncodeShortAsSmallint() {\n        testType(connection, \"SMALLINT\", Short.MAX_VALUE);\n    }\n\n    @Test\n    void shouldEncodeIntegerAInt() {\n        testType(connection, \"INT\", Integer.MAX_VALUE);\n    }\n\n    @Test\n    void shouldEncodeLongABigint() {\n        testType(connection, \"BIGINT\", Long.MAX_VALUE);\n    }\n\n    @Test\n    void shouldEncodeFloatAsReal() {\n        testType(connection, \"REAL\", Float.MAX_VALUE);\n    }\n\n    @Test\n    void shouldEncodeDoubleAsFloat() {\n        testType(connection, \"FLOAT\", Double.MAX_VALUE);\n    }\n\n    @Test\n    void shouldEncodeDoubleAsNumeric() {\n        testType(connection, \"NUMERIC(38,5)\", new BigDecimal(\"12345.12345\"));\n    }\n\n    @Test\n    void shouldEncodeBigIntegerAsNumeric() {\n        testType(connection, \"NUMERIC(38,0)\", new BigInteger(\"12345\"), BigInteger.class, new BigDecimal(\"12345\"));\n    }\n\n    @Test\n    void shouldEncodeDoubleAsDecimal() {\n        testType(connection, \"DECIMAL(38,5)\", new BigDecimal(\"12345.12345\"));\n    }\n\n    @Test\n    void shouldEncodeDoubleAsDecimal1() {\n        testType(connection, \"DECIMAL(38,0)\", new BigDecimal(\"12345\"));\n    }\n\n    @Test\n    void shouldEncodeDate() {\n        testType(connection, \"DATE\", LocalDate.parse(\"2018-11-08\"));\n    }\n\n    @Test\n    void shouldEncodeTime() {\n        testType(connection, \"TIME\", LocalTime.parse(\"11:08:27.1\"));\n    }\n\n    @Test\n    void shouldEncodeDateTime() {\n        testType(connection, \"DATETIME\", LocalDateTime.parse(\"2018-11-08T11:08:28.2\"));\n    }\n\n    @Test\n    void shouldEncodeDateTime2() {\n        testType(connection, \"DATETIME2\", LocalDateTime.parse(\"2018-11-08T11:08:28.2\"));\n    }\n\n    @Test\n    void shouldEncodeZonedDateTimeAsDatetimeoffset() {\n        testType(connection, \"DATETIMEOFFSET\", ZonedDateTime.parse(\"2018-08-27T17:41:14.890+00:45\"), ZonedDateTime.class, OffsetDateTime.parse(\"2018-08-27T17:41:14.890+00:45\"));\n        testType(connection, \"DATETIMEOFFSET\", ZonedDateTime.parse(\"2018-08-27T17:41:14.890-01:45\"), ZonedDateTime.class, OffsetDateTime.parse(\"2018-08-27T17:41:14.890-01:45\"));\n    }\n\n    @Test\n    void shouldEncodeOffsetDateTimeAsDatetimeoffset() {\n        testType(connection, \"DATETIMEOFFSET\", OffsetDateTime.parse(\"2018-08-27T17:41:14.890+00:45\"));\n    }\n\n    @Test\n    void shouldEncodeGuid() {\n        testType(connection, \"uniqueidentifier\", UUID.randomUUID());\n    }\n\n    @Test\n    void shouldEncodeStringAsVarchar() {\n        testType(connection, \"VARCHAR(255)\", \"Hello, World!\");\n        testType(connection, \"VARCHAR(255)\", \"Hello, World!\", R2dbcType.VARCHAR);\n        testType(connection, \"VARCHAR(255)\", \"Hello, World!\", R2dbcType.NVARCHAR);\n        testType(connection, \"VARCHAR(255)\", \"Hello, World!\", SqlServerType.VARCHAR);\n        testType(connection, \"VARCHAR(255)\", \"Hello, World!\", SqlServerType.NVARCHAR);\n    }\n\n    @Test\n    void shouldEncodeStringAsNVarchar() {\n        testType(connection, \"NVARCHAR(255)\", \"Hello, World! äöü\");\n        testType(connection, \"NVARCHAR(255)\", \"Hello, World! äöü\", R2dbcType.NVARCHAR);\n        testType(connection, \"NVARCHAR(255)\", \"Hello, World!äöü\", SqlServerType.NVARCHAR);\n    }\n\n    @Test\n    void shouldEncodeStringAsVarcharSendingCharsAsNatl() {\n\n        ConnectionFactoryOptions options = builder().option(MssqlConnectionFactoryProvider.SEND_STRING_PARAMETERS_AS_UNICODE, false).build();\n        MssqlConnection natlConnection = Mono.from(ConnectionFactories.get(options).create()).cast(MssqlConnection.class).block();\n\n        testType(natlConnection, \"VARCHAR(255)\", \"Hello, World!\");\n\n        natlConnection.close().block();\n    }\n\n    @Test\n    void shouldEncodeStringAsVarcharMax() {\n        testType(connection, \"VARCHAR(MAX)\", \"Hello, World!\");\n    }\n\n    @Test\n    void shouldEncodeStringAsVarcharMaxWithBigString() {\n\n        String template = UUID.randomUUID().toString();\n        StringBuilder builder = new StringBuilder();\n        IntStream.range(0, 1900).forEach(ignore -> builder.append(template));\n\n        assertThat(builder).hasSize(68400);\n\n        testType(connection, \"VARCHAR(MAX)\", builder.toString());\n    }\n\n    @Test\n    void shouldEncodeStringAsNVarcharMax() {\n        testType(connection, \"NVARCHAR(MAX)\", \"Hello, World! äöü\");\n        testType(connection, \"NVARCHAR(MAX)\", \"Hello, World! äöü\", R2dbcType.NVARCHAR);\n        testType(connection, \"NVARCHAR(MAX)\", \"Hello, World! äöü\", SqlServerType.NVARCHARMAX);\n        testType(connection, \"NVARCHAR(MAX)\", \"Hello, World! äöü\", R2dbcType.VARCHAR);\n    }\n\n    @Test\n    void shouldEncodeClobAsNVarcharMax() {\n        testType(connection, \"NVARCHAR(MAX)\", Clob.from(Mono.just(\"Hello, World! äöü\")), Clob.class, actual -> {\n            assertThat(actual).isInstanceOf(Clob.class);\n            Flux.from(((Clob) actual).stream()).as(StepVerifier::create).expectNext(\"Hello, World! äöü\").verifyComplete();\n        }, actual -> {\n            assertThat(actual).isEqualTo(\"Hello, World! äöü\");\n        }, null);\n    }\n\n    @Test\n    void shouldEncodeClobAsVarcharMaxAsNatl() {\n\n        ConnectionFactoryOptions options = builder().option(MssqlConnectionFactoryProvider.SEND_STRING_PARAMETERS_AS_UNICODE, false).build();\n        MssqlConnection natlConnection = Mono.from(ConnectionFactories.get(options).create()).cast(MssqlConnection.class).block();\n\n        testType(natlConnection, \"VARCHAR(MAX)\", Clob.from(Mono.just(\"Hello, World!\")), Clob.class, actual -> {\n            assertThat(actual).isInstanceOf(Clob.class);\n            Flux.from(((Clob) actual).stream()).as(StepVerifier::create).expectNext(\"Hello, World!\").verifyComplete();\n        }, actual -> {\n            assertThat(actual).isEqualTo(\"Hello, World!\");\n        }, null);\n\n        natlConnection.close().block();\n    }\n\n    @Test\n    void shouldEncodeClobAsVarcharMaxAsUnicode() {\n\n        testType(connection, \"VARCHAR(MAX)\", Clob.from(Mono.just(\"Hello, World!\")), Clob.class, actual -> {\n            assertThat(actual).isInstanceOf(Clob.class);\n            Flux.from(((Clob) actual).stream()).as(StepVerifier::create).expectNext(\"Hello, World!\").verifyComplete();\n        }, actual -> {\n            assertThat(actual).isEqualTo(\"Hello, World!\");\n        }, null);\n    }\n\n    @Test\n    void shouldEncodeStringAsText() {\n        testType(connection, \"TEXT\", \"Hello, World!\");\n    }\n\n    @Test\n    void shouldEncodeStringAsNText() {\n        testType(connection, \"NTEXT\", \"Hello, World! äöü\");\n    }\n\n    @Test\n    void shouldEncodeByteBufferAsBinary() {\n        testType(connection, \"BINARY(9)\", ByteBuffer.wrap(\"foobarbaz\".getBytes()));\n    }\n\n    @Test\n    void shouldEncodeByteBufferAsVarBinary() {\n        testType(connection, \"VARBINARY(9)\", ByteBuffer.wrap(\"foobarbaz\".getBytes()));\n    }\n\n    @Test\n    void shouldEncodeByteArrayAsVarBinaryMax() {\n        testType(connection, \"VARBINARY(MAX)\", \"foobarbaz\".getBytes(), byte[].class, actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(\"foobarbaz\".getBytes())));\n        testType(connection, \"VARBINARY(MAX)\", new byte[8000], byte[].class, actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(new byte[8000])));\n        testType(connection, \"VARBINARY(MAX)\", new byte[8001], byte[].class, actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(new byte[8001])));\n        testType(connection, \"VARBINARY(MAX)\", new byte[65534], byte[].class, actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(new byte[65534])));\n        testType(connection, \"VARBINARY(MAX)\", new byte[65535], byte[].class, actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(new byte[65535])));\n        testType(connection, \"VARBINARY(MAX)\", new byte[65536], byte[].class, actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(new byte[65536])));\n    }\n\n    @Test\n    void shouldEncodeBlobAsVarBinaryMax() {\n        testType(connection, \"VARBINARY(MAX)\", Blob.from(Mono.just(ByteBuffer.wrap(\"foobarbaz\".getBytes()))), Blob.class, actual -> {\n\n            assertThat(actual).isInstanceOf(Blob.class);\n            Mono.from(((Blob) actual).discard()).subscribe();\n\n        }, actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(\"foobarbaz\".getBytes())), null);\n    }\n\n    @Test\n    void shouldEncodeByteArrayAsImage() {\n        testType(connection, \"IMAGE\", \"foobarbaz\".getBytes(), byte[].class, actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(\"foobarbaz\".getBytes())));\n    }\n\n    @Test\n    void shouldEncodeByteArrayAsBinary() {\n        testType(connection, \"BINARY(9)\", \"foobarbaz\".getBytes(), byte[].class, ByteBuffer.wrap(\"foobarbaz\".getBytes()));\n    }\n\n    @Test\n    void shouldEncodeByteArrayAsVarBinary() {\n        testType(connection, \"VARBINARY(9)\", \"foobarbaz\".getBytes(), byte[].class, ByteBuffer.wrap(\"foobarbaz\".getBytes()));\n    }\n\n    @Test\n    void shouldEncodeByteBufferAsVarBinaryMax() {\n        testType(connection, \"VARBINARY(MAX)\", ByteBuffer.wrap(\"foobarbaz\".getBytes()), ByteBuffer.class, actual -> {\n            assertThat(actual).isInstanceOf(ByteBuffer.class).isEqualTo(ByteBuffer.wrap(\"foobarbaz\".getBytes()));\n        });\n    }\n\n    @Test\n    void shouldEncodeByteBufferAsImage() {\n        testType(connection, \"IMAGE\", ByteBuffer.wrap(\"foobarbaz\".getBytes()), ByteBuffer.class, actual -> {\n            assertThat(actual).isInstanceOf(ByteBuffer.class).isEqualTo(ByteBuffer.wrap(\"foobarbaz\".getBytes()));\n        });\n    }\n\n    private void testType(MssqlConnection connection, String columnType, Object value) {\n        testType(connection, columnType, value, value.getClass(), value, null);\n    }\n\n    private void testType(MssqlConnection connection, String columnType, Object value, @Nullable Type parameterValueType) {\n        testType(connection, columnType, value, value.getClass(), value, parameterValueType);\n    }\n\n    private void testType(MssqlConnection connection, String columnType, Object value, Class<?> valueClass, Object expectedGetObjectValue) {\n        testType(connection, columnType, value, valueClass, actual -> assertThat(actual).isEqualTo(value), actual -> assertThat(actual).isEqualTo(expectedGetObjectValue), null);\n    }\n\n    private void testType(MssqlConnection connection, String columnType, Object value, Class<?> valueClass, Object expectedGetObjectValue, @Nullable Type parameterValueType) {\n        testType(connection, columnType, value, valueClass, actual -> assertThat(actual).isEqualTo(value), actual -> assertThat(actual).isEqualTo(expectedGetObjectValue), parameterValueType);\n    }\n\n    private void testType(MssqlConnection connection, String columnType, Object value, Class<?> valueClass, Consumer<Object> nativeValueConsumer) {\n        testType(connection, columnType, value, valueClass, actual -> assertThat(actual).isEqualTo(value), nativeValueConsumer, null);\n    }\n\n    private void testType(MssqlConnection connection, String columnType, Object value, Class<?> valueClass, Consumer<Object> expectedValueConsumer, Consumer<Object> nativeValueConsumer,\n                          @Nullable Type parameterValueType) {\n\n        createTable(connection, columnType);\n\n        if (parameterValueType == null) {\n            Flux.from(connection.createStatement(\"INSERT INTO codec_test values(@P0)\")\n                    .bind(\"P0\", value)\n                    .execute())\n                .flatMap(Result::getRowsUpdated)\n                .as(StepVerifier::create)\n                .expectNext(1L)\n                .verifyComplete();\n        } else {\n            Flux.from(connection.createStatement(\"INSERT INTO codec_test values(@P0)\")\n                    .bind(\"P0\", Parameters.in(parameterValueType, value))\n                    .execute())\n                .flatMap(Result::getRowsUpdated)\n                .as(StepVerifier::create)\n                .expectNext(1L)\n                .verifyComplete();\n        }\n\n        if (value instanceof ByteBuffer) {\n            ((ByteBuffer) value).rewind();\n        }\n\n        connection.createStatement(\"SELECT my_col FROM codec_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> (Object) row.get(\"my_col\", valueClass)))\n            .as(StepVerifier::create)\n            .consumeNextWith(expectedValueConsumer)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT my_col FROM codec_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"my_col\")))\n            .as(StepVerifier::create)\n            .consumeNextWith(nativeValueConsumer)\n            .verifyComplete();\n\n        if (parameterValueType == null) {\n            Flux.from(connection.createStatement(\"UPDATE codec_test SET my_col = @P0\")\n                    .bindNull(\"P0\", value.getClass())\n                    .execute())\n                .flatMap(Result::getRowsUpdated)\n                .as(StepVerifier::create)\n                .expectNext(1L)\n                .verifyComplete();\n\n            connection.createStatement(\"SELECT my_col FROM codec_test\")\n                .execute()\n                .flatMap(it -> it.map((row, rowMetadata) -> Optional.ofNullable((Object) row.get(\"my_col\", valueClass))))\n                .as(StepVerifier::create)\n                .expectNext(Optional.empty())\n                .verifyComplete();\n\n            Flux.from(connection.createStatement(\"UPDATE codec_test SET my_col = @P0\")\n                    .bind(\"P0\", Parameters.in(value.getClass()))\n                    .execute())\n                .flatMap(Result::getRowsUpdated)\n                .as(StepVerifier::create)\n                .expectNext(1L)\n                .verifyComplete();\n\n            connection.createStatement(\"SELECT my_col FROM codec_test\")\n                .execute()\n                .flatMap(it -> it.map((row, rowMetadata) -> Optional.ofNullable((Object) row.get(\"my_col\", valueClass))))\n                .as(StepVerifier::create)\n                .expectNext(Optional.empty())\n                .verifyComplete();\n        } else {\n\n            Flux.from(connection.createStatement(\"UPDATE codec_test SET my_col = @P0\")\n                    .bind(\"P0\", Parameters.in(parameterValueType))\n                    .execute())\n                .flatMap(Result::getRowsUpdated)\n                .as(StepVerifier::create)\n                .expectNext(1L)\n                .verifyComplete();\n\n            connection.createStatement(\"SELECT my_col FROM codec_test\")\n                .execute()\n                .flatMap(it -> it.map((row, rowMetadata) -> Optional.ofNullable((Object) row.get(\"my_col\", valueClass))))\n                .as(StepVerifier::create)\n                .expectNext(Optional.empty())\n                .verifyComplete();\n        }\n    }\n\n    private void createTable(MssqlConnection connection, String columnType) {\n\n        connection.createStatement(\"DROP TABLE codec_test\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE codec_test (my_col \" + columnType + \")\")\n                .execute().flatMap(MssqlResult::getRowsUpdated))\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/ColumnMetadataIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.Nullability;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.OffsetDateTime;\nimport java.time.ZonedDateTime;\nimport java.util.UUID;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration tests for {@link MssqlColumnMetadata} testing all known types. This is an integration test because we want the server to produce type information.\n *\n * @author Mark Paluch\n */\nclass ColumnMetadataIntegrationTests extends IntegrationTestSupport {\n\n    @Test\n    void shouldReportBit() {\n        testType(\"BIT\", true, new Expectation(1, 0, Boolean.class));\n    }\n\n    @Test\n    void shouldReportTinyint() {\n        testType(\"TINYINT\", 0x42, new Expectation(3, 0, Byte.class));\n    }\n\n    @Test\n    void shouldReportSmallint() {\n        testType(\"SMALLINT\", Short.MAX_VALUE, new Expectation(5, 0, Short.class));\n    }\n\n    @Test\n    void shouldReportInt() {\n        testType(\"INT\", Integer.MAX_VALUE, new Expectation(10, 0, Integer.class));\n    }\n\n    @Test\n    void shouldReportBigint() {\n        testType(\"BIGINT\", Long.MAX_VALUE, new Expectation(19, 0, Long.class));\n    }\n\n    @Test\n    void shouldReportReal() {\n        testType(\"REAL\", Float.MAX_VALUE, new Expectation(7, 0, Float.class));\n    }\n\n    @Test\n    void shouldReportFloat() {\n        testType(\"FLOAT\", Float.MAX_VALUE, new Expectation(15, 0, Double.class));\n        testType(\"FLOAT(48)\", Float.MAX_VALUE, new Expectation(15, 0, Double.class));\n    }\n\n    @Test\n    void shouldReportNumeric() {\n        testType(\"NUMERIC(38,5)\", new BigDecimal(\"12345.12345\"), new Expectation(38, 5, BigDecimal.class));\n    }\n\n    @Test\n    void shouldReportDecimal() {\n        testType(\"DECIMAL(38,5)\", new BigDecimal(\"12345.12345\"), new Expectation(38, 5, BigDecimal.class));\n    }\n\n    @Test\n    void shouldReportDate() {\n        testType(\"DATE\", LocalDate.parse(\"2018-11-08\"), new Expectation(10, 0, LocalDate.class));\n    }\n\n    @Test\n    void shouldReportTime() {\n        testType(\"TIME\", LocalTime.parse(\"11:08:27.1\"), new Expectation(16, 7, LocalTime.class));\n    }\n\n    @Test\n    void shouldReportDateTime() {\n        testType(\"DATETIME\", LocalDateTime.parse(\"2018-11-08T11:08:28.2\"), new Expectation(23, 3, LocalDateTime.class));\n    }\n\n    @Test\n    void shouldReportDateTime2() {\n        testType(\"DATETIME2\", LocalDateTime.parse(\"2018-11-08T11:08:28.2\"), new Expectation(27, 7, LocalDateTime.class));\n    }\n\n    @Test\n    void shouldReportDatetimeoffset() {\n        testType(\"DATETIMEOFFSET\", ZonedDateTime.parse(\"2018-08-27T17:41:14.890+00:45\"), new Expectation(34, 7, OffsetDateTime.class));\n    }\n\n    @Test\n    void shouldReportUuid() {\n        testType(\"uniqueidentifier\", UUID.randomUUID(), new Expectation(36, 0, UUID.class));\n    }\n\n    @Test\n    void shouldReportVarchar() {\n        testType(\"VARCHAR(255)\", \"Hello, World!\", new Expectation(255, 0, String.class));\n    }\n\n    @Test\n    void shouldEncodeStringAsNVarchar() {\n        testType(\"NVARCHAR(200)\", \"Hello, World!\", new Expectation(200, 0, String.class));\n    }\n\n    private void testType(String columnType, Object value, Expectation expectation) {\n\n        createTable(connection, columnType);\n\n        Flux.from(connection.createStatement(\"INSERT INTO metadata_test values(@P0, @P0)\")\n                .bind(\"P0\", value)\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT non_nullable_col FROM metadata_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> rowMetadata.getColumnMetadata(\"non_nullable_col\")))\n            .as(StepVerifier::create)\n            .consumeNextWith(it -> {\n\n                assertThat(it.getNullability()).isEqualTo(Nullability.NON_NULL);\n                assertThat(it.getPrecision()).isEqualTo(expectation.precision);\n                assertThat(it.getScale()).isEqualTo(expectation.scale);\n                assertThat(it.getJavaType()).isEqualTo(expectation.javaClass);\n\n            })\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT nullable_col FROM metadata_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> rowMetadata.getColumnMetadata(\"nullable_col\")))\n            .as(StepVerifier::create)\n            .consumeNextWith(it -> {\n\n                assertThat(it.getNullability()).isEqualTo(Nullability.NULLABLE);\n                assertThat(it.getPrecision()).isEqualTo(expectation.precision);\n                assertThat(it.getScale()).isEqualTo(expectation.scale);\n                assertThat(it.getJavaType()).isEqualTo(expectation.javaClass);\n\n            })\n            .verifyComplete();\n    }\n\n    private void createTable(MssqlConnection connection, String columnType) {\n\n        connection.createStatement(\"DROP TABLE metadata_test\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE metadata_test (nullable_col \" + columnType + \" NULL, non_nullable_col \" + columnType + \"  NOT NULL)\")\n                .execute().flatMap(MssqlResult::getRowsUpdated))\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    /**\n     * Expectations wrapper.\n     */\n    static class Expectation {\n\n        final int precision;\n\n        final int scale;\n\n        final Class<?> javaClass;\n\n        Expectation(int precision, int scale, Class<?> javaClass) {\n            this.precision = precision;\n            this.scale = scale;\n            this.javaClass = javaClass;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/ConcurrentAccessIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.ColumnMetadata;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * Integration tests for multiple active subscriptions.\n *\n * @author Mark Paluch\n */\nclass ConcurrentAccessIntegrationTests extends IntegrationTestSupport {\n\n    @Test\n    void shouldSerializeMultipleActiveSubscriptions() {\n\n        createTable(connection);\n        connection.beginTransaction().as(StepVerifier::create).verifyComplete();\n\n        Flux<Long> insertOne = insertRecord(connection, 1);\n        Flux<Long> insertTwo = insertRecord(connection, 2);\n        Flux<Long> insertThree = insertRecord(connection, 3);\n\n        Flux.merge(insertOne, insertTwo, insertThree).as(StepVerifier::create).expectNextCount(3).verifyComplete();\n\n        connection.commitTransaction().as(StepVerifier::create).verifyComplete();\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example ORDER BY first_name\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> {\n\n                Map<String, Object> values = new LinkedHashMap<>();\n\n                for (ColumnMetadata column : rowMetadata.getColumnMetadatas()) {\n                    values.put(column.getName(), row.get(column.getName()));\n                }\n\n                return values;\n            }))\n            .as(StepVerifier::create)\n            .expectNextCount(3)\n            .verifyComplete();\n    }\n\n    private void createTable(MssqlConnection connection) {\n\n        connection.createStatement(\"DROP TABLE r2dbc_example\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE r2dbc_example (\" +\n                \"id int PRIMARY KEY, \" +\n                \"first_name varchar(255), \" +\n                \"last_name varchar(255))\")\n                .execute().flatMap(MssqlResult::getRowsUpdated).then())\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    private Flux<Long> insertRecord(MssqlConnection connection, int id) {\n\n        return connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@id, @firstname, @lastname)\")\n            .bind(\"id\", id)\n            .bind(\"firstname\", \"Walter\")\n            .bind(\"lastname\", \"White\")\n            .execute()\n            .flatMap(Result::getRowsUpdated);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/EscapeAwareNameMatcherUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link EscapeAwareNameMatcher}.\n *\n * @author Mark Paluch\n */\nclass EscapeAwareNameMatcherUnitTests {\n\n    List<String> columns = Arrays.asList(\"one\", \"two\", \"three\", \"one\");\n\n    @Test\n    void containsConsidersNamingRules() {\n\n        assertThat(EscapeAwareNameMatcher.find(\"one\", this.columns)).isNotNull();\n        assertThat(EscapeAwareNameMatcher.find(\"[one]\", this.columns)).isNotNull();\n        assertThat(EscapeAwareNameMatcher.find(\"[one\", this.columns)).isNull();\n        assertThat(EscapeAwareNameMatcher.find(\"one]\", this.columns)).isNull();\n        assertThat(EscapeAwareNameMatcher.find(\"[One]\", this.columns)).isNull();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/ExceptionFactoryUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.message.token.InfoToken;\nimport io.r2dbc.spi.R2dbcBadGrammarException;\nimport io.r2dbc.spi.R2dbcDataIntegrityViolationException;\nimport io.r2dbc.spi.R2dbcException;\nimport io.r2dbc.spi.R2dbcNonTransientException;\nimport io.r2dbc.spi.R2dbcPermissionDeniedException;\nimport io.r2dbc.spi.R2dbcRollbackException;\nimport io.r2dbc.spi.R2dbcTransientException;\nimport io.r2dbc.spi.R2dbcTransientResourceException;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ExceptionFactory}.\n *\n * @author Mark Paluch\n */\nclass ExceptionFactoryUnitTests {\n\n    @Test\n    void shouldTranslateWellKnownGrammarErrors() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 130, 0x00, 0x10, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcBadGrammarException.class);\n    }\n\n    @Test\n    void shouldTranslateWellKnownIntegrityViolation() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 2601, 0x00, 0x0E, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcDataIntegrityViolationException.class);\n    }\n\n    @Test\n    void shouldTranslateGeneralPermissionErrors() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 999, 0x00, 0x0E, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcPermissionDeniedException.class);\n    }\n\n    @Test\n    void shouldTranslateWellKnownTransientError() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 701, 0x00, 0x13, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcTransientException.class);\n    }\n\n    @Test\n    void shouldTranslateGeneralGrammarErrors() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 999, 0x00, 0x0B, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcBadGrammarException.class);\n\n        exception = ExceptionFactory.createException(new InfoToken(0, 999, 0x00, 0x0F, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcBadGrammarException.class);\n    }\n\n    @Test\n    void shouldTranslateGeneralIntegrityViolation() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 999, 0x00, 0x0C, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcDataIntegrityViolationException.class);\n    }\n\n    @Test\n    void shouldTranslateGeneralRollbackException() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 999, 0x00, 0x0D, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcRollbackException.class);\n    }\n\n    @Test\n    void shouldTranslateGeneralError() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 999, 0x00, 0x10, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcNonTransientException.class);\n    }\n\n    @Test\n    void shouldTranslateGeneralResourceException() {\n\n        R2dbcException exception = ExceptionFactory.createException(new InfoToken(0, 999, 0x00, 0x11, \"err\", \"\", \"\", 0), \"\");\n\n        assertThat(exception).isInstanceOf(R2dbcTransientResourceException.class);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/GeneratedValuesUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.ssl.SslState;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.ReturnStatus;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.test.StepVerifier;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Unit tests for {@link GeneratedValues}.\n *\n * @author Mark Paluch\n */\nclass GeneratedValuesUnitTests {\n\n    @Test\n    void shouldRejectNullColumns() {\n\n        assertThatThrownBy(() -> GeneratedValues.getGeneratedKeysClause((String[]) null)).isInstanceOf(IllegalArgumentException.class);\n        assertThatThrownBy(() -> GeneratedValues.getGeneratedKeysClause(new String[]{null})).isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    void shouldRejectMultipleColumns() {\n\n        assertThatThrownBy(() -> GeneratedValues.getGeneratedKeysClause(\"foo\", \"bar\")).isInstanceOf(UnsupportedOperationException.class);\n    }\n\n    @Test\n    void shouldAugmentQuery() {\n        assertThat(GeneratedValues.augmentQuery(\"foo\", new String[]{\"bar\"})).isEqualTo(\"foo SELECT SCOPE_IDENTITY() AS bar\");\n    }\n\n    @Test\n    void shouldReportGeneratedKeysExpectation() {\n        assertThat(GeneratedValues.shouldExpectGeneratedKeys(null)).isFalse();\n        assertThat(GeneratedValues.shouldExpectGeneratedKeys(new String[0])).isTrue();\n    }\n\n    @Test\n    void shouldGenerateDefaultClause() {\n        assertThat(GeneratedValues.getGeneratedKeysClause()).isEqualTo(\"SELECT SCOPE_IDENTITY() AS GENERATED_KEYS\");\n    }\n\n    @Test\n    void shouldGenerateCustomizedClause() {\n        assertThat(GeneratedValues.getGeneratedKeysClause(\"foo\")).isEqualTo(\"SELECT SCOPE_IDENTITY() AS foo\");\n    }\n\n    @Test\n    void shouldReorderMessageFlowForGeneratedKeys() {\n\n        ReturnStatus status = ReturnStatus.create(2);\n        DoneToken count = DoneToken.count(10);\n        ReturnStatus finalSegment = ReturnStatus.create(2);\n\n        Flux.just(status, count, SslState.CONNECTION, DoneToken.create(1), finalSegment) //\n            .transform(GeneratedValues::reduceToSingleCountDoneToken) //\n            .as(StepVerifier::create) //\n            .expectNext(status, SslState.CONNECTION, count, finalSegment) //\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/IndefinitePreparedStatementCacheUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link IndefinitePreparedStatementCache}.\n *\n * @author Mark Paluch\n */\nclass IndefinitePreparedStatementCacheUnitTests {\n\n    @Test\n    void shouldReuseCachedStatements() {\n\n        AtomicInteger parseCounter = new AtomicInteger();\n        IndefinitePreparedStatementCache cache = new IndefinitePreparedStatementCache();\n\n        cache.getParsedSql(\"statement\", s -> {\n            parseCounter.incrementAndGet();\n            return Optional.of(s);\n        });\n\n        cache.getParsedSql(\"statement\", s -> {\n            parseCounter.incrementAndGet();\n            return Optional.of(s);\n        });\n\n        assertThat(parseCounter).hasValue(1);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/JsonIntegrationTests.java",
    "content": "/*\n * Copyright 2021-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\n/**\n * Integration tests using JSON as return type.\n *\n * @author Mark Paluch\n */\nclass JsonIntegrationTests extends IntegrationTestSupport {\n\n    @Test\n    void shouldExecuteForJsonSimple() {\n\n        connection.createStatement(\"select 1 as a for json path\").execute()\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0)))\n            .as(StepVerifier::create)\n            .expectNext(\"[{\\\"a\\\":1}]\")\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldExecuteForJsonParametrized() {\n\n        connection.createStatement(\"select 1 as a where @P0 = @P0 for json path\").bind(\"@P0\", true).fetchSize(0).execute()\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0)))\n            .as(StepVerifier::create)\n            .expectNext(\"[{\\\"a\\\":1}]\")\n            .verifyComplete();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/LobIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.Blob;\nimport io.r2dbc.spi.Clob;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.nio.Buffer;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration tests for {@link DefaultCodecs} testing all known codecs with pre-defined values and {@code null} values.\n *\n * @author Mark Paluch\n */\nclass LobIntegrationTests extends IntegrationTestSupport {\n\n    static byte[] ALL_BYTES = new byte[-(-128) + 127];\n\n    static {\n        for (int i = -128; i < 127; i++) {\n            ALL_BYTES[-(-128) + i] = (byte) i;\n        }\n    }\n\n    @Test\n    void testNullBlob() {\n\n        createTable(connection, \"IMAGE\");\n\n        Flux.from(connection.createStatement(\"INSERT INTO lob_test values(@P0)\")\n                .bindNull(\"P0\", Blob.class)\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT my_col FROM lob_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> Optional.ofNullable(row.get(\"my_col\", Blob.class))))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> assertThat(actual).isEqualTo(Optional.empty()))\n            .verifyComplete();\n    }\n\n    @Test\n    void testSmallBlob() {\n\n        createTable(connection, \"IMAGE\");\n\n        Flux.from(connection.createStatement(\"INSERT INTO lob_test values(@P0)\")\n                .bind(\"P0\", Blob.from(Mono.just(\"foo\".getBytes()).map(ByteBuffer::wrap)))\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT my_col FROM lob_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"my_col\", Blob.class)))\n            .flatMap(Blob::stream)\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> assertThat(actual).isEqualTo(ByteBuffer.wrap(\"foo\".getBytes())))\n            .verifyComplete();\n    }\n\n    @Test\n    void testBigBlob() {\n\n        int count = 1500; // ~ 382kb\n        createTable(connection, \"VARBINARY(MAX)\");\n\n        Flux.from(connection.createStatement(\"INSERT INTO lob_test values(@P0)\")\n                .bind(\"P0\", Blob.from(Flux.range(0, count).map(it -> ByteBuffer.wrap(ALL_BYTES))))\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT my_col FROM lob_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"my_col\", Blob.class)))\n            .flatMap(Blob::stream)\n            .map(Buffer::remaining)\n            .collect(Collectors.summingInt(value -> value))\n            .as(StepVerifier::create)\n            .expectNext(count * ALL_BYTES.length)\n            .verifyComplete();\n    }\n\n    @Test\n    void testBigBlobs() {\n\n        int count = 512000;\n        createTable(connection, \"NVARCHAR(MAX)\");\n\n        CharBuffer chars = CharBuffer.allocate(count);\n        IntStream.range(0, count).forEach(i -> chars.put((char) ((i % 26) + 'a')));\n        chars.flip();\n        String data = chars.toString();\n\n        for (int i = 0; i < 30; i++) {\n            Flux.from(connection.createStatement(\"INSERT INTO lob_test values(@P0)\")\n                    .bind(\"P0\", data)\n                    .execute())\n                .flatMap(Result::getRowsUpdated)\n                .as(StepVerifier::create)\n                .expectNext(1L)\n                .verifyComplete();\n\n        }\n\n        connection.createStatement(\"SELECT my_col FROM lob_test\")\n            .execute()\n            .concatMap(it -> it.map((row, rowMetadata) -> row.get(\"my_col\")))\n            .as(StepVerifier::create)\n            .expectNextCount(30)\n            .verifyComplete();\n    }\n\n    @Test\n    void testByteArrayBlob() {\n\n        int count = 1500; // ~ 382kb\n        createTable(connection, \"VARBINARY(MAX)\");\n\n        byte[] bytes = new byte[count * ALL_BYTES.length];\n\n        for (int i = 0; i < count; i++) {\n            System.arraycopy(ALL_BYTES, 0, bytes, i * ALL_BYTES.length, ALL_BYTES.length);\n        }\n\n        Flux.from(connection.createStatement(\"INSERT INTO lob_test values(@P0)\")\n                .bind(\"P0\", bytes)\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT my_col FROM lob_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"my_col\", Blob.class)))\n            .flatMap(Blob::stream)\n            .map(Buffer::remaining)\n            .collect(Collectors.summingInt(value -> value))\n            .as(StepVerifier::create)\n            .expectNext(count * ALL_BYTES.length)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT my_col FROM lob_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"my_col\", byte[].class)))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> assertThat(actual).isEqualTo(bytes))\n            .verifyComplete();\n    }\n\n    @Test\n    void testNullClob() {\n\n        createTable(connection, \"NTEXT\");\n\n        Flux.from(connection.createStatement(\"INSERT INTO lob_test values(@P0)\")\n                .bindNull(\"P0\", Clob.class)\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT my_col FROM lob_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> Optional.ofNullable(row.get(\"my_col\", Clob.class))))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> assertThat(actual).isEqualTo(Optional.empty()))\n            .verifyComplete();\n    }\n\n    @Test\n    void testSmallClob() {\n\n        createTable(connection, \"NTEXT\");\n\n        Flux.from(connection.createStatement(\"INSERT INTO lob_test values(@P0)\")\n                .bind(\"P0\", Clob.from(Mono.just(\"foo\")))\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT my_col FROM lob_test\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"my_col\", Clob.class)))\n            .flatMap(Clob::stream)\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> assertThat(actual).isEqualTo(\"foo\"))\n            .verifyComplete();\n    }\n\n    private void createTable(MssqlConnection connection, String columnType) {\n\n        connection.createStatement(\"DROP TABLE lob_test\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE lob_test (my_col \" + columnType + \")\")\n                .execute().flatMap(MssqlResult::getRowsUpdated))\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/LoginFlowUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.TestClient;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.Prelogin;\nimport io.r2dbc.spi.R2dbcPermissionDeniedException;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link LoginFlow}.\n *\n * @author Mark Paluch\n */\nclass LoginFlowUnitTests {\n\n    @Test\n    void shouldInitiateLogin() {\n\n        List<Prelogin.Token> tokens = new ArrayList<>();\n\n        tokens.add(new Prelogin.Version(14, 0));\n        tokens.add(new Prelogin.Encryption(Prelogin.Encryption.ENCRYPT_NOT_SUP));\n        tokens.add(Prelogin.Terminator.INSTANCE);\n        Prelogin response = new Prelogin(tokens);\n\n        TestClient client = TestClient.builder()\n            .assertNextRequestWith(actual -> assertThat(actual).isInstanceOf(Prelogin.class))\n            .thenRespond(response)\n            .build();\n\n        LoginConfiguration login = new LoginConfiguration(\"app\", null, \"db\", \"host\", \"bar\", \"server\", false, \"foo\");\n\n        LoginFlow.exchange(client, login)\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldFinishLogin() {\n\n        TestClient client = TestClient.builder()\n            .assertNextRequestWith(actual -> assertThat(actual).isInstanceOf(Prelogin.class))\n            .thenRespond(DoneToken.create(0))\n            .build();\n\n        LoginConfiguration login = new LoginConfiguration(\"app\", null, \"db\", \"host\", \"bar\", \"server\", false, \"foo\");\n\n        LoginFlow.exchange(client, login)\n            .as(StepVerifier::create)\n            .expectNext(DoneToken.create(0))\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldPropagateError() {\n\n        TestClient client = TestClient.builder()\n            .assertNextRequestWith(actual -> assertThat(actual).isInstanceOf(Prelogin.class))\n            .thenRespond(new ErrorToken(0, 0, (byte) 0x00, (byte) 0x0E, \"some error\", \"\", \"\", 0))\n            .expectClose()\n            .build();\n\n        LoginConfiguration login = new LoginConfiguration(\"app\", null, \"db\", \"host\", \"bar\", \"server\", false, \"foo\");\n\n        LoginFlow.exchange(client, login)\n            .as(StepVerifier::create)\n            .expectError(R2dbcPermissionDeniedException.class)\n            .verify();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlBatchIntegrationTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Hooks;\nimport reactor.test.StepVerifier;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration tests for {@link MssqlBatch}.\n *\n * @author Mark Paluch\n */\nclass MssqlBatchIntegrationTests extends IntegrationTestSupport {\n\n    static {\n        Hooks.onOperatorDebug();\n    }\n\n    @Test\n    void shouldRunBatchWithMultipleResults() {\n\n        AtomicLong resultCounter = new AtomicLong();\n        AtomicLong firstUpdateCount = new AtomicLong();\n        AtomicLong rowCount = new AtomicLong();\n\n        Flux.from(connection.createBatch().add(\"DECLARE @t TABLE(i INT)\").add(\"INSERT INTO @t VALUES (1),(2),(3)\").add(\"SELECT * FROM @t\")\n            .execute()).flatMap(it -> {\n\n            if (resultCounter.compareAndSet(0, 1)) {\n                return it.getRowsUpdated().doOnNext(firstUpdateCount::set).then();\n            }\n\n            if (resultCounter.incrementAndGet() == 2) {\n                return it.map(((row, rowMetadata) -> {\n                    rowCount.incrementAndGet();\n\n                    return new Object();\n                })).then();\n            }\n\n            throw new IllegalStateException(\"Unexpected result\");\n        }).as(StepVerifier::create).verifyComplete();\n\n        assertThat(resultCounter).hasValue(2);\n        assertThat(firstUpdateCount).hasValue(3);\n        assertThat(rowCount).hasValue(3);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlBatchUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.TestClient;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.SqlBatch;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\n/**\n * Unit tests for {@link MssqlBatch}.\n *\n * @author Mark Paluch\n */\nclass MssqlBatchUnitTests {\n\n    @Test\n    void shouldExecuteSingleBatch() {\n\n        TestClient client = TestClient.builder()\n            .expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"foo\"))\n            .thenRespond(DoneToken.create(1))\n            .build();\n\n        new MssqlBatch(client, new TestConnectionOptions())\n            .add(\"foo\")\n            .execute()\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldExecuteMultiBatch() {\n\n        TestClient client = TestClient.builder()\n            .expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"foo; bar\"))\n            .thenRespond(DoneToken.create(1), DoneToken.create(1))\n            .build();\n\n        new MssqlBatch(client, new TestConnectionOptions())\n            .add(\"foo\")\n            .add(\"bar\")\n            .execute()\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldFailOnExecution() {\n\n        TestClient client = TestClient.builder()\n            .expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"foo\"))\n            .thenRespond(new ErrorToken(1, 1, (byte) 0, (byte) 0, \"error\", \"server\",\n                \"proc\", 0))\n            .build();\n\n        new MssqlBatch(client, new TestConnectionOptions())\n            .add(\"foo\")\n            .execute()\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlCancelIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.reactivestreams.Subscription;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\n/**\n * Integration tests for {@link Subscription subscription cancellation} {@link MssqlConnection} and {@link MssqlStatement}.\n *\n * @author Mark Paluch\n */\nclass MssqlCancelIntegrationTests extends IntegrationTestSupport {\n\n    static boolean initialized = false;\n\n    @BeforeEach\n    void setUp() {\n\n        if (!initialized) {\n\n            createTable(connection, \"r2dbc_example\");\n            createTable(connection, \"r2dbc_empty\");\n\n            for (int i = 0; i < 100; i++) {\n                insertRecord(connection, i);\n            }\n\n            initialized = true;\n        }\n    }\n\n    @Test\n    void shouldCancelUnparametrizedBatch() {\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example\")\n            .fetchSize(0)\n            .execute()\n            .concatMap(it -> it.map((row, metadata) -> row.get(\"id\", Integer.class)))\n            .as(it -> StepVerifier.create(it, 0))\n            .thenRequest(1)\n            .expectNextCount(1)\n            .thenCancel()\n            .verify();\n\n        connection.createStatement(\"SELECT * FROM r2dbc_empty\")\n            .execute()\n            .flatMap(it -> it.map((row, metadata) -> row.get(\"id\")))\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldCancelUnparametrizedCursoredBatch() {\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example\")\n            .fetchSize(10)\n            .execute()\n            .concatMap(it -> it.map((row, metadata) -> row.get(\"id\", Integer.class)))\n            .as(it -> StepVerifier.create(it, 0))\n            .thenRequest(1)\n            .expectNextCount(1)\n            .thenCancel()\n            .verify();\n\n        connection.createStatement(\"SELECT * FROM r2dbc_empty\")\n            .execute()\n            .flatMap(it -> it.map((row, metadata) -> row.get(\"id\")))\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldCancelParametrizedBatch() {\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example where id != @P1\")\n            .bind(\"@P1\", -1)\n            .fetchSize(0)\n            .execute()\n            .concatMap(it -> it.map((row, metadata) -> row.get(\"id\", Integer.class)))\n            .as(it -> StepVerifier.create(it, 0))\n            .thenRequest(1)\n            .expectNextCount(1)\n            .thenCancel()\n            .verify();\n\n        connection.createStatement(\"SELECT * FROM r2dbc_empty\")\n            .execute()\n            .flatMap(it -> it.map((row, metadata) -> row.get(\"id\")))\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldCancelParametrizedCursoredBatch() {\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example where id != @P1\")\n            .bind(\"@P1\", -1)\n            .fetchSize(10)\n            .execute()\n            .concatMap(it -> it.map((row, metadata) -> row.get(\"id\", Integer.class)))\n            .as(it -> StepVerifier.create(it, 0))\n            .thenRequest(1)\n            .expectNextCount(1)\n            .thenCancel()\n            .verify();\n\n        connection.createStatement(\"SELECT * FROM r2dbc_empty\")\n            .execute()\n            .flatMap(it -> it.map((row, metadata) -> row.get(\"id\")))\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    private void createTable(MssqlConnection connection, String table) {\n\n        connection.createStatement(\"DROP TABLE \" + table).execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE \" + table + \" (\" +\n                \"id int PRIMARY KEY, \" +\n                \"first_name varchar(255), \" +\n                \"last_name varchar(255))\")\n                .execute().flatMap(MssqlResult::getRowsUpdated).then())\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    private void insertRecord(MssqlConnection connection, int id) {\n\n        Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@id, @firstname, @lastname)\")\n                .bind(\"id\", id)\n                .bind(\"firstname\", \"Walter\")\n                .bind(\"lastname\", \"White\")\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlConnectionConfigurationUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.message.tds.Redirect;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.testcontainers.shaded.org.bouncycastle.asn1.x500.X500Name;\nimport org.testcontainers.shaded.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\nimport org.testcontainers.shaded.org.bouncycastle.cert.X509CertificateHolder;\nimport org.testcontainers.shaded.org.bouncycastle.cert.X509v3CertificateBuilder;\nimport org.testcontainers.shaded.org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;\nimport org.testcontainers.shaded.org.bouncycastle.operator.ContentSigner;\nimport org.testcontainers.shaded.org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;\nimport reactor.netty.resources.ConnectionProvider;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.math.BigInteger;\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\nimport java.security.KeyStore;\nimport java.security.SecureRandom;\nimport java.security.cert.Certificate;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.UUID;\nimport java.util.function.Predicate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\n\n/**\n * Unit tests for {@link  MssqlConnectionConfiguration}.\n *\n * @author Mark Paluch\n * @author Paul Johe\n */\nfinal class MssqlConnectionConfigurationUnitTests {\n\n    @Test\n    void builderNoApplicationName() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder().applicationName(null))\n                .withMessage(\"applicationName must not be null\");\n    }\n\n    @Test\n    void builderNoConnectionId() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder().connectionId(null))\n                .withMessage(\"connectionId must not be null\");\n    }\n\n    @Test\n    void builderNoHost() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder().host(null))\n                .withMessage(\"host must not be null\");\n    }\n\n    @Test\n    void builderNoPassword() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder().password(null))\n                .withMessage(\"password must not be null\");\n    }\n\n    @Test\n    void builderNoUsername() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder().username(null))\n                .withMessage(\"username must not be null\");\n    }\n\n    @Test\n    void configuration() {\n        UUID connectionId = UUID.randomUUID();\n        Predicate<String> TRUE = s -> true;\n        ConnectionProvider connectionProvider = ConnectionProvider.create(\"test\");\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n                .connectionId(connectionId)\n                .database(\"test-database\")\n                .host(\"test-host\")\n                .password(\"test-password\")\n                .preferCursoredExecution(TRUE)\n                .port(100)\n                .username(\"test-username\")\n                .sendStringParametersAsUnicode(false)\n                .connectionProvider(connectionProvider)\n                .build();\n\n        assertThat(configuration)\n                .hasFieldOrPropertyWithValue(\"connectionId\", connectionId)\n                .hasFieldOrPropertyWithValue(\"connectionProvider\", connectionProvider)\n                .hasFieldOrPropertyWithValue(\"database\", \"test-database\")\n                .hasFieldOrPropertyWithValue(\"host\", \"test-host\")\n                .hasFieldOrPropertyWithValue(\"password\", \"test-password\")\n                .hasFieldOrPropertyWithValue(\"preferCursoredExecution\", TRUE)\n                .hasFieldOrPropertyWithValue(\"port\", 100)\n                .hasFieldOrPropertyWithValue(\"username\", \"test-username\")\n                .hasFieldOrPropertyWithValue(\"sendStringParametersAsUnicode\", false);\n\n    }\n\n    @Test\n    void configurationDefaults() {\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n                .applicationName(\"r2dbc\")\n                .database(\"test-database\")\n                .host(\"test-host\")\n                .password(\"test-password\")\n                .username(\"test-username\")\n                .build();\n\n        assertThat(configuration)\n                .hasFieldOrPropertyWithValue(\"applicationName\", \"r2dbc\")\n                .hasFieldOrPropertyWithValue(\"database\", \"test-database\")\n                .hasFieldOrPropertyWithValue(\"host\", \"test-host\")\n                .hasFieldOrPropertyWithValue(\"password\", \"test-password\")\n                .hasFieldOrPropertyWithValue(\"port\", 1433)\n                .hasFieldOrPropertyWithValue(\"username\", \"test-username\")\n                .hasFieldOrPropertyWithValue(\"sendStringParametersAsUnicode\", true);\n    }\n\n    @Test\n    void constructorNoNoHost() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder()\n                        .password(\"test-password\")\n                        .username(\"test-username\")\n                        .build())\n                .withMessage(\"host must not be null\");\n    }\n\n    @Test\n    void constructorNoPassword() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder()\n                        .host(\"test-host\")\n                        .username(\"test-username\")\n                        .build())\n                .withMessage(\"password must not be null\");\n    }\n\n    @Test\n    void constructorNoUsername() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder()\n                        .host(\"test-host\")\n                        .password(\"test-password\")\n                        .build())\n                .withMessage(\"username must not be null\");\n    }\n\n    @Test\n    void constructorNoSslCustomizer() {\n        assertThatIllegalArgumentException().isThrownBy(() -> MssqlConnectionConfiguration.builder()\n                        .sslContextBuilderCustomizer(null)\n                        .build())\n                .withMessage(\"sslContextBuilderCustomizer must not be null\");\n    }\n\n    @Test\n    void redirect() {\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n                .applicationName(\"r2dbc\")\n                .database(\"test-database\")\n                .host(\"test-host\")\n                .password(\"test-password\")\n                .username(\"test-username\")\n                .build();\n\n        MssqlConnectionConfiguration target = configuration.withRedirect(Redirect.create(\"target\", 1234));\n\n        assertThat(target)\n                .hasFieldOrPropertyWithValue(\"applicationName\", \"r2dbc\")\n                .hasFieldOrPropertyWithValue(\"database\", \"test-database\")\n                .hasFieldOrPropertyWithValue(\"host\", \"target\")\n                .hasFieldOrPropertyWithValue(\"password\", \"test-password\")\n                .hasFieldOrPropertyWithValue(\"port\", 1234)\n                .hasFieldOrPropertyWithValue(\"username\", \"test-username\")\n                .hasFieldOrPropertyWithValue(\"sendStringParametersAsUnicode\", true)\n                .hasFieldOrPropertyWithValue(\"hostNameInCertificate\", \"test-host\");\n    }\n\n    @Test\n    void redirectOtherDomain() {\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n                .applicationName(\"r2dbc\")\n                .database(\"test-database\")\n                .host(\"test-host.windows.net\")\n                .password(\"test-password\")\n                .username(\"test-username\")\n                .build();\n\n        MssqlConnectionConfiguration target = configuration.withRedirect(Redirect.create(\"target.other.domain\", 1234));\n\n        assertThat(target)\n                .hasFieldOrPropertyWithValue(\"applicationName\", \"r2dbc\")\n                .hasFieldOrPropertyWithValue(\"database\", \"test-database\")\n                .hasFieldOrPropertyWithValue(\"host\", \"target.other.domain\")\n                .hasFieldOrPropertyWithValue(\"password\", \"test-password\")\n                .hasFieldOrPropertyWithValue(\"port\", 1234)\n                .hasFieldOrPropertyWithValue(\"username\", \"test-username\")\n                .hasFieldOrPropertyWithValue(\"sendStringParametersAsUnicode\", true)\n                .hasFieldOrPropertyWithValue(\"hostNameInCertificate\", \"test-host.windows.net\");\n    }\n\n    @Test\n    void redirectInDomain() {\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n                .applicationName(\"r2dbc\")\n                .database(\"test-database\")\n                .host(\"test-host.windows.net\")\n                .password(\"test-password\")\n                .username(\"test-username\")\n                .hostNameInCertificate(\"*.windows.net\")\n                .build();\n\n        MssqlConnectionConfiguration target = configuration.withRedirect(Redirect.create(\"worker.target.windows.net\", 1234));\n\n        assertThat(target)\n                .hasFieldOrPropertyWithValue(\"applicationName\", \"r2dbc\")\n                .hasFieldOrPropertyWithValue(\"database\", \"test-database\")\n                .hasFieldOrPropertyWithValue(\"host\", \"worker.target.windows.net\")\n                .hasFieldOrPropertyWithValue(\"password\", \"test-password\")\n                .hasFieldOrPropertyWithValue(\"port\", 1234)\n                .hasFieldOrPropertyWithValue(\"username\", \"test-username\")\n                .hasFieldOrPropertyWithValue(\"sendStringParametersAsUnicode\", true)\n                .hasFieldOrPropertyWithValue(\"hostNameInCertificate\", \"*.target.windows.net\");\n    }\n\n    @Test\n    void configureKeyStore(@TempDir File tempDir) throws Exception {\n\n        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(\"RSA\");\n        keyGen.initialize(1024, new SecureRandom());\n        KeyPair keypair = keyGen.generateKeyPair();\n\n        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());\n        keyStore.load(null, null);\n\n        Certificate selfSignedCertificate = selfSign(keypair, \"CN=dummy\");\n\n        KeyStore.Entry entry = new KeyStore.PrivateKeyEntry(keypair.getPrivate(),\n                new Certificate[]{selfSignedCertificate});\n\n        keyStore.setEntry(\"dummy\", entry, new KeyStore.PasswordProtection(\"key-password\".toCharArray()));\n\n        File file = new File(tempDir, getClass().getName() + \".jks\");\n        try (FileOutputStream stream = new FileOutputStream(file)) {\n            keyStore.store(stream, \"my-password\".toCharArray());\n        }\n\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n                .database(\"test-database\")\n                .host(\"test-host.windows.net\")\n                .password(\"test-password\")\n                .username(\"test-username\")\n                .trustStore(file)\n                .trustStorePassword(\"my-password\".toCharArray())\n                .build();\n\n        MssqlConnectionConfiguration.DefaultClientConfiguration clientConfiguration = (MssqlConnectionConfiguration.DefaultClientConfiguration) configuration.toClientConfiguration();\n\n        KeyStore loaded = clientConfiguration.loadCustomTrustStore();\n\n        KeyStore.Entry loadedEntry = loaded.getEntry(\"dummy\", new KeyStore.PasswordProtection(\"key-password\".toCharArray()));\n        assertThat(loadedEntry).isInstanceOf(KeyStore.PrivateKeyEntry.class);\n    }\n\n    private static Certificate selfSign(KeyPair keyPair, String subjectDN)\n            throws Exception {\n\n        Date startDate = new Date();\n        X500Name dnName = new X500Name(subjectDN);\n\n        Calendar calendar = Calendar.getInstance();\n        calendar.setTime(startDate);\n        calendar.add(Calendar.YEAR, 1);\n        Date endDate = calendar.getTime();\n\n\n        SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair\n                .getPublic().getEncoded());\n\n        X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(dnName,\n                BigInteger.valueOf(1), startDate, endDate, dnName, subjectPublicKeyInfo);\n\n        ContentSigner contentSigner = new JcaContentSignerBuilder(\"SHA256WithRSA\").build(keyPair.getPrivate());\n\n        X509CertificateHolder certificateHolder = certificateBuilder.build(contentSigner);\n\n        return new JcaX509CertificateConverter()\n                .getCertificate(certificateHolder);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"select\", \"SELECT\", \"sElEcT\"})\n    void shouldAcceptQueries(String query) {\n        assertThat(MssqlConnectionConfiguration.DefaultCursorPreference.INSTANCE).accepts(query);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\" select\", \"sp_cursor\", \"INSERT\"})\n    void shouldRejectQueries(String query) {\n        assertThat(MssqlConnectionConfiguration.DefaultCursorPreference.INSTANCE).rejects(query);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlConnectionFactoryMetadataUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nfinal class MssqlConnectionFactoryMetadataUnitTests {\n\n    @Test\n    void name() {\n        assertThat(MssqlConnectionFactoryMetadata.INSTANCE.getName()).isEqualTo(MssqlConnectionFactoryMetadata.NAME);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlConnectionFactoryProviderTest.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.ClientConfiguration;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport io.r2dbc.spi.Option;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.ALTERNATE_MSSQL_DRIVER;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.MSSQL_DRIVER;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.SSL_CONTEXT_BUILDER_CUSTOMIZER;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.SSL_TUNNEL;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TCP_KEEPALIVE;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TCP_NODELAY;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TRUST_SERVER_CERTIFICATE;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TRUST_STORE;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TRUST_STORE_PASSWORD;\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TRUST_STORE_TYPE;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.HOST;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.LOCK_WAIT_TIMEOUT;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.PORT;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.SSL;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.USER;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalStateException;\n\n/**\n * Unit tests for {@link MssqlConnectionFactoryProvider}.\n *\n * @author Mark Paluch\n */\nfinal class MssqlConnectionFactoryProviderTest {\n\n    private final MssqlConnectionFactoryProvider provider = new MssqlConnectionFactoryProvider();\n\n    @Test\n    void doesNotSupportWithWrongDriver() {\n        assertThat(this.provider.supports(ConnectionFactoryOptions.builder()\n            .option(DRIVER, \"test-driver\")\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(PORT, -1)\n            .option(USER, \"test-user\")\n            .build())).isFalse();\n    }\n\n    @Test\n    void doesNotSupportWithoutDriver() {\n        assertThat(this.provider.supports(ConnectionFactoryOptions.builder()\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(PORT, -1)\n            .option(USER, \"test-user\")\n            .build())).isFalse();\n    }\n\n    @Test\n    void doesNotSupportWithoutHost() {\n        assertThat(this.provider.supports(ConnectionFactoryOptions.builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(PASSWORD, \"test-password\")\n            .option(PORT, -1)\n            .option(USER, \"test-user\")\n            .build())).isFalse();\n    }\n\n    @Test\n    void doesNotSupportWithoutPassword() {\n        assertThat(this.provider.supports(ConnectionFactoryOptions.builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PORT, -1)\n            .option(USER, \"test-user\")\n            .build())).isFalse();\n    }\n\n    @Test\n    void doesNotSupportWithoutUser() {\n        assertThat(this.provider.supports(ConnectionFactoryOptions.builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(PORT, -1)\n            .build())).isFalse();\n    }\n\n    @Test\n    void supports() {\n        assertThat(this.provider.supports(ConnectionFactoryOptions.builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .build())).isTrue();\n    }\n\n    @Test\n    void supportsAlternateDriverId() {\n        assertThat(this.provider.supports(ConnectionFactoryOptions.builder()\n            .option(DRIVER, ALTERNATE_MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .build())).isTrue();\n    }\n\n    @Test\n    void shouldConfigureWithStaticCursoredExecutionPreference() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(MssqlConnectionFactoryProvider.PREFER_CURSORED_EXECUTION, \"true\")\n            .build());\n\n        ConnectionOptions options = factory.getConnectionOptions();\n\n        assertThat(options.prefersCursors(\"foo\")).isTrue();\n    }\n\n    @Test\n    void shouldConfigureWithLockWaitTimeout() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(LOCK_WAIT_TIMEOUT, Duration.ofSeconds(10))\n            .build());\n\n        MssqlConnectionConfiguration configuration = factory.getConfiguration();\n\n        assertThat(configuration.getLockWaitTimeout()).isEqualTo(Duration.ofSeconds(10));\n    }\n\n    @Test\n    void shouldConfigureWithStringAsUnicode() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(MssqlConnectionFactoryProvider.SEND_STRING_PARAMETERS_AS_UNICODE, false)\n            .build());\n\n        assertThat(factory.getConnectionOptions().isSendStringParametersAsUnicode()).isFalse();\n\n        factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(MssqlConnectionFactoryProvider.SEND_STRING_PARAMETERS_AS_UNICODE, true)\n            .build());\n\n        assertThat(factory.getConnectionOptions().isSendStringParametersAsUnicode()).isTrue();\n    }\n\n    @Test\n    void shouldConfigureWithSsl() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(MssqlConnectionFactoryProvider.HOSTNAME_IN_CERTIFICATE, \"*.foo\")\n            .build());\n\n        ClientConfiguration configuration = factory.getClientConfiguration();\n\n        assertThat(configuration.isSslEnabled()).isTrue();\n        assertThat(configuration.getSslTunnelConfiguration().isSslEnabled()).isFalse();\n    }\n\n    @Test\n    void shouldConfigureWithoutSsl() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, false)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .build());\n\n        ClientConfiguration configuration = factory.getClientConfiguration();\n\n        assertThat(configuration.isSslEnabled()).isFalse();\n    }\n\n    @Test\n    void shouldConfigureWithSslCustomizer() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(SSL_CONTEXT_BUILDER_CUSTOMIZER, sslContextBuilder -> {\n                throw new IllegalStateException(\"Works!\");\n            })\n            .build());\n\n        assertThatIllegalStateException().isThrownBy(() -> factory.getClientConfiguration().getSslContext()).withMessageContaining(\"Works!\");\n    }\n\n    @Test\n    void shouldConfigureWithSslTunnel() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(Option.valueOf(\"sslTunnel\"), true)\n            .build());\n\n        assertThat(factory.getClientConfiguration().getSslTunnelConfiguration().isSslEnabled()).isTrue();\n\n        factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(Option.valueOf(\"sslTunnel\"), false)\n            .build());\n\n        assertThat(factory.getClientConfiguration().getSslTunnelConfiguration().isSslEnabled()).isFalse();\n    }\n\n    @Test\n    void shouldConfigureWithSslTunnelCustomizer() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(SSL_TUNNEL, Function.identity())\n            .build());\n\n        assertThat(factory.getClientConfiguration().getSslTunnelConfiguration().isSslEnabled()).isTrue();\n    }\n\n    @Test\n    void shouldConfigureTcpKeepAlive() {\n\n        MssqlConnectionFactory factory = this.provider.create(builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(TCP_KEEPALIVE, true)\n            .build());\n\n        assertThat(factory.getClientConfiguration().isTcpKeepAlive()).isTrue();\n    }\n\n    @Test\n    void shouldConfigureTcpNoDelay() {\n\n        MssqlConnectionFactory factory = this.provider.create(builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(TCP_NODELAY, true)\n            .build());\n\n        assertThat(factory.getClientConfiguration().isTcpNoDelay()).isTrue();\n    }\n\n    @Test\n    void shouldConfigureWithTrustServerCertificate() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(TRUST_SERVER_CERTIFICATE, true)\n            .option(TRUST_STORE_PASSWORD, \"hello\".toCharArray())\n            .option(TRUST_STORE_TYPE, \"PKCS\")\n            .build());\n\n        assertThat(factory.getClientConfiguration())\n            .hasFieldOrPropertyWithValue(\"trustServerCertificate\", true);\n    }\n\n    @Test\n    void shouldConfigureWithTrustStoreCustomizer() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(SSL, true)\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(TRUST_STORE, new File(\"foo\"))\n            .option(TRUST_STORE_PASSWORD, \"hello\".toCharArray())\n            .option(TRUST_STORE_TYPE, \"PKCS\")\n            .build());\n\n        assertThat(factory.getClientConfiguration())\n            .hasFieldOrPropertyWithValue(\"trustServerCertificate\", false)\n            .hasFieldOrPropertyWithValue(\"trustStore\", new File(\"foo\"))\n            .hasFieldOrPropertyWithValue(\"trustStorePassword\", \"hello\".toCharArray())\n            .hasFieldOrPropertyWithValue(\"trustStoreType\", \"PKCS\");\n    }\n\n    @Test\n    void shouldConfigureWithStaticBooleanCursoredExecutionPreference() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(MssqlConnectionFactoryProvider.PREFER_CURSORED_EXECUTION, true)\n            .build());\n\n        ConnectionOptions options = factory.getConnectionOptions();\n\n        assertThat(options.prefersCursors(\"foo\")).isTrue();\n    }\n\n    @Test\n    void shouldConfigureWithClassCursoredExecutionPreference() {\n\n        MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder()\n            .option(DRIVER, MSSQL_DRIVER)\n            .option(HOST, \"test-host\")\n            .option(PASSWORD, \"test-password\")\n            .option(USER, \"test-user\")\n            .option(MssqlConnectionFactoryProvider.PREFER_CURSORED_EXECUTION, MyPredicate.class.getName())\n            .build());\n\n        ConnectionOptions options = factory.getConnectionOptions();\n\n        assertThat(options.prefersCursors(\"foo\")).isTrue();\n        assertThat(options.prefersCursors(\"bar\")).isFalse();\n    }\n\n    @Test\n    void returnsDriverIdentifier() {\n        assertThat(this.provider.getDriver()).isEqualTo(MSSQL_DRIVER);\n    }\n\n    static class MyPredicate implements Predicate<String> {\n\n        @Override\n        public boolean test(String s) {\n            return s.equals(\"foo\");\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlConnectionFactoryUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.TestClient;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.Redirect;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.token.*;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.spi.R2dbcNonTransientResourceException;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\nimport reactor.util.annotation.Nullable;\n\nimport java.nio.charset.Charset;\nimport java.util.Arrays;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\n\n/**\n * Unit tests for {@link MssqlConnectionFactory}.\n *\n * @author Mark Paluch\n */\nfinal class MssqlConnectionFactoryUnitTests {\n\n    MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder().host(\"initial\").username(\"user\").password(\"password\").build();\n\n    static final Column[] COLUMNS = Arrays.asList(\n        createColumn(0, \"Edition\", SqlServerType.NVARCHAR, 100, LengthStrategy.USHORTLENTYPE, ServerCharset.UNICODE.charset()),\n        createColumn(1, \"VersionString\", SqlServerType.VARCHAR, 100, LengthStrategy.USHORTLENTYPE, ServerCharset.CP1252.charset())).toArray(new Column[0]);\n\n    @Test\n    void constructorNoClientFactory() {\n        assertThatIllegalArgumentException().isThrownBy(() -> new MssqlConnectionFactory(null, MssqlConnectionConfiguration.builder()\n            .host(\"test-host\")\n            .password(\"test-password\")\n            .username(\"test-username\")\n            .build()))\n            .withMessage(\"clientFactory must not be null\");\n    }\n\n    @Test\n    void constructorNoConfiguration() {\n        assertThatIllegalArgumentException().isThrownBy(() -> new MssqlConnectionFactory(null))\n            .withMessage(\"configuration must not be null\");\n    }\n\n    @Test\n    void shouldFollowRedirect() {\n\n        ColumnMetadataToken columns = ColumnMetadataToken.create(COLUMNS);\n        RowToken rowToken = RowTokenFactory.create(columns, buffer -> {\n            Encode.uString(buffer, \"Edition\", ServerCharset.UNICODE.charset());\n            Encode.uString(buffer, \"1.2.3\", ServerCharset.CP1252.charset());\n        });\n\n        TestClient initial =\n            TestClient.builder().expectClose().withRedirect(Redirect.create(\"redirect\", 1234)).assertNextRequestWith(clientMessage -> {\n\n                assertThat(clientMessage).isInstanceOf(Prelogin.class);\n\n            }).thenRespond(DoneToken.create(0)).build();\n\n\n        TestClient redirect =\n            TestClient.builder().assertNextRequestWith(clientMessage -> {\n\n                assertThat(clientMessage).isInstanceOf(Prelogin.class);\n\n            }).thenRespond(DoneToken.create(0)).assertNextRequestWith(clientMessage -> {\n\n                assertThat(clientMessage).isInstanceOf(SqlBatch.class);\n            }).thenRespond(columns, rowToken, DoneToken.create(1)).build();\n\n        MssqlConnectionFactory connectionFactory = new MssqlConnectionFactory(config -> {\n\n            if (config.getHost().equals(\"initial\")) {\n                return Mono.just(initial);\n            }\n\n            return Mono.just(redirect);\n        }, this.configuration);\n\n\n        connectionFactory.create().as(StepVerifier::create).expectNextCount(1).verifyComplete();\n\n        assertThat(initial.isClosed()).isTrue();\n        assertThat(redirect.isClosed()).isFalse();\n    }\n\n    @Test\n    void properlyPropagatesFailures() {\n\n        ErrorToken error = new ErrorToken(0, 0, (byte) 0, (byte) 0, \"failure\", \"\", \"\", 0);\n\n        TestClient initial =\n            TestClient.builder().expectClose().withRedirect(Redirect.create(\"redirect\", 1234)).assertNextRequestWith(clientMessage -> {\n\n                assertThat(clientMessage).isInstanceOf(Prelogin.class);\n\n            }).thenRespond(error).build();\n\n        MssqlConnectionFactory connectionFactory = new MssqlConnectionFactory(config -> {\n            return Mono.just(initial);\n        }, this.configuration);\n\n\n        connectionFactory.create().as(StepVerifier::create).verifyError(R2dbcNonTransientResourceException.class);\n\n        assertThat(initial.isClosed()).isTrue();\n    }\n\n    @Test\n    void shouldFailOnMultipleRedirects() {\n\n        TestClient initial =\n            TestClient.builder().expectClose().withRedirect(Redirect.create(\"redirect\", 1234)).assertNextRequestWith(clientMessage -> {\n\n                assertThat(clientMessage).isInstanceOf(Prelogin.class);\n\n            }).thenRespond(DoneToken.create(0)).build();\n\n\n        TestClient redirect =\n            TestClient.builder().expectClose().withRedirect(Redirect.create(\"redirect\", 1234)).assertNextRequestWith(clientMessage -> {\n\n                assertThat(clientMessage).isInstanceOf(Prelogin.class);\n\n            }).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnectionFactory connectionFactory = new MssqlConnectionFactory(config -> {\n\n            if (config.getHost().equals(\"initial\")) {\n                return Mono.just(initial);\n            }\n\n            return Mono.just(redirect);\n        }, this.configuration);\n\n\n        connectionFactory.create().as(StepVerifier::create).verifyError(MssqlConnectionFactory.MssqlRoutingException.class);\n\n        assertThat(initial.isClosed()).isTrue();\n        assertThat(redirect.isClosed()).isTrue();\n    }\n\n    @Test\n    void shouldCreateNewPreparedStatement() {\n\n        MssqlConnectionFactory connectionFactory = new MssqlConnectionFactory(config -> Mono.empty(), this.configuration);\n        ConnectionOptions options = connectionFactory.getConnectionOptions();\n        ConnectionOptions other = connectionFactory.getConnectionOptions();\n\n        options.getPreparedStatementCache().putHandle(1, \"foo\", new Binding());\n\n        assertThat(options.getPreparedStatementCache().getHandle(\"foo\", new Binding())).isEqualTo(1);\n        assertThat(other.getPreparedStatementCache().getHandle(\"foo\", new Binding())).isEqualTo(0);\n    }\n\n    private static Column createColumn(int index, String name, SqlServerType serverType, int length, LengthStrategy lengthStrategy, @Nullable Charset charset) {\n\n        TypeInformation.Builder builder = TypeInformation.builder().withServerType(serverType).withMaxLength(length).withLengthStrategy(lengthStrategy);\n        if (charset != null) {\n            builder.withCharset(charset);\n        }\n        TypeInformation type = builder.build();\n\n        return new Column(index, name, type, null);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlConnectionIntegrationTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.ColumnMetadata;\nimport io.r2dbc.spi.ConnectionFactories;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport io.r2dbc.spi.IsolationLevel;\nimport io.r2dbc.spi.R2dbcPermissionDeniedException;\nimport io.r2dbc.spi.R2dbcTimeoutException;\nimport io.r2dbc.spi.R2dbcTransientException;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.ConnectException;\nimport java.nio.file.Path;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.time.Duration;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\n\nimport static io.r2dbc.spi.ConnectionFactoryOptions.LOCK_WAIT_TIMEOUT;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration tests for {@link MssqlConnection} and {@link MssqlStatement}.\n *\n * @author Mark Paluch\n */\nclass MssqlConnectionIntegrationTests extends IntegrationTestSupport {\n\n    @Test\n    void shouldFailOnConnectionRefused() {\n\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n            .host(SERVER.getHost())\n            .port(123)\n            .username(SERVER.getUsername())\n            .password(SERVER.getPassword())\n            .build();\n\n        MssqlConnectionFactory connectionFactory = new MssqlConnectionFactory(configuration);\n\n        connectionFactory.create()\n            .as(StepVerifier::create)\n            .expectError(ConnectException.class)\n            .verify();\n    }\n\n    @Test\n    void shouldFailOnLoginFailedRefused() {\n\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n            .host(SERVER.getHost())\n            .port(SERVER.getPort())\n            .username(SERVER.getUsername())\n            .password(\"foobar\")\n            .build();\n\n        MssqlConnectionFactory connectionFactory = new MssqlConnectionFactory(configuration);\n\n        connectionFactory.create()\n            .as(StepVerifier::create)\n            .expectError(R2dbcPermissionDeniedException.class)\n            .verify();\n    }\n\n    @Test\n    void shouldConnectWithSelfSignedCert() {\n\n        MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder()\n            .host(SERVER.getHost())\n            .port(SERVER.getPort())\n            .database(\"master\")\n            .username(SERVER.getUsername())\n            .password(SERVER.getPassword())\n            .enableSsl()\n            .trustServerCertificate()\n            .build();\n\n        MssqlConnectionFactory connectionFactory = new MssqlConnectionFactory(configuration);\n\n        Flux.usingWhen(connectionFactory.create(), conn -> conn.createStatement(\"SELECT @@VERSION\").execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0))), MssqlConnection::close)\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldReportMetadata() throws Exception {\n\n        try (Connection connection = SERVER.getDataSource().getConnection()) {\n\n            DatabaseMetaData jdbcMetadata = connection.getMetaData();\n            MssqlConnectionMetadata metadata = IntegrationTestSupport.connection.getMetadata();\n\n            assertThat(metadata.getDatabaseProductName()).contains(jdbcMetadata.getDatabaseProductName());\n            assertThat(metadata.getDatabaseProductName()).doesNotContain(\"Copyright\");\n            assertThat(metadata.getDatabaseVersion()).isEqualTo(jdbcMetadata.getDatabaseProductVersion());\n        }\n    }\n\n    @Test\n    void shouldInsertAndSelectUsingMap() {\n\n        createTable(connection);\n\n        insertRecord(connection, 1);\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example ORDER BY first_name\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> {\n\n                Map<String, Object> values = new LinkedHashMap<>();\n\n                for (ColumnMetadata column : rowMetadata.getColumnMetadatas()) {\n                    values.put(column.getName(), row.get(column.getName()));\n                }\n\n                return values;\n            }))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> {\n\n                assertThat(actual)\n                    .containsEntry(\"id\", 1)\n                    .containsEntry(\"first_name\", \"Walter\")\n                    .containsEntry(\"last_name\", \"White\");\n            })\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldInsertAndSelectUsingRowCount() {\n\n        createTable(connection);\n\n        insertRecord(connection, 1);\n        insertRecord(connection, 2);\n        insertRecord(connection, 3);\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example\")\n            .execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(3L)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldInsertAndSelectUsingPagingAndDirectMode() {\n\n        createTable(connection);\n\n        insertRecord(connection, 1);\n        insertRecord(connection, 2);\n        insertRecord(connection, 3);\n\n        Flux.from(connection.createStatement(\"SELECT * FROM r2dbc_example ORDER BY id OFFSET @Offset ROWS\" +\n            \"  FETCH NEXT @Rows ROWS ONLY\")\n            .bind(\"Offset\", 0)\n            .bind(\"Rows\", 2)\n            .execute())\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"id\", Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(1)\n            .expectNext(2)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"SELECT * FROM r2dbc_example ORDER BY id OFFSET @Offset ROWS\" +\n            \" FETCH NEXT @Rows ROWS ONLY\")\n            .bind(\"Offset\", 2)\n            .bind(\"Rows\", 2)\n            .execute())\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"id\", Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(3)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldInsertAndSelectUsingPagingAndCursors() {\n\n        createTable(connection);\n\n        insertRecord(connection, 1);\n        insertRecord(connection, 2);\n        insertRecord(connection, 3);\n\n        Flux.from(connection.createStatement(\"SELECT * FROM r2dbc_example ORDER BY id OFFSET @Offset ROWS\" +\n            \"  FETCH NEXT @Rows ROWS ONLY /* cursored */\")\n            .bind(\"Offset\", 0)\n            .bind(\"Rows\", 2)\n            .execute())\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(\"id\", Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(1)\n            .expectNext(2)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldInsertAndSelectCompoundStatement() {\n\n        createTable(connection);\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example;SELECT * FROM r2dbc_example\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> {\n                return new Object();   // just a marker\n            }).collectList())\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> assertThat(actual).isEmpty())\n            .consumeNextWith(actual -> assertThat(actual).isEmpty())\n            .verifyComplete();\n\n        insertRecord(connection, 1);\n\n        connection.createStatement(\"SELECT * FROM r2dbc_example;SELECT * FROM r2dbc_example\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> {\n\n                Map<String, Object> values = new LinkedHashMap<>();\n\n                for (ColumnMetadata column : rowMetadata.getColumnMetadatas()) {\n                    values.put(column.getName(), row.get(column.getName()));\n                }\n\n                return values;\n            }).collectList())\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> assertThat(actual).hasSize(1))\n            .consumeNextWith(actual -> assertThat(actual).hasSize(1))\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldConsumeSequence() {\n\n        createSequence(connection);\n\n        connection.createStatement(\"SELECT CAST(NEXT VALUE FOR integration_test AS BIGINT)\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0)))\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n\n        connection.createStatement(\"SELECT CAST(NEXT VALUE FOR integration_test AS BIGINT)\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0)))\n            .as(StepVerifier::create)\n            .expectNext(2L)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldReusePreparedStatements() {\n\n        createTable(connection);\n\n        insertRecord(connection, 1);\n        insertRecord(connection, 2);\n    }\n\n    @Test\n    void shouldApplyLockWaitwaot() {\n\n        ConnectionFactoryOptions options = builder().option(LOCK_WAIT_TIMEOUT, Duration.ofMillis(100)).build();\n\n        MssqlConnectionFactory factory = (MssqlConnectionFactory) ConnectionFactories.get(options);\n        MssqlConnection connection1 = factory.create().block();\n        MssqlConnection connection2 = factory.create().block();\n\n        connection1.createStatement(\"SELECT @@LOCK_TIMEOUT AS [Lock Timeout]\").execute()\n            .flatMap(it -> it.map(row -> row.get(\"Lock Timeout\")))\n            .as(StepVerifier::create)\n            .expectNext(100)\n            .verifyComplete();\n\n        createTable(connection);\n\n        connection1.createStatement(\"INSERT INTO r2dbc_example VALUES (1, 'fn', 'ln')\")\n            .execute()\n            .flatMap(Result::getRowsUpdated)\n            .then()\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        connection1.beginTransaction(IsolationLevel.READ_COMMITTED).as(StepVerifier::create).verifyComplete();\n        connection2.beginTransaction(IsolationLevel.READ_COMMITTED).as(StepVerifier::create).verifyComplete();\n\n        connection1.createStatement(\"SELECT * FROM r2dbc_example WITH (UPDLOCK) WHERE id = 1\")\n            .execute()\n            .flatMap(Result::getRowsUpdated)\n            .then()\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        connection2.createStatement(\"UPDATE r2dbc_example SET first_name = 'updated' WHERE id = 1\")\n            .execute()\n            .flatMap(Result::getRowsUpdated)\n            .then()\n            .as(StepVerifier::create)\n            .verifyError(R2dbcTimeoutException.class);\n\n        connection1.close().block();\n        connection2.close().block();\n    }\n\n    @Test\n    void queryAfterCancelShouldCompleteSuccessfully() {\n\n        connection.cancel().as(StepVerifier::create).verifyComplete();\n\n        connection.createStatement(\"SELECT 1\").execute().flatMap(it -> it.map(row -> row.get(0))).as(StepVerifier::create).expectNext(1).verifyComplete();\n    }\n\n    @Test\n    void cancelShouldTerminateOngoingDirectQuery() throws Exception {\n\n        CompletableFuture<Void> future = connection.createStatement(\"WAITFOR DELAY '10:00'\").fetchSize(0).execute().flatMap(Result::getRowsUpdated).then().toFuture();\n        connection.cancel().as(StepVerifier::create).verifyComplete();\n\n        try {\n            future.get(2, TimeUnit.SECONDS);\n        } catch (ExecutionException e) {\n            assertThat(e.getCause()).isInstanceOf(R2dbcTransientException.class).hasMessageContaining(\"cancelled\");\n        }\n\n        assertThat(future).isCompletedExceptionally();\n    }\n\n    @Test\n    void cancelShouldTerminateOngoingCursoredQuery() throws Exception {\n\n        CompletableFuture<Void> future = connection.createStatement(\"WAITFOR DELAY '10:00'\").fetchSize(10).execute().flatMap(Result::getRowsUpdated).then().toFuture();\n        connection.cancel().as(StepVerifier::create).verifyComplete();\n\n        try {\n            future.get(2, TimeUnit.SECONDS);\n        } catch (ExecutionException e) {\n            assertThat(e.getCause()).isInstanceOf(R2dbcTransientException.class).hasMessageContaining(\"cancelled\");\n        }\n\n        assertThat(future).isCompletedExceptionally();\n    }\n\n    @Test\n    void shouldResumeOperationsAfterCancellationOfPendingRequest() throws Exception {\n\n        CompletableFuture<Void> future = connection.createStatement(\"WAITFOR DELAY '10:00'\").execute().flatMap(Result::getRowsUpdated).then().toFuture();\n        connection.cancel().as(StepVerifier::create).verifyComplete();\n\n        try {\n            future.get(2, TimeUnit.SECONDS);\n        } catch (ExecutionException e) {\n            assertThat(e.getCause()).isInstanceOf(R2dbcTransientException.class).hasMessageContaining(\"cancelled\");\n        }\n\n        connection.createStatement(\"SELECT 1\").execute().flatMap(it -> it.map(row -> row.get(0))).as(StepVerifier::create).expectNext(1).verifyComplete();\n    }\n\n    @Test\n    void shouldRejectMultipleParametrizedExecutions() {\n\n        createTable(connection);\n\n        Flux<MssqlResult> prepared = Flux.from(connection.createStatement(\"SELECT * FROM r2dbc_example ORDER BY id OFFSET @Offset ROWS\")\n            .bind(\"Offset\", 0)\n            .execute());\n\n        prepared.flatMap(MssqlResult::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n\n        prepared.flatMap(MssqlResult::getRowsUpdated)\n            .as(StepVerifier::create)\n            .verifyError(IllegalStateException.class);\n    }\n\n    private void createTable(MssqlConnection connection) {\n\n        connection.createStatement(\"DROP TABLE r2dbc_example\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE r2dbc_example (\" +\n                \"id int PRIMARY KEY, \" +\n                \"first_name varchar(255), \" +\n                \"last_name varchar(255))\")\n                .execute().flatMap(MssqlResult::getRowsUpdated).then())\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    private void createSequence(MssqlConnection connection) {\n\n        connection.createStatement(\"DROP SEQUENCE integration_test\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE SEQUENCE integration_test START WITH 1 INCREMENT BY 1\")\n                .execute().flatMap(MssqlResult::getRowsUpdated).then())\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    private String writeKeyStoreToTempFile(Path tempDir, KeyStore keyStore, String password) {\n        final File file = new File(tempDir.toFile(), UUID.randomUUID() + \".jks\");\n        try (OutputStream outputStream = new FileOutputStream(file)) {\n            keyStore.store(outputStream, password.toCharArray());\n            return file.getAbsolutePath();\n        } catch (final IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {\n            throw new RuntimeException(\"Failed to write key store to file\", e);\n        }\n    }\n\n    private void insertRecord(MssqlConnection connection, int id) {\n\n        Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@id, @firstname, @lastname)\")\n                .bind(\"id\", id)\n                .bind(\"firstname\", \"Walter\")\n                .bind(\"lastname\", \"White\")\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlConnectionMetadataUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link MssqlConnectionMetadata}\n *\n * @author Mark Paluch\n */\nclass MssqlConnectionMetadataUnitTests {\n\n    @Test\n    void shouldConstructMetadata() {\n\n        String edition = \"Developer Edition (64-bit)\";\n        String version = \"14.0.3162.1\";\n        String versionString =\n            \"Microsoft SQL Server 2017 (RTM-CU15) (KB4498951) - 14.0.3162.1 (X64) \" +\n                \"May 15 2019 19:14:30\" +\n                \"Copyright (C) 2017 Microsoft Corporation\" +\n                \"Developer Edition (64-bit) on Linux (Ubuntu 16.04.6 LTS)\";\n\n        MssqlConnectionMetadata metadata = MssqlConnectionMetadata.from(edition, version, versionString);\n\n        assertThat(metadata.getDatabaseProductName()).isEqualTo(\"Microsoft SQL Server 2017 (RTM-CU15) (KB4498951) - Developer Edition (64-bit)\");\n        assertThat(metadata.getDatabaseVersion()).isEqualTo(version);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlConnectionUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.client.TestClient;\nimport io.r2dbc.mssql.client.TransactionStatus;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.SqlBatch;\nimport io.r2dbc.spi.IsolationLevel;\nimport io.r2dbc.spi.ValidationDepth;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport reactor.test.StepVerifier;\n\nimport java.time.Duration;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.Mockito.*;\n\n/**\n * Unit tests for {@link MssqlConnection}.\n *\n * @author Mark Paluch\n * @author Hebert Coelho\n * @author Nayan Hajratwala\n */\nclass MssqlConnectionUnitTests {\n\n    static MssqlConnectionMetadata metadata = new MssqlConnectionMetadata(\"SQL Server\", \"1.0\");\n\n    static ConnectionOptions conectionOptions = new TestConnectionOptions();\n\n    @Test\n    void shouldBeginTransactionFromInitialState() {\n\n        TestClient client =\n                TestClient.builder().expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"BEGIN TRANSACTION;\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.beginTransaction()\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @Test\n    void shouldBeginTransactionFromExplicitState() {\n\n        TestClient client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.EXPLICIT).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"BEGIN TRANSACTION;\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.beginTransaction()\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @Test\n    void shouldNotBeginTransactionFromStartedState() {\n\n        Client clientMock = mock(Client.class);\n        when(clientMock.getContext()).thenReturn(new ConnectionContext());\n        when(clientMock.getTransactionStatus()).thenReturn(TransactionStatus.STARTED);\n\n        MssqlConnection connection = new MssqlConnection(clientMock, metadata, conectionOptions);\n\n        connection.beginTransaction()\n                .as(StepVerifier::create)\n                .verifyComplete();\n\n        verify(clientMock, times(2)).getTransactionStatus();\n        verify(clientMock, atLeast(1)).getContext();\n        verifyNoMoreInteractions(clientMock);\n    }\n\n    @Test\n    void shouldCommitFromExplicitTransaction() {\n\n        TestClient client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.STARTED).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"IF @@TRANCOUNT > 0 COMMIT TRANSACTION;\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.commitTransaction()\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @Test\n    void shouldNotCommitInAutoCommitState() {\n\n        Client clientMock = mock(Client.class);\n        when(clientMock.getContext()).thenReturn(new ConnectionContext());\n        when(clientMock.getTransactionStatus()).thenReturn(TransactionStatus.AUTO_COMMIT);\n\n        MssqlConnection connection = new MssqlConnection(clientMock, metadata, conectionOptions);\n\n        connection.commitTransaction()\n                .as(StepVerifier::create)\n                .verifyComplete();\n\n        verify(clientMock, times(2)).getTransactionStatus();\n        verify(clientMock, atLeast(1)).getContext();\n        verifyNoMoreInteractions(clientMock);\n    }\n\n    @Test\n    void shouldRollbackFromExplicitTransaction() {\n\n        TestClient client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.STARTED).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.rollbackTransaction()\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @Test\n    void shouldNotRollbackInAutoCommitState() {\n\n        Client clientMock = mock(Client.class);\n        when(clientMock.getContext()).thenReturn(new ConnectionContext());\n        when(clientMock.getTransactionStatus()).thenReturn(TransactionStatus.AUTO_COMMIT);\n\n        MssqlConnection connection = new MssqlConnection(clientMock, metadata, conectionOptions);\n\n        connection.rollbackTransaction()\n                .as(StepVerifier::create)\n                .verifyComplete();\n\n        verify(clientMock, times(2)).getTransactionStatus();\n        verify(clientMock, atLeast(1)).getContext();\n        verifyNoMoreInteractions(clientMock);\n    }\n\n    @Test\n    void shouldNotSupportSavePointRelease() {\n\n        Client clientMock = mock(Client.class);\n        when(clientMock.getContext()).thenReturn(new ConnectionContext());\n        MssqlConnection connection = new MssqlConnection(clientMock, metadata, conectionOptions);\n\n        connection.releaseSavepoint(\"foo\").as(StepVerifier::create).verifyComplete();\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"0\", \"a\", \"A\", \"foo\", \"foo_bar\"})\n    void shouldAllowSavepointNames(String name) {\n\n        Client clientMock = mock(Client.class);\n        when(clientMock.getContext()).thenReturn(new ConnectionContext());\n        MssqlConnection connection = new MssqlConnection(clientMock, metadata, conectionOptions);\n\n        assertThat(connection.createSavepoint(name)).isNotNull();\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"\", \"@\", \"a'\", \"a\\\"\", \"a[\", \"a]\"})\n    void shouldRejectSavepointNames(String name) {\n\n        Client clientMock = mock(Client.class);\n        when(clientMock.getContext()).thenReturn(new ConnectionContext());\n        MssqlConnection connection = new MssqlConnection(clientMock, metadata, conectionOptions);\n\n        assertThatThrownBy(() -> connection.createSavepoint(name)).isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    void shouldRollbackTransactionToSavepointFromExplicitTransaction() {\n\n        TestClient client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.STARTED).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"ROLLBACK TRANSACTION foo\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.rollbackTransactionToSavepoint(\"foo\")\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @Test\n    void shouldNotRollbackTransactionToSavepointInAutoCommitState() {\n\n        Client clientMock = mock(Client.class);\n        when(clientMock.getContext()).thenReturn(new ConnectionContext());\n        when(clientMock.getTransactionStatus()).thenReturn(TransactionStatus.AUTO_COMMIT);\n\n        MssqlConnection connection = new MssqlConnection(clientMock, metadata, conectionOptions);\n\n        connection.rollbackTransactionToSavepoint(\"foo\")\n                .as(StepVerifier::create)\n                .verifyComplete();\n\n        verify(clientMock, times(2)).getTransactionStatus();\n        verify(clientMock, atLeast(1)).getContext();\n        verifyNoMoreInteractions(clientMock);\n    }\n\n    @Test\n    void shouldCreateSavepointFromExplicitTransaction() {\n\n        TestClient client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.STARTED).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"SET IMPLICIT_TRANSACTIONS ON; IF @@TRANCOUNT = 0 \" +\n                        \"BEGIN BEGIN TRAN IF @@TRANCOUNT = 2 COMMIT TRAN END SAVE TRAN foo;\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.createSavepoint(\"foo\")\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @Test\n    void createSavepointShouldBeginTransaction() {\n\n        TestClient client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.AUTO_COMMIT).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(), \"SET IMPLICIT_TRANSACTIONS ON; IF @@TRANCOUNT =\" +\n                        \" 0 BEGIN BEGIN TRAN IF @@TRANCOUNT = 2 COMMIT TRAN END SAVE TRAN foo;\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.createSavepoint(\"foo\")\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"isolationLevels\")\n    void shouldSetIsolationLevel(IsolationLevel isolationLevel) {\n\n        TestClient client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.EXPLICIT).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(),\n                        \"SET TRANSACTION ISOLATION LEVEL \" + isolationLevel.asSql().toUpperCase())).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.setTransactionIsolationLevel(isolationLevel)\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @Test\n    void shouldSetLockWaitTimeout() {\n\n        TestClient client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.EXPLICIT).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(),\n                        \"SET LOCK_TIMEOUT 10000\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.setLockWaitTimeout(Duration.ofSeconds(10))\n                .as(StepVerifier::create)\n                .verifyComplete();\n\n        client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.EXPLICIT).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(),\n                        \"SET LOCK_TIMEOUT -1\")).thenRespond(DoneToken.create(0)).build();\n\n        connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.setLockWaitTimeout(Duration.ofSeconds(-10))\n                .as(StepVerifier::create)\n                .verifyComplete();\n\n        client =\n                TestClient.builder().withTransactionStatus(TransactionStatus.EXPLICIT).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(),\n                        \"SET LOCK_TIMEOUT 0\")).thenRespond(DoneToken.create(0)).build();\n\n        connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.setLockWaitTimeout(Duration.ZERO)\n                .as(StepVerifier::create)\n                .verifyComplete();\n    }\n\n    @Test\n    void localValidationShouldValidateAgainstConnectionState() {\n\n        TestClient connected =\n                TestClient.builder().withConnected(true).build();\n\n        MssqlConnection connection = new MssqlConnection(connected, metadata, conectionOptions);\n\n        connection.validate(ValidationDepth.LOCAL)\n                .as(StepVerifier::create)\n                .expectNext(true)\n                .verifyComplete();\n\n        TestClient disconnected =\n                TestClient.builder().withConnected(false).build();\n\n        connection = new MssqlConnection(disconnected, metadata, conectionOptions);\n\n        connection.validate(ValidationDepth.LOCAL)\n                .as(StepVerifier::create)\n                .expectNext(false)\n                .verifyComplete();\n    }\n\n    @Test\n    void remoteValidationShouldIssueQuery() {\n\n        TestClient client =\n                TestClient.builder().withConnected(true).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(),\n                        \"SELECT 1\")).thenRespond(DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.validate(ValidationDepth.REMOTE)\n                .as(StepVerifier::create)\n                .expectNext(true)\n                .verifyComplete();\n    }\n\n    @Test\n    void remoteValidationShouldFail() {\n\n        TestClient client =\n                TestClient.builder().withConnected(true).expectRequest(SqlBatch.create(1, TransactionDescriptor.empty(),\n                        \"SELECT 1\")).thenRespond(new ErrorToken(1, 1, (byte) 1, (byte) 1, \"failed\", \"\", \"\", 0), DoneToken.create(0)).build();\n\n        MssqlConnection connection = new MssqlConnection(client, metadata, conectionOptions);\n\n        connection.validate(ValidationDepth.REMOTE)\n                .as(StepVerifier::create)\n                .expectNext(false)\n                .verifyComplete();\n\n    }\n\n    @Test\n    void shouldSanitizeProperly() {\n\n        assertThat(MssqlConnection.sanitize(\"12345\", 10)).isEqualTo(\"12345\");\n        assertThat(MssqlConnection.sanitize(\"1234567\", 7)).isEqualTo(\"1234567\");\n        assertThat(MssqlConnection.sanitize(\"1234567\", 3)).isEqualTo(\"123\");\n\n    }\n\n    private static Stream<IsolationLevel> isolationLevels() {\n        return Stream.of(MssqlIsolationLevel.SERIALIZABLE, MssqlIsolationLevel.READ_COMMITTED,\n                MssqlIsolationLevel.READ_UNCOMMITTED, MssqlIsolationLevel.REPEATABLE_READ,\n                MssqlIsolationLevel.SNAPSHOT);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlResultUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport reactor.core.publisher.Flux;\nimport reactor.test.StepVerifier;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Unit tests for {@link DefaultMssqlResult}.\n *\n * @author Mark Paluch\n */\nclass MssqlResultUnitTests {\n\n    @ParameterizedTest\n    @MethodSource(\"factories\")\n    void shouldEmitErrorSignalInOrder(ResultFactory factory) {\n\n        ErrorToken error = new ErrorToken(0, 0, Byte.MIN_VALUE, Byte.MIN_VALUE, \"foo\", \"\", \"\", 0);\n        DoneToken done = DoneToken.create(0);\n\n        MssqlResult countThenError = factory.create(Flux.just(done, error));\n\n        countThenError.getRowsUpdated()\n            .as(StepVerifier::create)\n            .expectError()\n            .verify();\n\n        MssqlResult errorThenCount = factory.create(Flux.just(error, done));\n\n        errorThenCount.getRowsUpdated()\n            .as(StepVerifier::create)\n            .expectError()\n            .verify();\n    }\n\n    static List<ResultFactory> factories() {\n\n        return Arrays.asList(new ResultFactory() {\n\n            @Override\n            MssqlResult create(Flux<Message> messages) {\n                return DefaultMssqlResult.toResult(\"\", new ConnectionContext(), new DefaultCodecs(), messages, false);\n            }\n\n            @Override\n            public String toString() {\n                return \"DefaultMssqlResult\";\n            }\n        }, new ResultFactory() {\n\n            @Override\n            MssqlResult create(Flux<Message> messages) {\n                return MssqlSegmentResult.toResult(\"\", new ConnectionContext(), new DefaultCodecs(), messages, false);\n            }\n\n            @Override\n            public String toString() {\n                return \"MssqlSegmentResult\";\n            }\n        });\n    }\n\n    static abstract class ResultFactory {\n\n        abstract MssqlResult create(Flux<Message> messages);\n\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlReturnValuesUnitTests.java",
    "content": "/*\n * Copyright 2021-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.token.ReturnValue;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.Types;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link MssqlReturnValues}.\n *\n * @author Mark Paluch\n */\nclass MssqlReturnValuesUnitTests {\n\n    TypeInformation integer = Types.integer();\n\n    Column column = new Column(0, \"foo\", this.integer, null);\n\n    ByteBuf data1 = Unpooled.wrappedBuffer(new byte[]{(byte) 0x4, 0x42, 0, 0, 0});\n\n    ByteBuf data2 = Unpooled.wrappedBuffer(new byte[]{(byte) 0x4, 0x43, 0, 0, 0});\n\n    ReturnValue first = new ReturnValue(6, \"@First\", 0, this.integer, this.data1);\n\n    ReturnValue second = new ReturnValue(17, \"@Second\", 0, this.integer, this.data2);\n\n    MssqlReturnValues returnValues = MssqlReturnValues.toReturnValues(new DefaultCodecs(), Arrays.asList(this.first, this.second));\n\n    @Test\n    void metadataShouldReportColumnNames() {\n\n        MssqlReturnValuesMetadata metadata = this.returnValues.getMetadata();\n        assertThat(metadata).containsExactly(\"@First\", \"@Second\");\n        assertThat(metadata.getParameterMetadatas().stream().map(MssqlColumnMetadata::getName).collect(Collectors.toList())).containsExactly(\"@First\", \"@Second\");\n    }\n\n    @Test\n    void metadataShouldReportCorrectType() {\n\n        MssqlReturnValuesMetadata metadata = this.returnValues.getMetadata();\n        assertThat(metadata.getParameterMetadata(0).getName()).isEqualTo(\"@First\");\n        assertThat(metadata.getParameterMetadata(0).getType()).isEqualTo(SqlServerType.INTEGER);\n        assertThat(metadata.getParameterMetadata(\"first\").getName()).isEqualTo(\"@First\");\n        assertThat(metadata.getParameterMetadata(\"@FIRST\").getName()).isEqualTo(\"@First\");\n\n        assertThat(metadata.getParameterMetadata(1).getName()).isEqualTo(\"@Second\");\n        assertThat(metadata.getParameterMetadata(1).getType()).isEqualTo(SqlServerType.INTEGER);\n    }\n\n    @Test\n    void getShouldReturnValue() {\n\n        assertThat(this.returnValues.get(0)).isEqualTo(0x42);\n        assertThat(this.returnValues.get(0)).isEqualTo(0x42);\n        assertThat(this.returnValues.get(\"First\")).isEqualTo(0x42);\n        assertThat(this.returnValues.get(\"@First\")).isEqualTo(0x42);\n\n        assertThat(this.returnValues.get(1)).isEqualTo(0x43);\n        assertThat(this.returnValues.get(1)).isEqualTo(0x43);\n        assertThat(this.returnValues.get(\"Second\")).isEqualTo(0x43);\n        assertThat(this.returnValues.get(\"@Second\")).isEqualTo(0x43);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlRowMetadataUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.spi.Nullability;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\n/**\n * Unit tests for {@link MssqlRowMetadata}.\n *\n * @author Mark Paluch\n */\nclass MssqlRowMetadataUnitTests {\n\n    Codecs codecs = new DefaultCodecs();\n\n    TypeInformation integer = builder().withScale(5).withMaxLength(4).withPrecision(4).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withServerType(SqlServerType.INTEGER).build();\n\n    Column column = new Column(0, \"foo\", this.integer, null);\n\n    ByteBuf data = Unpooled.wrappedBuffer(new byte[]{(byte) 0x42, 0, 0, 0});\n\n    MssqlRowMetadata rowMetadata = new MssqlRowMetadata(this.codecs, new Column[]{this.column}, Collections.singletonMap(\"foo\", this.column));\n\n    @Test\n    void shouldLookupMetadataByName() {\n\n        MssqlColumnMetadata columnMetadata = this.rowMetadata.getColumnMetadata(\"foo\");\n\n        assertThat(columnMetadata).isNotNull();\n        assertThat(columnMetadata.getName()).isEqualTo(\"foo\");\n        assertThat(columnMetadata.getJavaType()).isEqualTo(Integer.class);\n        assertThat(columnMetadata.getPrecision()).isEqualTo(4);\n        assertThat(columnMetadata.getScale()).isEqualTo(5);\n        assertThat(columnMetadata.getNullability()).isEqualTo(Nullability.NON_NULL);\n        assertThat(columnMetadata.getNativeTypeMetadata()).isEqualTo(this.integer);\n    }\n\n    @Test\n    void shouldRemoveRowstatIfLastColumn() {\n\n        Column rowstat = new Column(0, \"ROWSTAT\", this.integer, null);\n\n        MssqlRowMetadata rowMetadata1 = new MssqlRowMetadata(this.codecs, new Column[]{this.column, rowstat}, new HashMap<>());\n\n        assertThat(rowMetadata1.getCount()).isOne();\n        assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> rowMetadata1.getColumnMetadata(\"ROWSTAT\"));\n\n        MssqlRowMetadata rowMetadata2 = new MssqlRowMetadata(this.codecs, new Column[]{rowstat}, new HashMap<>());\n\n        assertThat(rowMetadata2.getCount()).isZero();\n        assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> rowMetadata2.getColumnMetadata(\"ROWSTAT\"));\n    }\n\n    @Test\n    void retainsRowstatIfNotLastColumn() {\n\n        Column rowstat = new Column(0, \"ROWSTAT\", this.integer, null);\n        Map<String, Column> nameKeyedColumns = new HashMap<>();\n        nameKeyedColumns.put(\"foo\", this.column);\n        nameKeyedColumns.put(\"ROWSTAT\", rowstat);\n\n        MssqlRowMetadata rowMetadata = new MssqlRowMetadata(this.codecs, new Column[]{rowstat, this.column}, nameKeyedColumns);\n\n        assertThat(rowMetadata.getCount()).isEqualTo(2);\n    }\n\n    @Test\n    void shouldLookupMetadataByIndex() {\n\n        MssqlColumnMetadata columnMetadata = this.rowMetadata.getColumnMetadata(0);\n\n        assertThat(columnMetadata).isNotNull();\n        assertThat(columnMetadata.getName()).isEqualTo(\"foo\");\n    }\n\n    @Test\n    void shouldReturnMetadataForAllColumns() {\n        assertThat(this.rowMetadata.getColumnMetadatas()).hasSize(1);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlRowUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.codec.Codecs;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.token.RowToken;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.Types;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Collections;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Unit tests for {@link MssqlRow}.\n *\n * @author Mark Paluch\n */\nclass MssqlRowUnitTests {\n\n    Codecs codecs = new DefaultCodecs();\n\n    TypeInformation integer = Types.integer();\n\n    Column column = new Column(0, \"foo\", this.integer, null);\n\n    ByteBuf data = Unpooled.wrappedBuffer(new byte[]{(byte) 0x4, 0x42, 0, 0, 0});\n\n    RowToken rowToken = RowToken.decode(this.data, new Column[]{this.column});\n\n    MssqlRowMetadata rowMetadata = new MssqlRowMetadata(this.codecs, new Column[]{this.column}, Collections.singletonMap(\"foo\", this.column));\n\n    MssqlRow row = new MssqlRow(this.codecs, this.rowToken, this.rowMetadata);\n\n    @Test\n    void shouldReadRowByIndex() {\n        assertThat(this.row.get(0)).isEqualTo(66);\n        assertThat(this.row.get(0, Short.class)).isEqualTo((short) 66);\n        assertThat(this.row.get(0, Integer.class)).isEqualTo(66);\n        assertThat(this.row.get(0, Long.class)).isEqualTo(66L);\n    }\n\n    @Test\n    void shouldReadRowByName() {\n        assertThat(this.row.get(\"foo\")).isEqualTo(66);\n        assertThat(this.row.get(\"foo\", Integer.class)).isEqualTo(66);\n    }\n\n    @Test\n    void releaseShouldDeallocateResources() {\n\n        this.row.release();\n        assertThatThrownBy(() -> this.row.get(\"foo\")).isInstanceOf(IllegalStateException.class).hasMessage(\"Value cannot be retrieved after row has been released\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlSegmentResultUnitTests.java",
    "content": "/*\n * Copyright 2021-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.util.ReferenceCounted;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.token.ColumnMetadataToken;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.InfoToken;\nimport io.r2dbc.mssql.message.token.NbcRowToken;\nimport io.r2dbc.mssql.message.token.ReturnValue;\nimport io.r2dbc.mssql.message.token.RowToken;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.Types;\nimport io.r2dbc.spi.R2dbcNonTransientException;\nimport io.r2dbc.spi.R2dbcNonTransientResourceException;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.util.Arrays;\nimport java.util.function.Function;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link MssqlSegmentResult}.\n *\n * @author Mark Paluch\n */\nclass MssqlSegmentResultUnitTests {\n\n    ErrorToken errorToken = new ErrorToken(0, 0, 0, 0, \"error message desc\", \"\", \"\", 0);\n\n    InfoToken infoToken = new InfoToken(0, 0, 0, 0, \"error message desc\", \"\", \"\", 0);\n\n    TypeInformation integerType = Types.integer();\n\n    TypeInformation stringType = Types.varchar(255);\n\n    Column[] columns = Arrays.asList(new Column(0, \"id\", this.integerType),\n        new Column(1, \"first_name\", this.stringType),\n        new Column(2, \"last_name\", this.stringType),\n        new Column(3, \"other\", this.stringType),\n        new Column(4, \"other2\", this.stringType),\n        new Column(5, \"other3\", this.stringType),\n        new Column(6, \"rowstat\", this.integerType)).toArray(new Column[0]);\n\n    DefaultCodecs codecs = new DefaultCodecs();\n\n    private NbcRowToken getRowToken() {\n        return NbcRowToken.decode(HexUtils.decodeToByteBuf(\"D2 1C 04 01 00 00 00 01 00 61 02 00 78 61 04 01 00 00 00\").skipBytes(1), this.columns);\n    }\n\n    @Test\n    void shouldApplyRowMapping() {\n\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(ColumnMetadataToken.create(this.columns), getRowToken()), false);\n\n        result.map((row, rowMetadata) -> row)\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldApplyOutParameterMapping() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"AC0000000100000000000026\" +\n            \"0404F3DEBC0A\").skipBytes(1);\n\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(ReturnValue.decode(buffer, false)), true);\n\n        result.map((readable) -> readable)\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n    @Test\n    void mapShouldIgnoreNotice() {\n\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(this.infoToken), false);\n\n        result.map((row, rowMetadata) -> row)\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    @Test\n    void mapShouldTerminateWithError() {\n\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(this.errorToken), false);\n\n        result.map((row, rowMetadata) -> row)\n            .as(StepVerifier::create)\n            .verifyError(R2dbcNonTransientException.class);\n    }\n\n    @Test\n    void getRowsUpdatedShouldTerminateWithError() {\n\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(this.errorToken), false);\n\n        result.getRowsUpdated()\n            .as(StepVerifier::create)\n            .verifyError(R2dbcNonTransientException.class);\n    }\n\n    @Test\n    void shouldConsumeRowsUpdated() {\n\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(DoneToken.count(42)), false);\n\n        result.getRowsUpdated()\n            .as(StepVerifier::create)\n            .expectNext(42L)\n            .verifyComplete();\n    }\n\n    @Test\n    void filterShouldRetainUpdateCount() {\n\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(DoneToken.count(42)), false);\n\n        result.filter(Result.UpdateCount.class::isInstance).getRowsUpdated()\n            .as(StepVerifier::create)\n            .expectNext(42L)\n            .verifyComplete();\n    }\n\n    @Test\n    void filterShouldSkipRowMapping() {\n\n        MssqlResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(ColumnMetadataToken.create(this.columns), getRowToken()), false);\n\n        result = result.filter(it -> false);\n\n        result.map((row, rowMetadata) -> row)\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    @Test\n    void filterShouldSkipErrorMessage() {\n\n        MssqlResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(this.errorToken, ColumnMetadataToken.create(this.columns), getRowToken()), false);\n\n        result = result.filter(Result.RowSegment.class::isInstance);\n\n        result.map((row, rowMetadata) -> row)\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n    @ValueSource(booleans = {true, false})\n    @ParameterizedTest\n    void mapRowShouldDeallocateRowResources(boolean expectReturnValues) {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"AC0000000100000000000026\" +\n            \"0404F3DEBC0A\").skipBytes(1);\n\n        ReturnValue returnValue = ReturnValue.decode(buffer, false);\n        buffer.release();\n\n        RowToken dataRow = getRowToken();\n        assertThat(dataRow.refCnt()).isOne();\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(ColumnMetadataToken.create(this.columns), dataRow, returnValue),\n            expectReturnValues);\n\n        result.map((row, rowMetadata) -> row)\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n\n        assertThat(dataRow.refCnt()).isZero();\n        assertThat(returnValue.refCnt()).isZero();\n        assertThat(buffer.refCnt()).isZero();\n    }\n\n    @ValueSource(booleans = {true, false})\n    @ParameterizedTest\n    void mapShouldDeallocateRowResources(boolean expectReturnValues) {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"AC0000000100000000000026\" +\n            \"0404F3DEBC0A\").skipBytes(1);\n\n        ReturnValue returnValue = ReturnValue.decode(buffer, false);\n        buffer.release();\n\n        RowToken dataRow = getRowToken();\n        assertThat(dataRow.refCnt()).isOne();\n        MssqlSegmentResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(ColumnMetadataToken.create(this.columns), dataRow, returnValue), expectReturnValues);\n\n        result.map(Function.identity())\n            .as(StepVerifier::create)\n            .expectNextCount(expectReturnValues ? 2 : 1)\n            .verifyComplete();\n\n        assertThat(dataRow.refCnt()).isZero();\n        assertThat(returnValue.refCnt()).isZero();\n        assertThat(buffer.refCnt()).isZero();\n    }\n\n    @ValueSource(booleans = {true, false})\n    @ParameterizedTest\n    void filterShouldDeallocateResources(boolean expectReturnValues) {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"AC0000000100000000000026\" +\n            \"0404F3DEBC0A\").skipBytes(1);\n\n        ReturnValue returnValue = ReturnValue.decode(buffer, false);\n        buffer.release();\n\n        RowToken dataRow = getRowToken();\n        assertThat(dataRow.refCnt()).isOne();\n        MssqlResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(ColumnMetadataToken.create(this.columns), dataRow, returnValue), expectReturnValues);\n\n        result = result.filter(it -> false);\n\n        result.map((row, rowMetadata) -> row)\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        assertThat(dataRow.refCnt()).isZero();\n        assertThat(returnValue.refCnt()).isZero();\n        assertThat(buffer.refCnt()).isZero();\n    }\n\n    @Test\n    void flatMapShouldDeallocateResourcesAfterConsumption() {\n\n        RowToken dataRow = getRowToken();\n        assertThat(dataRow.refCnt()).isOne();\n        MssqlResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(ColumnMetadataToken.create(this.columns), dataRow), false);\n\n        Flux.from(result.flatMap(Mono::just))\n            .map(it -> {\n                assertThat(((ReferenceCounted) it).refCnt()).isOne();\n                return it;\n            })\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n\n        assertThat(dataRow.refCnt()).isZero();\n    }\n\n    @Test\n    void flatMapShouldNotTerminateWithError() {\n\n        MssqlResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(this.errorToken, ColumnMetadataToken.create(this.columns), getRowToken(),\n            DoneToken.create(42)), false);\n\n        Flux.from(result.flatMap(Mono::just))\n            .as(StepVerifier::create)\n            .expectNextCount(3)\n            .verifyComplete();\n    }\n\n    @Test\n    void emptyFlatMapShouldDeallocateResourcesAfterConsumption() {\n\n        RowToken dataRow = getRowToken();\n        assertThat(dataRow.refCnt()).isOne();\n        MssqlResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(ColumnMetadataToken.create(this.columns), dataRow), false);\n\n        Flux.from(result.flatMap(data -> Mono.empty()))\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        assertThat(dataRow.refCnt()).isZero();\n    }\n\n    @Test\n    void flatMapShouldMapErrorResponse() {\n\n        MssqlResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(this.errorToken), false);\n\n        Flux.from(result.flatMap(data -> {\n\n            assertThat(data).isInstanceOf(Result.Message.class);\n\n            Result.Message message = (Result.Message) data;\n            assertThat(message.errorCode()).isZero();\n            assertThat(message.sqlState()).isEqualTo(\"S0000\");\n            assertThat(message.message()).isEqualTo(\"error message desc\");\n            assertThat(message.exception()).isInstanceOf(R2dbcNonTransientResourceException.class);\n\n            return Mono.just(data);\n        }))\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n    @Test\n    void flatMapShouldMapNoticeResponse() {\n\n        MssqlResult result = MssqlSegmentResult.toResult(\"\", new ConnectionContext(), this.codecs, Flux.just(this.infoToken), false);\n\n        Flux.from(result.flatMap(data -> {\n\n            assertThat(data).isInstanceOf(Result.Message.class);\n\n            Result.Message message = (Result.Message) data;\n            assertThat(message.errorCode()).isZero();\n            assertThat(message.sqlState()).isEqualTo(\"S0000\");\n            assertThat(message.message()).isEqualTo(\"error message desc\");\n            assertThat(message.exception()).isInstanceOf(R2dbcNonTransientResourceException.class);\n\n            return Mono.just(data);\n        }))\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/MssqlTestKit.java",
    "content": "/*\n * Copyright 2017-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.ConnectionFactories;\nimport io.r2dbc.spi.ConnectionFactory;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport io.r2dbc.spi.test.TestKit;\nimport org.junit.jupiter.api.Disabled;\nimport org.springframework.jdbc.core.JdbcOperations;\n\nimport static io.r2dbc.mssql.MssqlConnectionFactoryProvider.MSSQL_DRIVER;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.HOST;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.PORT;\nimport static io.r2dbc.spi.ConnectionFactoryOptions.USER;\n\n/**\n * TCK Test Kit for SQL Server driver.\n */\nfinal class MssqlTestKit extends IntegrationTestSupport implements TestKit<String> {\n\n    private final ConnectionFactory connectionFactory = ConnectionFactories.get(ConnectionFactoryOptions.builder()\n        .option(DRIVER, MSSQL_DRIVER)\n        .option(HOST, SERVER.getHost())\n        .option(PORT, SERVER.getPort())\n        .option(PASSWORD, SERVER.getPassword())\n        .option(USER, SERVER.getUsername())\n        .build());\n\n    @Override\n    public String blobType() {\n        return \"VARBINARY(MAX)\";\n    }\n\n    @Override\n    public String clobType() {\n        return \"VARCHAR(MAX)\";\n    }\n\n    @Override\n    public ConnectionFactory getConnectionFactory() {\n        return this.connectionFactory;\n    }\n\n    @Override\n    public String getIdentifier(int index) {\n        return getPlaceholder(index);\n    }\n\n    @Override\n    public JdbcOperations getJdbcOperations() {\n        JdbcOperations jdbcOperations = SERVER.getJdbcOperations();\n\n        if (jdbcOperations == null) {\n            throw new IllegalStateException(\"JdbcOperations not yet initialized\");\n        }\n\n        return jdbcOperations;\n    }\n\n    @Override\n    public String expand(TestStatement statement, Object... args) {\n\n        if(statement == TestStatement.CREATE_TABLE_AUTOGENERATED_KEY){\n            return \"CREATE TABLE test (id INTEGER IDENTITY, test_value INTEGER)\";\n        }\n\n        return TestKit.super.expand(statement, args);\n    }\n\n    @Override\n    public String getPlaceholder(int index) {\n        return String.format(\"@P%d\", index);\n    }\n\n    @Override\n    @Disabled\n    public void prepareStatementWithIncompleteBatchFails() {\n    }\n\n    @Override\n    @Disabled\n    public void prepareStatementWithIncompleteBindingFails() {\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/ParametrizedMssqlStatementIntegrationTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.R2dbcTimeoutException;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Hooks;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.time.Duration;\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration tests for {@link ParametrizedMssqlStatement}.\n *\n * @author Mark Paluch\n */\nclass ParametrizedMssqlStatementIntegrationTests extends IntegrationTestSupport {\n\n    static {\n        Hooks.onOperatorDebug();\n    }\n\n    @Test\n    void shouldExecuteBatch() {\n\n        connection.createStatement(\"DROP TABLE r2dbc_example\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE r2dbc_example (\" +\n                \"id int PRIMARY KEY IDENTITY(1,1), \" +\n                \"first_name varchar(255), \" +\n                \"last_name varchar(255))\")\n                .execute().flatMap(MssqlResult::getRowsUpdated).then())\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example (first_name, last_name) values (@fn, @ln)\")\n                .bind(\"fn\", \"Walter\").bind(\"ln\", \"White\").add()\n                .bind(\"fn\", \"Hank\").bind(\"@ln\", \"Schrader\").add()\n                .bind(\"fn\", \"Skyler\").bind(\"@ln\", \"White\")\n                .execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L, 1L, 1L)\n            .verifyComplete();\n    }\n\n    @Test\n    void failureShouldNotLockUpConnection() {\n\n        connection.createStatement(\"DROP TABLE r2dbc_example\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE r2dbc_example (\" +\n                \"id int NOT NULL, \" +\n                \"first_name varchar(255), \" +\n                \"last_name varchar(255))\")\n                .execute().flatMap(MssqlResult::getRowsUpdated).then())\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        for (int i = 0; i < 10; i++) {\n\n            connection.createStatement(\"INSERT INTO r2dbc_example (id, first_name) VALUES(@P1, @P2)\")\n                .bindNull(\"@P1\", Integer.class)\n                .bind(\"@P2\", \"foo\")\n                .returnGeneratedValues()\n                .execute()\n                .flatMap(Result::getRowsUpdated)\n                .as(StepVerifier::create)\n                .verifyError();\n        }\n    }\n\n    @Test\n    void shouldDecodeNull() {\n\n        shouldExecuteBatch();\n\n        Flux.from(connection.createStatement(\"SELECT null, first_name FROM r2dbc_example\")\n            .execute())\n            .flatMap(result -> result.map((row, rowMetadata) -> {\n                return Optional.ofNullable(row.get(0));\n            }))\n            .as(StepVerifier::create)\n            .expectNext(Optional.empty(), Optional.empty(), Optional.empty())\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldRunQueryWithLocalVariableDeclarations() {\n\n        Flux.from(connection.createStatement(\"declare @i int = 1; select @i where @x = 1\")\n            .bind(\"x\", 1)\n            .execute())\n            .flatMap(it -> it.map((r, m) -> r.get(0)))\n            .as(StepVerifier::create).expectNextCount(1).verifyComplete();\n    }\n\n    @Test\n    void shouldEmitSingleResultForCursoredExecution() {\n\n        shouldExecuteBatch();\n\n        AtomicInteger resultCounter = new AtomicInteger();\n        AtomicInteger rowCounter = new AtomicInteger();\n\n        Flux.from(connection.createStatement(\"SELECT first_name FROM r2dbc_example\")\n            .fetchSize(2)\n            .execute())\n            .flatMap(result -> {\n\n                resultCounter.incrementAndGet();\n                return result.map((row, rowMetadata) -> new Object()).doOnNext(it -> rowCounter.incrementAndGet()).then();\n            })\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        assertThat(resultCounter).hasValue(1);\n        assertThat(rowCounter).hasValue(3);\n    }\n\n    @Test\n    void shouldRepreparePreparedStatement() {\n\n        shouldExecuteBatch();\n\n        connection.createStatement(\"SET ANSI_NULLS ON\")\n            .execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"SELECT first_name FROM r2dbc_example where id != @P0\")\n            .fetchSize(2)\n            .bind(\"P0\", 99)\n            .execute())\n            .flatMap(result -> {\n\n                return result.map((row, rowMetadata) -> new Object());\n            })\n            .as(StepVerifier::create)\n            .expectNextCount(3)\n            .verifyComplete();\n\n        connection.createStatement(\"SET ANSI_NULLS OFF\")\n            .execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"SELECT first_name FROM r2dbc_example where id != @P0\")\n            .fetchSize(2)\n            .bind(\"P0\", 99)\n            .execute())\n            .flatMap(result -> {\n\n                return result.map((row, rowMetadata) -> new Object());\n            })\n            .as(StepVerifier::create)\n                .expectNextCount(3)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldRunStatementWithMultipleResults() {\n\n        AtomicLong resultCounter = new AtomicLong();\n        AtomicLong firstUpdateCount = new AtomicLong();\n        AtomicInteger rowCount = new AtomicInteger();\n\n        Flux.from(connection.createStatement(\"DECLARE @t TABLE(i INT);INSERT INTO @t VALUES (@P1),(2),(3);SELECT * FROM @t;\\n\")\n            .bind(\"@P1\", 1)\n            .execute()).flatMap(it -> {\n\n            if (resultCounter.compareAndSet(0, 1)) {\n                return it.getRowsUpdated().doOnNext(firstUpdateCount::set).then();\n            }\n\n            if (resultCounter.incrementAndGet() == 2) {\n                return it.map(((row, rowMetadata) -> {\n                    rowCount.incrementAndGet();\n\n                    return new Object();\n                })).then();\n            }\n\n            throw new IllegalStateException(\"Unexpected result\");\n        }).as(StepVerifier::create).verifyComplete();\n\n        assertThat(resultCounter).hasValue(2);\n        assertThat(firstUpdateCount).hasValue(3);\n        assertThat(rowCount).hasValue(3);\n    }\n\n    @Test\n    void shouldRunStatementWithMultipleBindingsAndResults() {\n\n        AtomicBoolean firstGurard = new AtomicBoolean();\n        AtomicBoolean secondGurard = new AtomicBoolean();\n        AtomicBoolean thirdGurard = new AtomicBoolean();\n        AtomicBoolean fourthGurard = new AtomicBoolean();\n\n        AtomicLong firstUpdateCount = new AtomicLong();\n        AtomicLong secondUpdateCount = new AtomicLong();\n        AtomicInteger rowCount = new AtomicInteger();\n\n        Flux.from(connection.createStatement(\"DECLARE @t TABLE(i INT);INSERT INTO @t VALUES (@P1),(2),(3);SELECT * FROM @t;\\n\")\n            .bind(\"@P1\", 1).add()\n            .bind(\"@P1\", 2)\n            .execute()).flatMap(it -> {\n\n            if (firstGurard.compareAndSet(false, true)) {\n                return it.getRowsUpdated().doOnNext(firstUpdateCount::set).then();\n            }\n\n            if (secondGurard.compareAndSet(false, true)) {\n                return it.map(((row, rowMetadata) -> {\n                    rowCount.incrementAndGet();\n\n                    return new Object();\n                })).then();\n            }\n\n            if (thirdGurard.compareAndSet(false, true)) {\n                return it.getRowsUpdated().doOnNext(secondUpdateCount::set).then();\n            }\n\n            if (fourthGurard.compareAndSet(false, true)) {\n                return it.map(((row, rowMetadata) -> {\n                    rowCount.incrementAndGet();\n\n                    return new Object();\n                })).then();\n            }\n\n            throw new IllegalStateException(\"Unexpected result\");\n        }).as(StepVerifier::create).verifyComplete();\n\n        assertThat(firstUpdateCount).hasValue(3);\n        assertThat(secondUpdateCount).hasValue(3);\n        assertThat(rowCount).hasValue(6);\n    }\n\n    @Test\n    void shouldTimeoutSqlBatch() {\n\n        connection.setStatementTimeout(Duration.ofMillis(100)).as(StepVerifier::create).verifyComplete();\n\n        connection.createStatement(\"WAITFOR DELAY @P0\").fetchSize(0).bind(\"P0\", \"10:00\").execute().flatMap(Result::getRowsUpdated).as(StepVerifier::create).verifyError(R2dbcTimeoutException.class);\n        connection.createStatement(\"SELECT 1\").execute().flatMap(it -> it.map(row -> row.get(0))).as(StepVerifier::create).expectNext(1).verifyComplete();\n    }\n\n    @Test\n    void shouldTimeoutCursored() {\n\n        connection.setStatementTimeout(Duration.ofMillis(100)).as(StepVerifier::create).verifyComplete();\n\n        connection.createStatement(\"WAITFOR DELAY @P0\").fetchSize(1).bind(\"P0\", \"10:00\").execute().flatMap(Result::getRowsUpdated).as(StepVerifier::create).verifyError(R2dbcTimeoutException.class);\n        connection.createStatement(\"SELECT 1\").execute().flatMap(it -> it.map(row -> row.get(0))).as(StepVerifier::create).expectNext(1).verifyComplete();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/ParametrizedMssqlStatementStoredProcedureIntegrationTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.Parameters;\nimport io.r2dbc.spi.R2dbcType;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.dao.DataAccessException;\nimport reactor.test.StepVerifier;\n\n/**\n * Integration tests for {@link ParametrizedMssqlStatement} to call stored procedures.\n *\n * @author Mark Paluch\n */\nclass ParametrizedMssqlStatementStoredProcedureIntegrationTests extends IntegrationTestSupport {\n\n    @BeforeEach\n    void setUp() {\n\n        try {\n            SERVER.getJdbcOperations().execute(\"DROP PROCEDURE test_proc\");\n        } catch (DataAccessException ignore) {\n        }\n\n        SERVER.getJdbcOperations().execute(\"CREATE PROCEDURE test_proc\\n\" +\n            \"    @TheName nvarchar(50),\\n\" +\n            \"    @Greeting nvarchar(255) OUTPUT\\n\" +\n            \"AS\\n\" +\n            \"\\n\" +\n            \"    SET NOCOUNT ON;  \\n\" +\n            \"    SET @Greeting = CONCAT('Hello ', @TheName)\");\n    }\n\n    @Test\n    void shouldCallProcedure() {\n\n        connection.createStatement(\"EXEC test_proc @P0, @Greeting OUTPUT\")\n            .bind(\"@P0\", \"Walter\")\n            .bind(\"@Greeting\", Parameters.out(R2dbcType.VARCHAR))\n            .execute()\n            .flatMap(it -> it.map((readable) -> {\n                return readable.get(0);\n            }))\n            .as(StepVerifier::create)\n            .expectNext(\"Hello Walter\")\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldCallProcedureWithFetchSize() {\n\nconnection.createStatement(\"EXEC test_proc @P0, @Greeting OUTPUT\")\n    .fetchSize(256)\n    .bind(\"@P0\", \"Walter\")\n    .bind(\"@Greeting\", Parameters.out(R2dbcType.VARCHAR))\n    .execute()\n    .flatMap(it -> it.map((readable) -> {\n        return readable.get(0);\n    }))\n            .as(StepVerifier::create)\n            .expectNext(\"Hello Walter\")\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldCallProcedureAsSegment() {\n\n        connection.createStatement(\"EXEC test_proc @P0, @Greeting OUTPUT\")\n            .bind(\"@P0\", \"Walter\")\n            .bind(\"@Greeting\", Parameters.out(R2dbcType.VARCHAR))\n            .execute()\n            .flatMap(it -> it.filter(s -> true).map((readable) -> {\n                return readable.get(0);\n            }))\n            .as(StepVerifier::create)\n            .expectNext(\"Hello Walter\")\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldCallProcedureWithFetchSizeAsSegment() {\n\n        connection.createStatement(\"EXEC test_proc @P0, @Greeting OUTPUT\")\n            .fetchSize(256)\n            .bind(\"@P0\", \"Walter\")\n            .bind(\"@Greeting\", Parameters.out(R2dbcType.VARCHAR))\n            .execute()\n            .flatMap(it -> it.filter(s -> true).map((readable) -> {\n                return readable.get(0);\n            }))\n            .as(StepVerifier::create)\n            .expectNext(\"Hello Walter\")\n            .verifyComplete();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/ParametrizedMssqlStatementUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.ParametrizedMssqlStatement.ParsedParameter;\nimport io.r2dbc.mssql.client.TestClient;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.codec.Encoded;\nimport io.r2dbc.mssql.codec.RpcParameterContext;\nimport io.r2dbc.mssql.message.tds.ProtocolException;\nimport io.r2dbc.mssql.message.token.ErrorToken;\nimport io.r2dbc.mssql.message.token.ReturnValue;\nimport io.r2dbc.mssql.message.token.RpcRequest;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TdsDataType;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport io.r2dbc.mssql.util.Types;\nimport io.r2dbc.spi.Parameters;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\nimport java.math.BigDecimal;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\n\nimport static io.r2dbc.mssql.ParametrizedMssqlStatement.ParsedQuery;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\n/**\n * Unit tests for {@link ParametrizedMssqlStatement}.\n *\n * @author Mark Paluch\n */\nclass ParametrizedMssqlStatementUnitTests {\n\n    PreparedStatementCache statementCache = new IndefinitePreparedStatementCache();\n\n    ConnectionOptions connectionOptions = new ConnectionOptions(sql -> true, new DefaultCodecs(), this.statementCache, true);\n\n    @Test\n    void shouldSupportSql() {\n\n        assertThat(ParametrizedMssqlStatement.supports(\"SELECT * from FOO where firstname = @firstname\")).isTrue();\n        assertThat(ParametrizedMssqlStatement.supports(\"SELECT * from FOO where firstname =@firstname\")).isTrue();\n        assertThat(ParametrizedMssqlStatement.supports(\"SELECT * from FOO where firstname = @foo_bar\")).isTrue();\n\n        assertThat(ParametrizedMssqlStatement.supports(\"SELECT * from FOO where firstname = 'foo'\")).isFalse();\n    }\n\n    @Test\n    void shouldParseSql() {\n\n        List<ParsedParameter> variables = ParsedQuery.parse(\"SELECT * from FOO where firstname = @firstname\").getParameters();\n\n        assertThat(variables).hasSize(1);\n        assertThat(variables.get(0)).isEqualTo(new ParsedParameter(\"firstname\", 37));\n\n        variables = ParsedQuery.parse(\"SELECT * from FOO where @p1 = @foo_bar\").getParameters();\n\n        assertThat(variables).hasSize(2);\n        assertThat(variables.get(0)).isEqualTo(new ParsedParameter(\"p1\", 25));\n        assertThat(variables.get(1)).isEqualTo(new ParsedParameter(\"foo_bar\", 31));\n    }\n\n    @Test\n    void executeWithoutBindingsShouldNotFail() {\n\n        new ParametrizedMssqlStatement(TestClient.NO_OP, this.connectionOptions, \"SELECT * from FOO where firstname = @firstname\").execute();\n        new ParametrizedMssqlStatement(TestClient.NO_OP, this.connectionOptions, \"SELECT * FROM users WHERE email = 'name[@]gmail.com'\").execute();\n    }\n\n    @Test\n    void shouldBindParameterByIndex() {\n\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(TestClient.NO_OP, this.connectionOptions, \"SELECT * from FOO where firstname = @firstname\");\n\n        statement.bind(0, \"name\");\n        assertThat(statement.getBindings().first().getParameters()).containsKeys(\"firstname\");\n    }\n\n    @Test\n    void shouldRejectBindIndexOutOfBounds() {\n\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(TestClient.NO_OP, this.connectionOptions, \"SELECT * from FOO where firstname = @firstname\");\n\n        assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> statement.bind(-1, \"name\"));\n        assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> statement.bind(1, \"name\"));\n    }\n\n    @Test\n    void shouldBindParameterByName() {\n\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(TestClient.NO_OP, this.connectionOptions, \"SELECT * from FOO where firstname = @firstname\");\n\n        statement.bind(\"firstname\", \"firstname\");\n        assertThat(statement.getBindings().first().getParameters()).containsKeys(\"firstname\");\n    }\n\n    @Test\n    void shouldBindParameterByNameWithPrefix() {\n\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(TestClient.NO_OP, this.connectionOptions, \"SELECT * from FOO where firstname = @firstname\");\n\n        statement.bind(\"@firstname\", \"firstname\");\n        assertThat(statement.getBindings().first().getParameters()).containsKeys(\"firstname\");\n    }\n\n    @Test\n    void shouldBindParameter() {\n\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(TestClient.NO_OP, this.connectionOptions, \"SELECT * from FOO where amount1 = @amount1 and amount2 = @amount2\");\n\n        statement.bind(\"amount1\", Parameters.in(BigDecimal.valueOf(2.1f)));\n        statement.bind(\"amount2\", Parameters.in(SqlServerType.MONEY));\n\n        Map<String, Binding.RpcParameter> parameters = statement.getBindings().first().getParameters();\n        assertThat(parameters).containsKeys(\"amount1\", \"amount2\");\n\n        Binding.RpcParameter amount1 = parameters.get(\"amount1\");\n        assertThat(amount1.encoded.getDataType()).isEqualTo(TdsDataType.DECIMALN);\n\n        Binding.RpcParameter amount2 = parameters.get(\"amount2\");\n        assertThat(amount2.encoded.getDataType()).isEqualTo(TdsDataType.MONEYN);\n    }\n\n    @Test\n    void shouldRejectBindForUnknownParameters() {\n\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(TestClient.NO_OP, this.connectionOptions, \"SELECT * from FOO where firstname = @firstname\");\n\n        assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> statement.bind(\"foo\", \"name\"));\n    }\n\n    @Test\n    void shouldCachePreparedStatementHandle() {\n\n        Encoded encodedPreparedStatementHandle = new DefaultCodecs().encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), 1);\n        ByteBuf value = encodedPreparedStatementHandle.getValue();\n        value.skipBytes(1); // skip maxlen byte\n\n        TestClient testClient = TestClient.builder()\n            .assertNextRequestWith(it -> {\n                assertThat(it).isInstanceOf(RpcRequest.class);\n                RpcRequest request = (RpcRequest) it;\n                assertThat(request.getProcId()).isEqualTo(RpcRequest.Sp_CursorPrepExec);\n            })\n            .thenRespond(new ReturnValue(0, null, (byte) 0, Types.integer(),\n                    value))\n            .build();\n\n        String sql = \"SELECT * from FOO where firstname = @firstname\";\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(testClient, this.connectionOptions, sql);\n\n        statement.bind(\"firstname\", \"\");\n\n        Binding binding = statement.getBindings().getCurrent();\n\n        statement.execute().flatMap(MssqlResult::getRowsUpdated).subscribe();\n\n        assertThat(this.statementCache.getHandle(sql, binding)).isEqualTo(1);\n    }\n\n    @Test\n    void shouldReusePreparedStatementHandle() {\n\n        Encoded cursorId = new DefaultCodecs().encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), 123);\n        cursorId.getValue().skipBytes(1); // skip maxlen byte\n\n        TestClient testClient = TestClient.builder()\n            .assertNextRequestWith(it -> {\n                assertThat(it).isInstanceOf(RpcRequest.class);\n                RpcRequest request = (RpcRequest) it;\n                assertThat(request.getProcId()).isEqualTo(RpcRequest.Sp_CursorExecute);\n            })\n            .thenRespond(new ReturnValue(0, null, (byte) 0, Types.integer(),\n                cursorId.getValue()))\n            .build();\n\n        String sql = \"SELECT * from FOO where firstname = @firstname\";\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(testClient, this.connectionOptions, sql);\n\n        statement.bind(\"firstname\", \"\");\n\n        Binding binding = statement.getBindings().getCurrent();\n\n        this.statementCache.putHandle(1, sql, binding);\n\n        statement.execute().subscribe();\n\n        assertThat(this.statementCache.getHandle(sql, binding)).isEqualTo(1);\n        assertThat(this.statementCache.size()).isEqualTo(1);\n    }\n\n    @Test\n    void shouldPropagateError() {\n\n        TestClient testClient = TestClient.builder()\n            .assertNextRequestWith(it -> {\n                assertThat(it).isInstanceOf(RpcRequest.class);\n                RpcRequest request = (RpcRequest) it;\n                assertThat(request.getProcId()).isEqualTo(RpcRequest.Sp_ExecuteSql);\n            })\n            .thenRespond(new ErrorToken(0, 4002, (byte) 0, (byte) 16, \"failure\", \"\", \"\", 0))\n            .build();\n\n        String sql = \"SELECT * from FOO where firstname = @firstname\";\n        ParametrizedMssqlStatement statement = new ParametrizedMssqlStatement(testClient, this.connectionOptions, sql).fetchSize(0);\n\n        statement.bind(\"firstname\", \"\");\n        statement.execute().flatMap(MssqlResult::getRowsUpdated).as(StepVerifier::create).verifyError(ProtocolException.class);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/QueryMessageFlowUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.token.DoneInProcToken;\nimport io.r2dbc.mssql.message.token.DoneProcToken;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.test.StepVerifier;\n\nimport java.util.function.Predicate;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * Unit tests for {@link QueryMessageFlow}.\n *\n * @author Mark Paluch\n */\n@SuppressWarnings(\"unchecked\")\nclass QueryMessageFlowUnitTests {\n\n    Client client = mock(Client.class);\n\n    @BeforeEach\n    void setUp() {\n        when(this.client.getTransactionDescriptor()).thenReturn(TransactionDescriptor.empty());\n        when(this.client.getContext()).thenReturn(new ConnectionContext());\n    }\n\n    @Test\n    void shouldAwaitDoneProcTokenShouldNotCompleteFlow() {\n\n        when(this.client.exchange(any(Publisher.class), any(Predicate.class))).thenReturn(Flux.just(DoneToken.more(20), DoneProcToken.create(0), DoneInProcToken.create(0)));\n\n        QueryMessageFlow.exchange(this.client, \"foo\")\n            .as(StepVerifier::create)\n            .expectNext(DoneToken.more(20), DoneProcToken.create(0), DoneInProcToken.create(0))\n            .thenCancel()\n            .verify();\n    }\n\n    @Test\n    void shouldAwaitDoneToken() {\n\n        when(this.client.exchange(any(Publisher.class), any(Predicate.class))).thenReturn(Flux.just(DoneInProcToken.create(0), DoneToken.create(0)));\n\n        QueryMessageFlow.exchange(this.client, \"foo\")\n            .as(StepVerifier::create)\n            .expectNext(DoneInProcToken.create(0), DoneToken.create(0))\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/ReturnGeneratedValuesIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.Statement;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration tests for {@link Statement#returnGeneratedValues(String...)}.\n *\n * @author Mark Paluch\n */\nclass ReturnGeneratedValuesIntegrationTests extends IntegrationTestSupport {\n\n    @BeforeEach\n    void setUp() {\n        createTable(connection);\n    }\n\n    @Test\n    void simpleStatementShouldReturnNumberOfInsertedRows() {\n\n        Flux<MssqlResult> result = connection.createStatement(\"INSERT INTO generated_values (Name) VALUES ('Timmy'), ('Johnny')\")  //\n            .returnGeneratedValues() //\n            .execute();\n\n        verifyRowsUpdated(result);\n    }\n\n    @Test\n    void simpleStatementShouldReturnGeneratedValues() {\n\n        Flux<MssqlResult> result = connection.createStatement(\"INSERT INTO generated_values (Name) VALUES ('Timmy'), ('Johnny')\") //\n            .returnGeneratedValues(\"id\") //\n            .execute();\n\n        verifyGeneratedValues(result);\n    }\n\n    @Test\n    void preparedStatementShouldReturnNumberOfInsertedRows() {\n\n        Flux<MssqlResult> result = connection.createStatement(\"INSERT INTO generated_values (Name) VALUES (@P0), (@P1)\")  //\n            .bind(\"P0\", \"Timmy\").bind(\"P1\", \"Johnny\")\n            .returnGeneratedValues() //\n            .execute();\n\n        verifyRowsUpdated(result);\n    }\n\n    @Test\n    void preparedStatementShouldReturnGeneratedValues() {\n\n        Flux<MssqlResult> result = connection.createStatement(\"INSERT INTO generated_values (Name) VALUES (@P0), (@P1)\") //\n            .bind(\"P0\", \"Timmy\").bind(\"P1\", \"Johnny\")\n            .returnGeneratedValues(\"id\") //\n            .execute();\n\n        verifyGeneratedValues(result);\n    }\n\n    private static void verifyRowsUpdated(Flux<MssqlResult> result) {\n\n        AtomicInteger resultCounter = new AtomicInteger();\n\n        result.flatMap(it -> {\n                resultCounter.incrementAndGet();\n                return it.getRowsUpdated();\n            }).as(StepVerifier::create)\n            .expectNext(2L)\n            .verifyComplete();\n\n        assertThat(resultCounter).hasValue(1);\n    }\n\n    private static void verifyGeneratedValues(Flux<MssqlResult> result) {\n\n        result.flatMap(it -> it.map((row, rowMetadata) -> row.get(\"id\"))) //\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n    }\n\n    private void createTable(MssqlConnection connection) {\n\n        connection.createStatement(\"DROP TABLE generated_values\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE generated_values (\" +\n                \"id int IDENTITY PRIMARY KEY, \" +\n                \"name varchar(255))\")\n                .execute().flatMap(MssqlResult::getRowsUpdated).then())\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/RpcBlobUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.util.concurrent.ImmediateEventExecutor;\nimport io.r2dbc.mssql.client.TdsEncoder;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.codec.PlpEncoded;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.codec.RpcParameterContext;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.header.PacketIdProvider;\nimport io.r2dbc.mssql.message.token.RpcRequest;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.spi.Blob;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.scheduler.Schedulers;\nimport reactor.test.StepVerifier;\n\nimport java.nio.ByteBuffer;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * Unit test for {@link Blob} encoding via {@link PlpEncoded} and {@link TdsEncoder}.\n *\n * @author Mark Paluch\n */\npublic class RpcBlobUnitTests {\n\n    static byte[] ALL_BYTES = new byte[-(-128) + 127];\n\n    static {\n        for (int i = -128; i < 127; i++) {\n            ALL_BYTES[-(-128) + i] = (byte) i;\n        }\n    }\n\n    @Test\n    void shouldEncodeChunkedStream() {\n\n        int segmentsToGenerate = 3501;\n\n        Blob blob = Blob.from(Flux.range(0, segmentsToGenerate).map(it -> ByteBuffer.wrap(ALL_BYTES)));\n\n        DefaultCodecs codecs = new DefaultCodecs();\n\n        Binding binding = new Binding();\n        binding.add(\"P0\", RpcDirection.IN, codecs.encode(ByteBufAllocator.DEFAULT, RpcParameterContext.in(), blob));\n\n        RpcRequest request = RpcQueryMessageFlow.spExecuteSql(\"INSERT INTO lob_test values(@P0)\", binding, Collation.RAW, TransactionDescriptor.empty());\n\n        TdsEncoder encoder = new TdsEncoder(PacketIdProvider.just(1), 8000);\n        ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);\n        when(ctx.alloc()).thenReturn(ByteBufAllocator.DEFAULT);\n        when(ctx.executor()).thenReturn(ImmediateEventExecutor.INSTANCE);\n        ChannelPromise promise = mock(ChannelPromise.class);\n        when(ctx.newPromise()).thenReturn(promise);\n        when(ctx.write(any(), any(ChannelPromise.class))).then(invocationOnMock -> {\n\n            ByteBuf buf = invocationOnMock.getArgument(0);\n\n            int toRead = buf.readableBytes();\n            byte[] bytes = new byte[toRead];\n            buf.readBytes(bytes);\n\n            if (buf != Unpooled.EMPTY_BUFFER) {\n                buf.release();\n            }\n\n            return invocationOnMock.getArgument(1);\n        });\n\n        Flux.from(request.encode(ByteBufAllocator.DEFAULT, 8000))\n            .publishOn(Schedulers.parallel())\n            .doOnNext(it -> encoder.write(ctx, it, promise))\n            .as(StepVerifier::create)\n            .expectNextCount(32)\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/RpcQueryMessageFlowUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.RpcQueryMessageFlow.CursorState;\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.codec.DefaultCodecs;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.codec.RpcParameterContext;\nimport io.r2dbc.mssql.codec.RpcParameterContext.ValueContext;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.token.AllHeaders;\nimport io.r2dbc.mssql.message.token.RpcRequest;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.util.ClientMessageAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Sinks;\nimport reactor.core.publisher.SynchronousSink;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\n/**\n * Unit tests for {@link RpcQueryMessageFlow}.\n *\n * @author Mark Paluch\n */\n@SuppressWarnings(\"unchecked\")\nclass RpcQueryMessageFlowUnitTests {\n\n    Client client = mock(Client.class);\n\n    Sinks.Many<ClientMessage> requests = mock(Sinks.Many.class);\n\n    SynchronousSink<Message> sink = mock(SynchronousSink.class);\n\n    Runnable completion = mock(Runnable.class);\n\n    DefaultCodecs codecs = new DefaultCodecs();\n\n    // windows-1252\n    Collation collation = Collation.from(13632521, 52);\n\n    @BeforeEach\n    void setUp() {\n        when(this.client.getTransactionDescriptor()).thenReturn(TransactionDescriptor.empty());\n    }\n\n    @Test\n    void shouldEncodeSpExecuteSql() {\n\n        Binding binding = new Binding();\n        binding.add(\"P0\", RpcDirection.IN, this.codecs.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(ValueContext.character(this.collation, true)), \"mark\"));\n\n        RpcRequest rpcRequest = RpcQueryMessageFlow.spExecuteSql(\"SELECT * FROM my_table\", binding, this.collation, TransactionDescriptor.empty());\n\n        String hex = \"ff ff 0a 00 00 00 00 00 e7 40\" +\n            \"1f 09 04 d0 00 34 2c 00 53 00 45 00 4c 00 45 00\" +\n            \"43 00 54 00 20 00 2a 00 20 00 46 00 52 00 4f 00\" +\n            \"4d 00 20 00 6d 00 79 00 5f 00 74 00 61 00 62 00\" +\n            \"6c 00 65 00 00 00 e7 40 1f 09 04 d0 00 34 24 00\" +\n            \"40 00 50 00 30 00 20 00 6e 00 76 00 61 00 72 00\" +\n            \"63 00 68 00 61 00 72 00 28 00 34 00 30 00 30 00\" +\n            \"30 00 29 00 03 40 00 50 00 30 00 00 e7 40 1f 09\" +\n            \"04 d0 00 34 08 00 6d 00 61 00 72 00 6b 00\";\n\n        ClientMessageAssert.assertThat(rpcRequest).encoded()\n            .hasHeader(HeaderOptions.create(Type.RPC, Status.empty()))\n            .isEncodedAs(expected -> {\n\n                AllHeaders.transactional(TransactionDescriptor.empty(), 1).encode(expected);\n\n                expected.writeBytes(HexUtils.decodeToByteBuf(hex));\n            });\n    }\n\n    @Test\n    void shouldEncodeSpCursorOpen() {\n\n        RpcRequest rpcRequest = RpcQueryMessageFlow.spCursorOpen(\"SELECT * FROM my_table\", this.collation, TransactionDescriptor.empty());\n\n        String hex = \"FFFF020000000001260404000000000000E7\" +\n            \"401F0904D000342C00530045004C0045\" +\n            \"004300540020002A002000460052004F\" +\n            \"004D0020006D0079005F007400610062\" +\n            \"006C0065000000260404040000000000\" +\n            \"26040401200000000126040400000000\";\n\n        ClientMessageAssert.assertThat(rpcRequest).encoded()\n            .hasHeader(HeaderOptions.create(Type.RPC, Status.empty()))\n            .isEncodedAs(expected -> {\n\n                AllHeaders.transactional(TransactionDescriptor.empty(), 1).encode(expected);\n\n                expected.writeBytes(HexUtils.decodeToByteBuf(hex));\n            });\n    }\n\n    @Test\n    void shouldEncodeSpCursorFetch() {\n\n        RpcRequest rpcRequest = RpcQueryMessageFlow.spCursorFetch(180150003, RpcQueryMessageFlow.FETCH_NEXT, 128, TransactionDescriptor.empty());\n\n        String hex =\n            \"FFFF070002000000260404F3DEBC0A000026\" +\n                \"04040200000000002604040000000000\" +\n                \"0026040480000000\";\n\n        ClientMessageAssert.assertThat(rpcRequest).encoded()\n            .hasHeader(HeaderOptions.create(Type.RPC, Status.empty()))\n            .isEncodedAs(expected -> {\n\n                AllHeaders.transactional(TransactionDescriptor.empty(), 1).encode(expected);\n\n                expected.writeBytes(HexUtils.decodeToByteBuf(hex));\n            });\n    }\n\n    @Test\n    void shouldEncodeSpCursorClose() {\n\n        RpcRequest rpcRequest = RpcQueryMessageFlow.spCursorClose(180150003, TransactionDescriptor.empty());\n\n        String hex = \"FFFF090000000000260404F3DEBC0A\";\n\n        ClientMessageAssert.assertThat(rpcRequest).encoded()\n            .hasHeader(HeaderOptions.create(Type.RPC, Status.empty()))\n            .isEncodedAs(expected -> {\n\n                AllHeaders.transactional(TransactionDescriptor.empty(), 1).encode(expected);\n\n                expected.writeBytes(HexUtils.decodeToByteBuf(hex));\n            });\n    }\n\n    @Test\n    void shouldEncodeSpPrepExec() {\n\n        String hex = \"ff ff 05 00 00 00 00 01 26 04\" +\n            \"04 00 00 00 00 00 01 26 04 04 00 00 00 00 00 00\" +\n            \"e7 40 1f 09 04 d0 00 34 24 00 40 00 50 00 30 00\" +\n            \"20 00 6e 00 76 00 61 00 72 00 63 00 68 00 61 00\" +\n            \"72 00 28 00 34 00 30 00 30 00 30 00 29 00 00 00\" +\n            \"e7 40 1f 09 04 d0 00 34 48 00 55 00 50 00 44 00\" +\n            \"41 00 54 00 45 00 20 00 6d 00 79 00 5f 00 74 00\" +\n            \"61 00 62 00 6c 00 65 00 20 00 73 00 65 00 74 00\" +\n            \"20 00 66 00 69 00 72 00 73 00 74 00 5f 00 6e 00\" +\n            \"61 00 6d 00 65 00 20 00 3d 00 20 00 40 00 50 00\" +\n            \"30 00 00 00 26 04 04 04 10 00 00 00 00 26 04 04\" +\n            \"01 20 00 00 00 01 26 04 04 00 00 00 00 03 40 00\" +\n            \"50 00 30 00 00 e7 40 1f 09 04 d0 00 34 08 00 6d\" +\n            \"00 61 00 72 00 6b 00\";\n\n        String sql = \"UPDATE my_table set first_name = @P0\";\n\n        Binding binding = new Binding();\n        binding.add(\"P0\", RpcDirection.IN, this.codecs.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(ValueContext.character(this.collation, true)), \"mark\"));\n\n        RpcRequest rpcRequest = RpcQueryMessageFlow.spCursorPrepExec(0, sql, binding, this.collation, TransactionDescriptor.empty());\n\n        ClientMessageAssert.assertThat(rpcRequest).encoded()\n            .hasHeader(HeaderOptions.create(Type.RPC, Status.empty()))\n            .isEncodedAs(expected -> {\n\n                AllHeaders.transactional(TransactionDescriptor.empty(), 1).encode(expected);\n\n                expected.writeBytes(HexUtils.decodeToByteBuf(hex));\n            });\n    }\n\n    @Test\n    void shouldEncodeSpCursorExec() {\n\n        String hex = \"ff ff 04 00 00 00 00 00 26 04\" +\n            \"04 02 00 00 00 00 01 26 04 04 00 00 00 00 00 00\" +\n            \"26 04 04 04 00 00 00 00 00 26 04 04 01 20 00 00\" +\n            \"00 01 26 04 04 00 00 00 00 03 40 00 50 00 30 00\" +\n            \"00 e7 40 1f 09 04 d0 00 34 08 00 6d 00 61 00 72\" +\n            \"00 6b 00\";\n\n        Binding binding = new Binding();\n        binding.add(\"P0\", RpcDirection.IN, this.codecs.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(ValueContext.character(this.collation, true)), \"mark\"));\n\n        RpcRequest rpcRequest = RpcQueryMessageFlow.spCursorExec(2, binding, TransactionDescriptor.empty());\n\n        ClientMessageAssert.assertThat(rpcRequest).encoded()\n            .hasHeader(HeaderOptions.create(Type.RPC, Status.empty()))\n            .isEncodedAs(expected -> {\n\n                AllHeaders.transactional(TransactionDescriptor.empty(), 1).encode(expected);\n\n                expected.writeBytes(HexUtils.decodeToByteBuf(hex));\n            });\n    }\n\n    @Test\n    void shouldTransitionFromNoneToFetching() {\n\n        CursorState state = new CursorState();\n        state.cursorId = 42;\n        state.hasMore = true;\n\n        RpcQueryMessageFlow.onDone(this.client, 128, this.requests::tryEmitNext, state, this.completion);\n\n        assertThat(state.phase).isEqualTo(CursorState.Phase.FETCHING);\n        verify(this.requests).tryEmitNext(RpcQueryMessageFlow.spCursorFetch(state.cursorId, RpcQueryMessageFlow.FETCH_NEXT, 128, TransactionDescriptor.empty()));\n        verifyNoInteractions(this.completion);\n    }\n\n    @Test\n    void shouldContinueFetching() {\n\n        CursorState state = new CursorState();\n        state.cursorId = 42;\n        state.phase = CursorState.Phase.FETCHING;\n        state.hasSeenRows = true;\n\n        RpcQueryMessageFlow.onDone(this.client, 128, this.requests::tryEmitNext, state, this.completion);\n\n        assertThat(state.phase).isEqualTo(CursorState.Phase.FETCHING);\n        verify(this.requests).tryEmitNext(RpcQueryMessageFlow.spCursorFetch(state.cursorId, RpcQueryMessageFlow.FETCH_NEXT, 128, TransactionDescriptor.empty()));\n        verifyNoInteractions(this.completion);\n    }\n\n    @Test\n    void shouldStopFetching() {\n\n        CursorState state = new CursorState();\n        state.cursorId = 42;\n        state.phase = CursorState.Phase.FETCHING;\n\n        RpcQueryMessageFlow.onDone(this.client, 128, this.requests::tryEmitNext, state, this.completion);\n\n        assertThat(state.phase).isEqualTo(CursorState.Phase.CLOSING);\n        verify(this.requests).tryEmitNext(RpcQueryMessageFlow.spCursorClose(state.cursorId, TransactionDescriptor.empty()));\n        verifyNoInteractions(this.sink);\n    }\n\n    @Test\n    void shouldTransitionFromNoneToClosing() {\n\n        CursorState state = new CursorState();\n        state.cursorId = 42;\n\n        RpcQueryMessageFlow.onDone(this.client, 128, this.requests::tryEmitNext, state, this.completion);\n\n        assertThat(state.phase).isEqualTo(CursorState.Phase.CLOSING);\n        verify(this.requests).tryEmitNext(RpcQueryMessageFlow.spCursorClose(state.cursorId, TransactionDescriptor.empty()));\n        verifyNoInteractions(this.completion);\n    }\n\n    @Test\n    void shouldTransitionFromClosingToClosed() {\n\n        CursorState state = new CursorState();\n        state.cursorId = 42;\n        state.phase = CursorState.Phase.CLOSING;\n\n        RpcQueryMessageFlow.onDone(this.client, 128, this.requests::tryEmitNext, state, this.completion);\n\n        assertThat(state.phase).isEqualTo(CursorState.Phase.CLOSED);\n        verifyNoInteractions(this.requests);\n        verify(this.completion).run();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/SimpleMssqlStatementIntegrationTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.R2dbcTimeoutException;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\nimport java.time.Duration;\n\n/**\n * Integration tests for {@link SimpleMssqlStatement}.\n *\n * @author Mark Paluch\n */\nclass SimpleMssqlStatementIntegrationTests extends IntegrationTestSupport {\n\n    @Test\n    void shouldTimeoutSqlBatch() {\n\n        connection.setStatementTimeout(Duration.ofMillis(100)).as(StepVerifier::create).verifyComplete();\n\n        connection.createStatement(\"WAITFOR DELAY '10:00'\").fetchSize(0).execute().flatMap(Result::getRowsUpdated).as(StepVerifier::create).verifyError(R2dbcTimeoutException.class);\n        connection.createStatement(\"SELECT 1\").execute().flatMap(it -> it.map(row -> row.get(0))).as(StepVerifier::create).expectNext(1).verifyComplete();\n    }\n\n    @Test\n    void shouldTimeoutCursored() {\n\n        connection.setStatementTimeout(Duration.ofMillis(100)).as(StepVerifier::create).verifyComplete();\n\n        connection.createStatement(\"WAITFOR DELAY '10:00'\").fetchSize(100).execute().flatMap(Result::getRowsUpdated).as(StepVerifier::create).verifyError(R2dbcTimeoutException.class);\n        connection.createStatement(\"SELECT 1\").execute().flatMap(it -> it.map(row -> row.get(0))).as(StepVerifier::create).expectNext(1).verifyComplete();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/SimpleMssqlStatementUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.client.Client;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.client.TestClient;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.token.*;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.spi.ColumnMetadata;\nimport io.r2dbc.spi.R2dbcNonTransientResourceException;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\nimport reactor.util.annotation.Nullable;\n\nimport java.math.BigDecimal;\nimport java.nio.charset.Charset;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Predicate;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.Builder;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.*;\n\n/**\n * Unit tests for {@link SimpleMssqlStatement}.\n *\n * @author Mark Paluch\n */\nclass SimpleMssqlStatementUnitTests {\n\n    static final Column[] COLUMNS = Arrays.asList(createColumn(0, \"employee_id\", SqlServerType.TINYINT, 1, LengthStrategy.FIXEDLENTYPE, null),\n        createColumn(1, \"last_name\", SqlServerType.NVARCHAR, 100, LengthStrategy.USHORTLENTYPE, ServerCharset.UNICODE.charset()),\n\n        createColumn(2, \"first_name\", SqlServerType.VARCHAR, 50, LengthStrategy.USHORTLENTYPE, ServerCharset.CP1252.charset()),\n\n        createColumn(3, \"salary\", SqlServerType.MONEY, 8, LengthStrategy.BYTELENTYPE, null)).toArray(new Column[0]);\n\n    static final ConnectionOptions OPTIONS = new TestConnectionOptions();\n\n    @Test\n    void shouldReportNumberOfAffectedRows() {\n\n        SqlBatch batch = SqlBatch.create(1, TransactionDescriptor.empty(), \"SELECT * FROM foo\");\n\n        ColumnMetadataToken columns = ColumnMetadataToken.create(COLUMNS);\n\n        Tabular tabular = Tabular.create(columns, DoneToken.create(1));\n\n        TestClient client = TestClient.builder().expectRequest(batch).thenRespond(tabular.getTokens().toArray(new DataToken[0])).build();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\").fetchSize(0);\n\n        statement.execute()\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldReturnColumnData() {\n\n        TestClient client = simpleResultAndCount();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\").fetchSize(0);\n\n        statement.execute()\n            .flatMap(result -> result.map((row, md) -> {\n\n                Map<String, Object> rowData = new HashMap<>();\n                for (ColumnMetadata column : md.getColumnMetadatas()) {\n                    rowData.put(column.getName(), row.get(column.getName()));\n                }\n\n                return rowData;\n            }))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> {\n\n                assertThat(actual).containsEntry(\"first_name\", \"mark\").containsEntry(\"last_name\", \"paluch\");\n\n            })\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldFlatMapResults() {\n\n        TestClient client = simpleResultAndCount();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\").fetchSize(0);\n\n        statement.execute()\n            .flatMap(result -> result.flatMap(segment -> Mono.just(segment.toString())))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> {\n                assertThat(actual).contains(\"MssqlRow\");\n            })\n            .consumeNextWith(actual -> {\n                assertThat(actual).contains(\"DoneToken\");\n            })\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldFilterAndFlatMapCount() {\n\n        TestClient client = simpleResultAndCount();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\").fetchSize(0);\n\n        statement.execute()\n            .flatMap(result -> result.filter(Result.UpdateCount.class::isInstance).flatMap(segment -> Mono.just(segment.toString())))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> {\n                assertThat(actual).contains(\"DoneToken\");\n            })\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldFilterAndFlatMapData() {\n\n        TestClient client = simpleResultAndCount();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\").fetchSize(0);\n\n        statement.execute()\n            .flatMap(result -> result.filter(Result.RowSegment.class::isInstance).flatMap(segment -> {\n\n                Result.RowSegment data = (Result.RowSegment) segment;\n\n                Map<String, Object> rowData = new HashMap<>();\n                for (ColumnMetadata column : data.row().getMetadata().getColumnMetadatas()) {\n                    rowData.put(column.getName(), data.row().get(column.getName()));\n                }\n\n                return Mono.just(rowData);\n            }))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> {\n                assertThat(actual).containsEntry(\"first_name\", \"mark\").containsEntry(\"last_name\", \"paluch\");\n            })\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldFilterError() {\n\n        SqlBatch batch = SqlBatch.create(1, TransactionDescriptor.empty(), \"SELECT * FROM foo\");\n\n        TestClient client = TestClient.builder().expectRequest(batch).thenRespond(new ErrorToken(10, 10, (byte) 1, (byte) 1, \"foo\", null, null, 0)).build();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\").fetchSize(0);\n\n        statement.execute()\n            .flatMap(result -> result.filter(Result.RowSegment.class::isInstance).getRowsUpdated())\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldFlatMapErrorMessage() {\n\n        SqlBatch batch = SqlBatch.create(1, TransactionDescriptor.empty(), \"SELECT * FROM foo\");\n\n        TestClient client = TestClient.builder().expectRequest(batch).thenRespond(new ErrorToken(10, 10, (byte) 1, (byte) 2, \"foo\", null, null, 0)).build();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\").fetchSize(0);\n\n        statement.execute()\n            .flatMap(result -> result.filter(Result.Message.class::isInstance).flatMap(segment -> {\n                return Mono.just(segment).cast(Result.Message.class);\n            }))\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> {\n\n                assertThat(actual.errorCode()).isEqualTo(10);\n                assertThat(actual.message()).isEqualTo(\"foo\");\n                assertThat(actual.sqlState()).isEqualTo(\"S0001\");\n                assertThat(actual.exception()).isInstanceOf(R2dbcNonTransientResourceException.class);\n\n            })\n            .verifyComplete();\n    }\n\n    private TestClient simpleResultAndCount() {\n\n        SqlBatch batch = SqlBatch.create(1, TransactionDescriptor.empty(), \"SELECT * FROM foo\");\n\n        ColumnMetadataToken columns = ColumnMetadataToken.create(COLUMNS);\n\n        RowToken rowToken = RowTokenFactory.create(columns, buffer -> {\n\n            Encode.asByte(buffer, 1);\n            Encode.uString(buffer, \"paluch\", ServerCharset.UNICODE.charset());\n            Encode.uString(buffer, \"mark\", ServerCharset.CP1252.charset());\n\n            //money/salary\n            Encode.asByte(buffer, 8);\n            Encode.money(buffer, new BigDecimal(\"50.0000\").unscaledValue());\n        });\n\n        Tabular tabular = Tabular.create(columns, rowToken, DoneToken.create(1));\n\n        return TestClient.builder().expectRequest(batch).thenRespond(tabular.getTokens().toArray(new DataToken[0])).build();\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    void shouldPreferCursoredExecution() {\n\n        Client client = mockClient();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\");\n\n        statement.execute().as(StepVerifier::create)\n            .verifyComplete();\n\n        ArgumentCaptor<Publisher<Message>> captor = ArgumentCaptor.forClass(Publisher.class);\n\n        verify(client).exchange((Publisher) captor.capture(), any(Predicate.class));\n\n        StepVerifier.create(captor.getValue())\n            .consumeNextWith(it -> assertThat(it)\n                .isInstanceOf(RpcRequest.class))\n            .thenCancel()\n            .verify();\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    void shouldForceDirectExecution() {\n\n        Client client = mockClient();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"SELECT * FROM foo\").fetchSize(0);\n\n        statement.execute().as(StepVerifier::create)\n            .verifyComplete();\n\n        ArgumentCaptor<Mono<ClientMessage>> captor = ArgumentCaptor.forClass(Mono.class);\n\n        verify(client).exchange(captor.capture(), any(Predicate.class));\n        assertThat(captor.getValue().block()).isInstanceOf(SqlBatch.class);\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    void shouldPreferDirectExecution() {\n\n        Client client = mockClient();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"INSERT INTO\");\n\n        statement.execute().as(StepVerifier::create)\n            .verifyComplete();\n\n        ArgumentCaptor<Mono<ClientMessage>> captor = ArgumentCaptor.forClass(Mono.class);\n\n        verify(client).exchange(captor.capture(), any(Predicate.class));\n        assertThat(captor.getValue().block()).isInstanceOf(SqlBatch.class);\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    void shouldForceCursoredExecution() {\n\n        Client client = mockClient();\n\n        SimpleMssqlStatement statement = new SimpleMssqlStatement(client, OPTIONS, \"INSERT INTO\").fetchSize(1);\n\n        statement.execute().as(StepVerifier::create)\n            .verifyComplete();\n\n        ArgumentCaptor<Publisher<Message>> captor = ArgumentCaptor.forClass(Publisher.class);\n\n        verify(client).exchange((Publisher) captor.capture(), any(Predicate.class));\n\n        StepVerifier.create(captor.getValue())\n            .consumeNextWith(it -> assertThat(it)\n                .isInstanceOf(RpcRequest.class))\n            .thenCancel()\n            .verify();\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static Client mockClient() {\n\n        Client client = mock(Client.class);\n\n        when(client.getRequiredCollation()).thenReturn(Collation.RAW);\n        when(client.getTransactionDescriptor()).thenReturn(TransactionDescriptor.empty());\n        when(client.exchange(any(Publisher.class), any(Predicate.class))).thenReturn(Flux.empty());\n        when(client.getContext()).thenReturn(new ConnectionContext());\n\n        return client;\n    }\n\n    private static Column createColumn(int index, String name, SqlServerType serverType, int length, LengthStrategy lengthStrategy, @Nullable Charset charset) {\n\n        Builder builder = TypeInformation.builder().withServerType(serverType).withMaxLength(length).withLengthStrategy(lengthStrategy);\n        if (charset != null) {\n            builder.withCharset(charset);\n        }\n        TypeInformation type = builder.build();\n\n        return new Column(index, name, type, null);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/SqlVariantIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\n/**\n * Integration tests for SQL Variant showing that {@code sql_variant} is not supported.\n *\n * @author Mark Paluch\n */\nclass SqlVariantIntegrationTests extends IntegrationTestSupport {\n\n    @Test\n    void shouldExecuteBatch() {\n\n        connection.createStatement(\" SELECT SERVERPROPERTY('Edition')\").execute()\n            .flatMap(mssqlResult -> mssqlResult.map((row, rowMetadata) -> row.get(0)))\n            .as(StepVerifier::create)\n            .verifyError(UnsupportedOperationException.class);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/TestConnectionOptions.java",
    "content": "/*\n * Copyright 2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.codec.DefaultCodecs;\n\n/**\n * @author Mark Paluch\n */\nclass TestConnectionOptions extends ConnectionOptions {\n\n    TestConnectionOptions() {\n        super(MssqlConnectionConfiguration.DefaultCursorPreference.INSTANCE, new DefaultCodecs(), new IndefinitePreparedStatementCache(), true);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/TransactionIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.api.MssqlTransactionDefinition;\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.IsolationLevel;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.time.Duration;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration tests for transactional behavior.\n *\n * @author Mark Paluch\n */\nclass TransactionIntegrationTests extends IntegrationTestSupport {\n\n    @Test\n    void savepointsSynchronized() {\n\n        createTable(connection);\n\n        connection.beginTransaction()\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@P1, @P2, @P3)\")\n            .bind(0, 0).bind(1, \"Walter\").bind(2, \"White\").add()\n            .bind(0, 1).bind(1, \"Jesse\").bind(2, \"Pinkman\").execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNextCount(2)\n            .verifyComplete();\n\n        connection.createSavepoint(\"savepoint\")\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@P1, @P2, @P3)\")\n            .bind(0, 2).bind(1, \"Hank\").bind(2, \"Schrader\").execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"SELECT COUNT(*) FROM r2dbc_example\")\n            .execute())\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0, Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(3)\n            .verifyComplete();\n\n        connection.rollbackTransactionToSavepoint(\"savepoint\")\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"SELECT COUNT(*) FROM r2dbc_example /* in-tx */\")\n            .execute())\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0, Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(2)\n            .verifyComplete();\n\n        connection.commitTransaction()\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"SELECT COUNT(*) FROM r2dbc_example /* after-tx */\")\n            .execute())\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0, Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(2)\n            .verifyComplete();\n    }\n\n    @Test\n    void savepointsConcatWith() {\n\n        createTable(connection);\n\n        connection.beginTransaction()\n            .cast(Object.class)\n            .concatWith(Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@P1, @P2, @P3)\")\n                .bind(0, 0).bind(1, \"Walter\").bind(2, \"White\").execute())\n                .flatMap(Result::getRowsUpdated))\n            .concatWith(connection.createSavepoint(\"savepoint.1\"))\n            .concatWith(Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@P1, @P2, @P3)\")\n                .bind(0, 2).bind(1, \"Hank\").bind(2, \"Schrader\").execute())\n                .flatMap(Result::getRowsUpdated))\n            .concatWith(connection.rollbackTransactionToSavepoint(\"savepoint.1\"))\n            .concatWith(Flux.from(connection.createStatement(\"SELECT COUNT(*) FROM r2dbc_example /* in-tx */\")\n                    .execute())\n                .flatMap(result -> result.map((row, rowMetadata) -> row.get(0, Integer.class))))\n            .concatWith(connection.commitTransaction())\n            .concatWith(Flux.from(connection.createStatement(\"SELECT COUNT(*) FROM r2dbc_example /* after-tx */\")\n                    .execute())\n                .flatMap(result -> result.map((row, rowMetadata) -> row.get(0, Integer.class))))\n            .as(StepVerifier::create)\n            .expectNext(1L).as(\"Affected Rows Count from first INSERT\")\n            .expectNext(1L).as(\"Affected Rows Count from second INSERT\")\n            .expectNext(1).as(\"SELECT COUNT(*) after ROLLBACK TO SAVEPOINT\")\n            .expectNext(1).as(\"SELECT COUNT(*) after COMMIT\")\n            .verifyComplete();\n    }\n\n    @Test\n    void autoCommitDisabled() {\n\n        createTable(connection);\n\n        assertThat(connection.isAutoCommit()).isTrue();\n\n        connection.setAutoCommit(false)\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        assertThat(connection.isAutoCommit()).isFalse();\n\n        connection.createStatement(\"INSERT INTO r2dbc_example VALUES(0, 'Walter', 'White')\").execute()\n            .<Object>flatMap(Result::getRowsUpdated)\n            .concatWith(connection.rollbackTransaction())\n            .as(StepVerifier::create)\n            .expectNext(1L).as(\"Affected Rows Count from first INSERT\")\n            .verifyComplete();\n\n        connectionFactory.create().flatMapMany(c -> c.createStatement(\"SELECT * FROM r2dbc_example\")\n            .execute().flatMap(it -> it.map((row, metadata) -> row.get(\"first_name\"))))\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n\n    @Test\n    void savepointStartsTransaction() {\n\n        createTable(connection);\n\n        connection.createStatement(\"INSERT INTO r2dbc_example VALUES(1, 'Jesse', 'Pinkman')\")\n            .execute().flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNext(1L)\n            .verifyComplete();\n\n        connection.createSavepoint(\"s1\")\n            .thenMany(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(0, 'Walter', 'White')\").execute()\n                .<Object>flatMap(Result::getRowsUpdated))\n            .concatWith(Mono.fromSupplier(() -> connection.isAutoCommit()))\n            .concatWith(connection.rollbackTransaction())\n            .as(StepVerifier::create)\n            .expectNext(1L).as(\"Affected Rows Count from first INSERT\")\n            .expectNext(false).as(\"Auto-commit disabled by createSavepoint\")\n            .verifyComplete();\n\n        connection.createStatement(\"INSERT INTO r2dbc_example VALUES(0, 'Walter', 'White')\").execute()\n            .<Object>flatMap(Result::getRowsUpdated)\n            .concatWith(connection.rollbackTransaction())\n            .as(StepVerifier::create)\n            .expectNext(1L).as(\"Affected Rows Count from second INSERT\")\n            .verifyComplete();\n\n        connectionFactory.create().flatMapMany(c -> c.createStatement(\"SELECT * FROM r2dbc_example\")\n            .execute().flatMap(it -> it.map((row, metadata) -> row.get(\"first_name\"))))\n            .as(StepVerifier::create)\n            .expectNext(\"Jesse\")\n            .verifyComplete();\n    }\n\n    @Test\n    void commitTransaction() {\n\n        createTable(connection);\n\n        connection.beginTransaction()\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@P1, @P2, @P3)\")\n            .bind(0, 0).bind(1, \"Walter\").bind(2, \"White\").add()\n            .bind(0, 1).bind(1, \"Jesse\").bind(2, \"Pinkman\").execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNextCount(2)\n            .verifyComplete();\n\n        connection.commitTransaction()\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"SELECT COUNT(*) FROM r2dbc_example\")\n            .execute())\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0, Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(2)\n            .verifyComplete();\n    }\n\n    @Test\n    void rollbackTransaction() {\n\n        createTable(connection);\n\n        connection.beginTransaction()\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"INSERT INTO r2dbc_example VALUES(@P1, @P2, @P3)\")\n            .bind(0, 0).bind(1, \"Walter\").bind(2, \"White\").add()\n            .bind(0, 1).bind(1, \"Jesse\").bind(2, \"Pinkman\").execute())\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNextCount(2)\n            .verifyComplete();\n\n        connection.rollbackTransaction()\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        Flux.from(connection.createStatement(\"SELECT COUNT(*) FROM r2dbc_example\")\n            .execute())\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0, Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(0)\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldBeginExtendedTransaction() {\n\n        getTransactionCount()\n            .as(StepVerifier::create)\n            .expectNext(0)\n            .verifyComplete();\n\n        connection.beginTransaction(MssqlTransactionDefinition.from(IsolationLevel.READ_UNCOMMITTED)\n            .name(\"foo-1\").mark(\"bar\")\n            .lockTimeout(Duration.ofMinutes(1))).as(StepVerifier::create).verifyComplete();\n\n        getTransactionCount()\n            .as(StepVerifier::create)\n            .expectNext(1)\n            .verifyComplete();\n\n        connection.createStatement(\"SELECT @@LOCK_TIMEOUT\").execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0, Long.class)))\n            .as(StepVerifier::create)\n            .expectNext(TimeUnit.MINUTES.toMillis(1))\n            .verifyComplete();\n\n        getIsolationLevel()\n            .as(StepVerifier::create)\n            .expectNext(MssqlIsolationLevel.READ_UNCOMMITTED)\n            .verifyComplete();\n\n        connection.rollbackTransaction().as(StepVerifier::create).verifyComplete();\n\n        getTransactionCount()\n            .as(StepVerifier::create)\n            .expectNext(0)\n            .verifyComplete();\n    }\n\n    private Flux<Integer> getTransactionCount() {\n        return connection.createStatement(\"SELECT @@TRANCOUNT\").execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0, Integer.class)));\n    }\n\n    @Test\n    void shouldRestoreAutoCommitAfterExtendedTx() {\n\n        getImplicitTransactions().as(StepVerifier::create).expectNext(false).verifyComplete();\n\n        connection.beginTransaction(IsolationLevel.READ_UNCOMMITTED).as(StepVerifier::create).verifyComplete();\n        assertThat(connection.isAutoCommit()).isFalse();\n        getImplicitTransactions().as(StepVerifier::create).expectNext(false).verifyComplete();\n\n        connection.rollbackTransaction().as(StepVerifier::create).verifyComplete();\n        assertThat(connection.isAutoCommit()).isTrue();\n\n        // huh?\n        getImplicitTransactions().as(StepVerifier::create).expectNext(false).verifyComplete();\n\n        connection.setAutoCommit(true).as(StepVerifier::create).verifyComplete();\n        assertThat(connection.isAutoCommit()).isTrue();\n        getImplicitTransactions().as(StepVerifier::create).expectNext(false).verifyComplete();\n\n        assertAutoCommit();\n    }\n\n    private void assertAutoCommit() {\n        createTable(connection);\n\n        connection.createStatement(\"INSERT INTO r2dbc_example VALUES(0, 'Walter', 'White')\")\n            .execute()\n            .flatMap(Result::getRowsUpdated)\n            .as(StepVerifier::create)\n            .expectNextCount(1)\n            .verifyComplete();\n\n        getTransactionCount()\n            .as(StepVerifier::create)\n            .expectNext(0)\n            .verifyComplete();\n\n        MssqlConnection connection = connectionFactory.create().block();\n\n        connection.createStatement(\"SELECT COUNT(*) FROM r2dbc_example\")\n            .execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0, Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(1)\n            .verifyComplete();\n\n        connection.close().as(StepVerifier::create).verifyComplete();\n    }\n\n    @Test\n    void shouldResetIsolationLevelAfterTransaction() {\n\n        getIsolationLevel()\n            .as(StepVerifier::create)\n            .expectNext(MssqlIsolationLevel.READ_COMMITTED)\n            .verifyComplete();\n\n        connection.beginTransaction(IsolationLevel.READ_UNCOMMITTED).as(StepVerifier::create).verifyComplete();\n\n        connection.rollbackTransaction().as(StepVerifier::create).verifyComplete();\n\n        getIsolationLevel()\n            .as(StepVerifier::create)\n            .expectNext(MssqlIsolationLevel.READ_COMMITTED)\n            .verifyComplete();\n\n        assertThat(connection.getTransactionIsolationLevel()).isEqualTo(MssqlIsolationLevel.READ_COMMITTED);\n    }\n\n    @Test\n    void shouldResetLockTimeoutAfterTransaction() {\n\n        connection.createStatement(\"SELECT @@LOCK_TIMEOUT\").execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0, Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(-1)\n            .verifyComplete();\n\n        connection.beginTransaction(MssqlTransactionDefinition.named(\"foo\").lockTimeout(Duration.ofMinutes(60))).as(StepVerifier::create).verifyComplete();\n        connection.rollbackTransaction().as(StepVerifier::create).verifyComplete();\n\n        connection.createStatement(\"SELECT @@LOCK_TIMEOUT\").execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0, Integer.class)))\n            .as(StepVerifier::create)\n            .expectNext(-1)\n            .verifyComplete();\n    }\n\n    Mono<IsolationLevel> getIsolationLevel() {\n\n        return connection.createStatement(\"SELECT CASE transaction_isolation_level \\n\" +\n            \"WHEN 0 THEN 'UNSPECIFIED' \\n\" +\n            \"WHEN 1 THEN 'READ_UNCOMMITTED' \\n\" +\n            \"WHEN 2 THEN 'READ_COMMITTED' \\n\" +\n            \"WHEN 3 THEN 'REPEATABLE_READ' \\n\" +\n            \"WHEN 4 THEN 'SERIALIZABLE' \\n\" +\n            \"WHEN 5 THEN 'SNAPSHOT' END AS TRANSACTION_ISOLATION_LEVEL \\n\" +\n            \"FROM sys.dm_exec_sessions \\n\" +\n            \"where session_id = @@SPID\").execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> row.get(0, String.class)))\n            .map(it -> {\n\n                switch (it) {\n                    case \"READ_UNCOMMITTED\":\n                        return MssqlIsolationLevel.READ_UNCOMMITTED;\n                    case \"READ_COMMITTED\":\n                        return MssqlIsolationLevel.READ_COMMITTED;\n                    case \"SERIALIZABLE\":\n                        return MssqlIsolationLevel.SERIALIZABLE;\n                    case \"REPEATABLE_READ\":\n                        return MssqlIsolationLevel.REPEATABLE_READ;\n                    case \"SNAPSHOT\":\n                        return MssqlIsolationLevel.SNAPSHOT;\n                }\n\n                return MssqlIsolationLevel.UNSPECIFIED;\n            }).single();\n    }\n\n    Mono<Boolean> getImplicitTransactions() {\n\n        return connection.createStatement(\"SELECT @@OPTIONS AS IMPLICIT_TRANSACTIONS;\").execute()\n            .flatMap(it -> it.map((row, rowMetadata) -> (row.get(0, Integer.class) & 2) == 2))\n            .single();\n    }\n\n    private void createTable(MssqlConnection connection) {\n\n        connection.createStatement(\"DROP TABLE r2dbc_example\").execute()\n            .flatMap(MssqlResult::getRowsUpdated)\n            .onErrorResume(e -> Mono.empty())\n            .thenMany(connection.createStatement(\"CREATE TABLE r2dbc_example (\" +\n                \"id int PRIMARY KEY, \" +\n                \"first_name varchar(255), \" +\n                \"last_name varchar(255))\")\n                .execute().flatMap(MssqlResult::getRowsUpdated).then())\n            .as(StepVerifier::create)\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/XmlIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql;\n\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\n/**\n * Integration tests using XML as return type.\n *\n * @author Mark Paluch\n */\nclass XmlIntegrationTests extends IntegrationTestSupport {\n\n    @Test\n    void shouldExecuteForXmlSimple() {\n\n        connection.createStatement(\"select 1 as a for xml path\").execute()\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0)))\n            .as(StepVerifier::create)\n            .expectNext(\"<row><a>1</a></row>\")\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldExecuteForXmlParametrized() {\n\n        connection.createStatement(\"select 1 as a where @P0 = @P0 for xml path\").bind(\"@P0\", true).fetchSize(0).execute()\n            .flatMap(result -> result.map((row, rowMetadata) -> row.get(0)))\n            .as(StepVerifier::create)\n            .expectNext(\"<row><a>1</a></row>\")\n            .verifyComplete();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/client/ConnectionStateUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelPipeline;\nimport io.r2dbc.mssql.client.ssl.SslState;\nimport io.r2dbc.mssql.message.token.Prelogin;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport reactor.netty.Connection;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n/**\n * Unit tests for {@link ConnectionState}.\n *\n * @author Mark Paluch\n */\nclass ConnectionStateUnitTests {\n\n    Connection nettyConnection = mock(Connection.class);\n\n    Channel channel = mock(Channel.class);\n\n    ChannelPipeline pipeline = mock(ChannelPipeline.class);\n\n    @BeforeEach\n    void setUp() {\n\n        when(this.nettyConnection.channel()).thenReturn(this.channel);\n        when(this.channel.pipeline()).thenReturn(this.pipeline);\n    }\n\n    @Test\n    void shouldInitiateSslHandshakeForLogin() {\n\n        Prelogin prelogin = Prelogin.builder().build();\n\n        ConnectionState.PRELOGIN.next(prelogin, this.nettyConnection);\n\n        verify(this.pipeline).fireUserEventTriggered(SslState.LOGIN_ONLY);\n    }\n\n    @Test\n    void shouldInitiateSslHandshakeForConnection() {\n\n        Prelogin prelogin = Prelogin.builder().withEncryptionEnabled().build();\n\n        ConnectionState.PRELOGIN.next(prelogin, this.nettyConnection);\n\n        verify(this.pipeline).fireUserEventTriggered(SslState.CONNECTION);\n    }\n\n    @Test\n    void shouldAdvanceToSslHandshakeState() {\n\n        Prelogin prelogin = Prelogin.builder().withEncryptionEnabled().build();\n\n        ConnectionState next = ConnectionState.PRELOGIN.next(prelogin, this.nettyConnection);\n\n        assertThat(next).isEqualTo(ConnectionState.PRELOGIN_SSL_NEGOTIATION);\n    }\n\n    @Test\n    void shouldAdvancePreloginState() {\n\n        Prelogin prelogin = Prelogin.builder().withEncryptionNotSupported().build();\n\n        ConnectionState next = ConnectionState.PRELOGIN.next(prelogin, this.nettyConnection);\n\n        assertThat(next).isEqualTo(ConnectionState.PRELOGIN);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/client/ReactorNettyClientIntegrationTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.r2dbc.mssql.MssqlConnection;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.token.SqlBatch;\nimport io.r2dbc.mssql.util.IntegrationTestSupport;\nimport io.r2dbc.spi.R2dbcNonTransientResourceException;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.util.ReflectionUtils;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.publisher.Sinks;\nimport reactor.netty.Connection;\nimport reactor.test.StepVerifier;\n\nimport java.lang.reflect.Field;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\n/**\n * Integration tests for {@link ReactorNettyClient}.\n */\nclass ReactorNettyClientIntegrationTests extends IntegrationTestSupport {\n\n    static final Field CONNECTION = ReflectionUtils.findField(ReactorNettyClient.class, \"connection\");\n\n    static final Field CLIENT = ReflectionUtils.findField(MssqlConnection.class, \"client\");\n\n    static {\n        ReflectionUtils.makeAccessible(CONNECTION);\n        ReflectionUtils.makeAccessible(CLIENT);\n    }\n\n    private io.r2dbc.spi.Connection r2dbcConnection;\n    private ReactorNettyClient client;\n\n    private Connection connection;\n\n    @BeforeEach\n    void setUp() {\n        this.r2dbcConnection = connectionFactory.create().block();\n        this.client = (ReactorNettyClient) ReflectionUtils.getField(CLIENT, this.r2dbcConnection);\n        this.connection = (Connection) ReflectionUtils.getField(CONNECTION, this.client);\n    }\n\n    @AfterEach\n    void tearDown() {\n        Mono.from(r2dbcConnection.close()).subscribe();\n    }\n\n    @Test\n    void disconnectedShouldRejectExchange() {\n\n        Connection connection = (Connection) ReflectionUtils.getField(CONNECTION, this.client);\n        connection.channel().close().awaitUninterruptibly();\n\n        this.client.close()\n            .thenMany(this.client.exchange(Mono.empty(), message -> true))\n            .as(StepVerifier::create)\n            .verifyErrorSatisfies(t -> assertThat(t).isInstanceOf(R2dbcNonTransientResourceException.class).hasMessage(\"Cannot exchange messages because the connection is closed\"));\n    }\n\n    @Test\n    void shouldCancelExchangeOnCloseFirstMessage() throws Exception {\n\n        Sinks.Many<ClientMessage> messages = Sinks.many().unicast().onBackpressureBuffer();\n        Flux<Message> query = this.client.exchange(messages.asFlux(), message -> true);\n        CompletableFuture<List<Message>> future = query.collectList().toFuture();\n\n        this.connection.channel().eventLoop().execute(() -> {\n\n            this.connection.channel().close();\n\n            SqlBatch batch = SqlBatch.create(0, this.client.getTransactionDescriptor(), \"SELECT value FROM test\");\n            messages.tryEmitNext(batch);\n        });\n\n        try {\n            future.get(9995, TimeUnit.SECONDS);\n            fail(\"Expected MssqlConnectionClosedException\");\n        } catch (ExecutionException e) {\n            assertThat(e).hasCauseInstanceOf(ReactorNettyClient.MssqlConnectionClosedException.class).hasMessageContaining(\"closed\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/client/StreamDecoderUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.PacketIdProvider;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.token.ColumnMetadataToken;\nimport io.r2dbc.mssql.message.token.DoneToken;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link StreamDecoder}.\n *\n * @author Mark Paluch\n */\nclass StreamDecoderUnitTests {\n\n    static final Client CLIENT = TestClient.NO_OP;\n\n    @Test\n    void shouldDecodeFullPacket() {\n\n        StreamDecoder decoder = new StreamDecoder();\n\n        Header header = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.of(Status.StatusBit.EOM)), Header.LENGTH + DoneToken.LENGTH, PacketIdProvider.just(1));\n        DoneToken token = DoneToken.create(2);\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n\n        header.encode(buffer);\n        token.encode(buffer);\n\n        List<Message> messageStream = decoder.decode(buffer, ConnectionState.POST_LOGIN.decoder(CLIENT));\n\n        assertThat(messageStream).containsOnly(token);\n\n        assertThat(decoder.getDecoderState()).isNull();\n        assertThat(buffer.refCnt()).isEqualTo(1);\n        buffer.release();\n    }\n\n    @Test\n    void shouldDecodePartialPacket() {\n\n        StreamDecoder decoder = new StreamDecoder();\n\n        DoneToken token = DoneToken.create(2);\n\n        // Just the header type.\n        ByteBuf partial = Unpooled.wrappedBuffer(new byte[]{4});\n\n        List<Message> noMessage = decoder.decode(partial, ConnectionState.POST_LOGIN.decoder(CLIENT));\n        assertThat(noMessage).isEmpty();\n\n        StreamDecoder.DecoderState state = decoder.getDecoderState();\n        assertThat(state).isNotNull();\n        assertThat(state.header).isNull();\n        assertThat(state.remainder.readableBytes()).isEqualTo(1);\n        assertThat(state.aggregatedBody.readableBytes()).isEqualTo(0);\n        assertThat(partial.refCnt()).isEqualTo(1);\n\n        ByteBuf nextPacket = TestByteBufAllocator.TEST.buffer();\n        nextPacket.writeBytes(new byte[]{1, 0, 0x15, 0, 0, 0, 0});\n        token.encode(nextPacket);\n\n        List<Message> completeMessage = decoder.decode(nextPacket, ConnectionState.POST_LOGIN.decoder(CLIENT));\n\n        assertThat(completeMessage).containsOnly(token);\n        assertThat(decoder.getDecoderState()).isNull();\n        assertThat(partial.refCnt()).isEqualTo(1);\n        assertThat(nextPacket.refCnt()).isEqualTo(1);\n\n        partial.release();\n        nextPacket.release();\n    }\n\n    @Test\n    void shouldDecodePacketWithNextRemainder() {\n\n        StreamDecoder decoder = new StreamDecoder();\n\n        Header header = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.of(Status.StatusBit.EOM)), Header.LENGTH + DoneToken.LENGTH, PacketIdProvider.just(1));\n        DoneToken token = DoneToken.create(2);\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n\n        header.encode(buffer);\n        token.encode(buffer);\n        buffer.writeByte(4);\n\n        List<Message> completeMessage = decoder.decode(buffer, ConnectionState.POST_LOGIN.decoder(CLIENT));\n\n        assertThat(completeMessage).containsOnly(token);\n\n        StreamDecoder.DecoderState state = decoder.getDecoderState();\n        assertThat(state).isNotNull();\n        assertThat(state.header).isNull();\n        assertThat(state.remainder.readableBytes()).isEqualTo(1);\n        assertThat(state.aggregatedBody.readableBytes()).isEqualTo(0);\n\n        state.release();\n        assertThat(buffer.refCnt()).isEqualTo(1);\n        buffer.release();\n    }\n\n    @Test\n    void shouldDecodePacketWithNextRemainderAfterNextHeader() {\n\n        StreamDecoder decoder = new StreamDecoder();\n\n        Header header = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.of(Status.StatusBit.EOM)), Header.LENGTH + DoneToken.LENGTH, PacketIdProvider.just(1));\n\n        Header header2 = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.of(Status.StatusBit.EOM)), Header.LENGTH + DoneToken.LENGTH, PacketIdProvider.just(2));\n        DoneToken token = DoneToken.create(2);\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n\n        header.encode(buffer);\n        token.encode(buffer);\n        header2.encode(buffer);\n        buffer.writeBytes(new byte[]{4, 2, 1});\n\n        List<Message> completeMessage = decoder.decode(buffer, ConnectionState.POST_LOGIN.decoder(CLIENT));\n\n        assertThat(completeMessage).containsOnly(token);\n\n        StreamDecoder.DecoderState state = decoder.getDecoderState();\n        assertThat(state).isNotNull();\n        assertThat(state.header).isNotNull().isEqualTo(header2);\n        assertThat(state.remainder.readableBytes()).isEqualTo(3);\n        assertThat(state.aggregatedBody.readableBytes()).isEqualTo(0);\n\n        buffer.release();\n    }\n\n    @Test\n    void shouldDecodeTwoPacketsFragmented() {\n\n        StreamDecoder decoder = new StreamDecoder();\n\n        Header header = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.of(Status.StatusBit.EOM)), Header.LENGTH + DoneToken.LENGTH, PacketIdProvider.just(1));\n\n        Header header2 = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.of(Status.StatusBit.EOM)), Header.LENGTH + DoneToken.LENGTH, PacketIdProvider.just(2));\n        DoneToken token = DoneToken.create(2);\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n        ByteBuf nextBuffer = TestByteBufAllocator.TEST.buffer();\n        token.encode(nextBuffer);\n\n        header.encode(buffer);\n        token.encode(buffer);\n        header2.encode(buffer);\n        buffer.writeBytes(new byte[]{nextBuffer.readByte(), nextBuffer.readByte(), nextBuffer.readByte()});\n\n        List<Message> firstMessage = decoder.decode(buffer, ConnectionState.POST_LOGIN.decoder(CLIENT));\n\n        assertThat(firstMessage).containsOnly(token);\n\n        StreamDecoder.DecoderState state = decoder.getDecoderState();\n        assertThat(state).isNotNull();\n        assertThat(state.header).isNotNull().isEqualTo(header2);\n        assertThat(state.remainder.readableBytes()).isEqualTo(3);\n        assertThat(state.aggregatedBody.readableBytes()).isEqualTo(0);\n        assertThat(buffer.refCnt()).isEqualTo(1);\n\n        List<Message> secondMessage = decoder.decode(nextBuffer, ConnectionState.POST_LOGIN.decoder(CLIENT));\n        assertThat(secondMessage).containsOnly(token);\n\n        assertThat(decoder.getDecoderState()).isNull();\n\n        assertThat(buffer.refCnt()).isEqualTo(1);\n        assertThat(nextBuffer.refCnt()).isEqualTo(1);\n\n        buffer.release();\n        nextBuffer.release();\n    }\n\n    @Test\n    void shouldDecodeChunkedPackets() {\n\n        StreamDecoder decoder = new StreamDecoder();\n\n        Header firstHeader = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.empty()), Header.LENGTH + 3, PacketIdProvider.just(1));\n\n        Header lastHeader = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.of(Status.StatusBit.EOM)), Header.LENGTH + 10, PacketIdProvider.just(2));\n        DoneToken token = DoneToken.create(2);\n\n        ByteBuf firstChunk = TestByteBufAllocator.TEST.buffer();\n        ByteBuf lastChunk = TestByteBufAllocator.TEST.buffer();\n        ByteBuf fullData = TestByteBufAllocator.TEST.buffer();\n        token.encode(fullData);\n\n        firstHeader.encode(firstChunk);\n        firstChunk.writeBytes(new byte[]{fullData.readByte(), fullData.readByte(), fullData.readByte()});\n\n        lastHeader.encode(lastChunk);\n        lastChunk.writeBytes(fullData);\n\n        List<Message> firstAttempt = decoder.decode(firstChunk, ConnectionState.POST_LOGIN.decoder(CLIENT));\n        assertThat(firstAttempt).isEmpty();\n\n\n        StreamDecoder.DecoderState state = decoder.getDecoderState();\n        assertThat(state).isNotNull();\n        assertThat(state.header).isNull(); // header completed\n        assertThat(state.remainder.readableBytes()).isEqualTo(0);\n        assertThat(state.aggregatedBody.readableBytes()).isEqualTo(3);\n        assertThat(firstChunk.refCnt()).isEqualTo(1);\n\n        List<Message> nextAttempt = decoder.decode(lastChunk, ConnectionState.POST_LOGIN.decoder(CLIENT));\n        assertThat(nextAttempt).containsOnly(token);\n\n        assertThat(decoder.getDecoderState()).isNull();\n\n        assertThat(firstChunk.refCnt()).isEqualTo(1);\n        assertThat(lastChunk.refCnt()).isEqualTo(1);\n\n        firstChunk.release();\n        lastChunk.release();\n    }\n\n    @Test\n    void shouldDecodeManyChunks() {\n\n        StreamDecoder decoder = new StreamDecoder();\n        MessageDecoder messageDecoder = ConnectionState.POST_LOGIN.decoder(CLIENT);\n\n        initializeColumMetadata(decoder, messageDecoder);\n\n        List<ByteBuf> chunks = createChunks();\n\n        // expect incomplete chunks to be empty\n        for (int i = 0; i < chunks.size() - 1; i++) {\n            assertThat(decoder.decode(chunks.get(i), messageDecoder)).isEmpty();\n        }\n\n        // Last chunk emits the data\n        assertThat(decoder.decode(chunks.get(chunks.size() - 1), messageDecoder)).hasSize(1);\n\n        StreamDecoder.DecoderState state = decoder.getDecoderState();\n        assertThat(state).isNull();\n        assertThat(decoder.getDecoderState()).isNull();\n    }\n\n    private List<ByteBuf> createChunks() {\n\n        ByteBuf row = HexUtils.decodeToByteBuf(\"D1010C00700061006C007500630068\" +\n            \"0004006D61726B080000000020A10700\" +\n            \"10F17B0DC7C7E5C54098C7A12F7E6867\" +\n            \"2408FED478E94628C6400437423146\");\n\n        List<ByteBuf> chunks = new ArrayList<>();\n\n        while (row.isReadable()) {\n\n            int bytesToRead = row.readableBytes();\n\n            Status status = Status.empty();\n\n            if (bytesToRead > 10) {\n                bytesToRead = 10;\n            } else {\n                status = Status.of(Status.StatusBit.EOM);\n            }\n\n            Header header = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, status), Header.LENGTH + bytesToRead, PacketIdProvider.just(1));\n            ByteBuf chunk = TestByteBufAllocator.TEST.buffer();\n            header.encode(chunk);\n            chunk.writeBytes(row, bytesToRead);\n\n            chunks.add(chunk);\n        }\n        return chunks;\n    }\n\n    private static void initializeColumMetadata(StreamDecoder decoder, MessageDecoder messageDecoder) {\n\n        // Required initialization. ColMetadata does not support yet chunking.\n        ByteBuf colmetadata = HexUtils.decodeToByteBuf(\"8107000000000000\" +\n            \"000800300B65006D0070006C006F0079\" +\n            \"00650065005F00690064000000000008\" +\n            \"00E764000904D00034096C0061007300\" +\n            \"74005F006E0061006D00650000000000\" +\n            \"0900A732000904D000340A6600690072\" +\n            \"00730074005F006E0061006D00650000\" +\n            \"00000009006E0806730061006C006100\" +\n            \"7200790000000000090024100366006F\" +\n            \"006F000000000009006D080366006C00\" +\n            \"74000000000009006D04036200610072\" +\n            \"00\");\n\n        Header colHeader = Header.create(HeaderOptions.create(Type.TABULAR_RESULT, Status.of(Status.StatusBit.EOM)), Header.LENGTH + colmetadata.readableBytes(), PacketIdProvider.just(1));\n\n        ByteBuf initialize = TestByteBufAllocator.TEST.heapBuffer();\n        colHeader.encode(initialize);\n        initialize.writeBytes(colmetadata);\n\n        assertThat(decoder.decode(initialize, messageDecoder).get(0)).isInstanceOf(ColumnMetadataToken.class);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/client/TdsEncoderUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.PacketIdProvider;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.ContextualTdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsPacket;\nimport io.r2dbc.mssql.message.tds.TdsPackets;\nimport org.assertj.core.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.message.header.Status.StatusBit;\nimport static io.r2dbc.mssql.message.header.Status.empty;\nimport static io.r2dbc.mssql.message.header.Status.of;\nimport static io.r2dbc.mssql.util.EmbeddedChannelAssert.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Unit tests for {@link TdsEncoder}.\n *\n * @author Mark Paluch\n */\nclass TdsEncoderUnitTests {\n\n    @Test\n    void shouldPassThruByteBuffers() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42)));\n\n        channel.writeOutbound(Unpooled.wrappedBuffer(\"foobar\".getBytes()));\n\n        assertThat(channel).outbound().hasByteBufMessage().contains(\"foobar\");\n    }\n\n    @Test\n    void shouldPrependByteBuffersWithHeader() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42)));\n\n        channel.writeOutbound(HeaderOptions.create(Type.PRE_LOGIN, empty()));\n        channel.writeOutbound(Unpooled.wrappedBuffer(\"foobar\".getBytes()));\n\n        assertThat(channel).outbound().hasByteBufMessage().isEmpty();\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.EOM, 0x0e, \"foobar\");\n        });\n    }\n\n    @Test\n    void shouldResetHeaderStatus() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42)));\n\n        channel.writeOutbound(HeaderOptions.create(Type.PRE_LOGIN, empty()));\n        channel.writeOutbound(TdsEncoder.ResetHeader.INSTANCE);\n        channel.writeOutbound(Unpooled.wrappedBuffer(\"foobar\".getBytes()));\n\n        assertThat(channel).outbound().hasByteBufMessage().isEmpty();\n        assertThat(channel).outbound().hasByteBufMessage().isEmpty();\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> buffer.writeBytes(\"foobar\".getBytes()));\n    }\n\n    @Test\n    void shouldEncodeTdsPacket() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42)));\n\n        TdsPacket packet = TdsPackets.create(new Header(Type.PRE_LOGIN, of(StatusBit.EOM), 10, 0, 0, 0),\n            Unpooled.wrappedBuffer(\"ab\".getBytes()));\n\n        channel.writeOutbound(packet);\n\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.EOM, 0x0a, \"ab\");\n        });\n    }\n\n    @Test\n    void shouldEncodeContextualTdsFragment() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42)));\n\n        ContextualTdsFragment fragment = TdsPackets.create(HeaderOptions.create(Type.PRE_LOGIN, empty()),\n            Unpooled.wrappedBuffer(\"ab\".getBytes()));\n\n        channel.writeOutbound(fragment);\n\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n\n            encodeExpectation(buffer, StatusBit.EOM, 0x0a, \"ab\");\n        });\n    }\n\n    @Test\n    void shouldEncodeAndSplitContextualTdsFragment() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42), 12));\n\n        ContextualTdsFragment fragment = TdsPackets.create(HeaderOptions.create(Type.PRE_LOGIN, empty()),\n            Unpooled.wrappedBuffer(\"foobar\".getBytes()));\n\n        channel.writeOutbound(fragment);\n\n        // Chunk 1\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0c, \"foob\");\n        });\n\n        // Chunk 2\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.EOM, 0x0a, \"ar\");\n        });\n    }\n\n    @Test\n    void shouldEncodeTdsFragment() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        TdsEncoder tdsEncoder = new TdsEncoder(PacketIdProvider.just(42));\n        tdsEncoder.setPacketSize(10);\n        channel.pipeline().addFirst(tdsEncoder);\n\n        TdsFragment fragment = TdsPackets.create(Unpooled.wrappedBuffer(\"ab\".getBytes()));\n\n        channel.writeOutbound(HeaderOptions.create(Type.PRE_LOGIN, empty()));\n        channel.writeOutbound(fragment);\n\n        assertThat(channel).outbound().hasByteBufMessage().isEmpty();\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0a, \"ab\");\n        });\n    }\n\n    @Test\n    void failsEncodingTdsFragmentWithoutHeader() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42)));\n\n        TdsFragment fragment = TdsPackets.create(Unpooled.wrappedBuffer(\"ab\".getBytes()));\n\n        assertThatThrownBy(() -> channel.writeOutbound(fragment)).isInstanceOf(IllegalStateException.class)\n            .hasMessage(\"HeaderOptions must not be null!\");\n    }\n\n    @Test\n    void shouldEncodeMessageSequence() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n\n        TdsEncoder encoder = new TdsEncoder(PacketIdProvider.just(42));\n        encoder.setPacketSize(10);\n        channel.pipeline().addFirst(encoder);\n\n        TdsFragment first = TdsPackets.first(HeaderOptions.create(Type.PRE_LOGIN, empty()),\n            Unpooled.wrappedBuffer(\"ab\".getBytes()));\n\n        TdsFragment last = TdsPackets.last(Unpooled.wrappedBuffer(\"ab\".getBytes()));\n\n        channel.writeOutbound(first, Unpooled.wrappedBuffer(\"fo\".getBytes()), last,\n            Unpooled.wrappedBuffer(\"fo\".getBytes()));\n\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0a, \"ab\");\n        });\n\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.EOM, 0x0a, \"fo\");\n        });\n\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.EOM, 0x0a, \"ab\");\n        });\n\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            buffer.writeBytes(\"fo\".getBytes());\n        });\n    }\n\n    @Test\n    void shouldChunkMessagesLargeSmall() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42), 12));\n\n        TdsFragment first = TdsPackets.first(HeaderOptions.create(Type.PRE_LOGIN, empty()),\n            Unpooled.wrappedBuffer(\"abcde\".getBytes()));\n\n        TdsFragment last = TdsPackets.last(Unpooled.wrappedBuffer(\"f\".getBytes()));\n\n        channel.writeOutbound(first, last);\n\n        // Chunk 1\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0c, \"abcd\");\n        });\n\n        // Chunk 2\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.EOM, 0x0a, \"ef\");\n        });\n    }\n\n    @Test\n    void shouldChunkMessagesLargeLargeSmall() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42), 12));\n\n        TdsFragment first = TdsPackets.first(HeaderOptions.create(Type.PRE_LOGIN, empty()),\n            Unpooled.wrappedBuffer(\"abcde\".getBytes()));\n\n        TdsFragment intermediate = TdsPackets.create(Unpooled.wrappedBuffer(\"fghijk\".getBytes()));\n\n        TdsFragment last = TdsPackets.last(Unpooled.wrappedBuffer(\"l\".getBytes()));\n\n        channel.writeOutbound(first, intermediate, last);\n\n        // Chunk 1\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0c, \"abcd\");\n        });\n\n        // Chunk 2\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0c, \"efgh\");\n        });\n\n        // Chunk 3\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.EOM, 0x0c, \"ijkl\");\n        });\n    }\n\n    @Test\n    void shouldChunkMessagesLargeLargeLarge() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addFirst(new TdsEncoder(PacketIdProvider.just(42), 12));\n\n        TdsFragment first = TdsPackets.first(HeaderOptions.create(Type.PRE_LOGIN, empty()),\n            Unpooled.wrappedBuffer(\"abcde\".getBytes()));\n\n        TdsFragment intermediate = TdsPackets.create(Unpooled.wrappedBuffer(\"fghijk\".getBytes()));\n\n        TdsFragment last = TdsPackets.last(Unpooled.wrappedBuffer(\"lmnop\".getBytes()));\n\n        channel.writeOutbound(first, intermediate, last);\n\n        // Chunk 1\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0c, \"abcd\");\n        });\n\n        // Chunk 2\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0c, \"efgh\");\n        });\n\n        // Chunk 3\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n\n            encodeExpectation(buffer, StatusBit.NORMAL, 0x0c, \"ijkl\");\n        });\n\n        // Chunk 4\n        assertThat(channel).outbound().hasByteBufMessage().isEncodedAs(buffer -> {\n\n            encodeExpectation(buffer, StatusBit.EOM, 0x0c, \"mnop\");\n        });\n    }\n\n    @Test\n    void shouldEstimateTdsPacketSize() {\n\n        TdsEncoder encoder = new TdsEncoder(PacketIdProvider.just(42), 12);\n\n        Assertions.assertThat(encoder.estimateChunkSize(1)).isEqualTo(9);\n        Assertions.assertThat(encoder.estimateChunkSize(4)).isEqualTo(12);\n        Assertions.assertThat(encoder.estimateChunkSize(5)).isEqualTo(12);\n    }\n\n    private static void encodeExpectation(ByteBuf buffer, StatusBit bit, int length, String content) {\n\n        buffer.writeByte(18); // Type\n        buffer.writeByte(bit.getBits()); // Status\n        buffer.writeShort(length); // Length\n        buffer.writeShort(0); // SPID\n        buffer.writeByte(42); // PacketID\n        buffer.writeByte(0); // Window\n        buffer.writeBytes(content.getBytes());\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/client/TestClient.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.Message;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.tds.Redirect;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.util.Assert;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.assertj.core.api.Assertions;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.publisher.Sinks;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * Test {@link Client} implementation.\n */\n@SuppressWarnings({\"unchecked\", \"rawtypes\"})\npublic final class TestClient implements Client {\n\n    public static final TestClient NO_OP = new TestClient(false, true, Flux.empty(), Optional.empty(), TransactionStatus.AUTO_COMMIT);\n\n\n    private final boolean expectClose;\n\n    private final boolean connected;\n\n    private boolean closed;\n\n    private final Sinks.Many<Message> requestProcessor = Sinks.many().multicast().onBackpressureBuffer();\n\n    private final Sinks.Many<Flux<Message>> responseProcessor = Sinks.many().multicast().onBackpressureBuffer(256, false);\n\n    private final TransactionStatus transactionStatus;\n\n    private final Optional<Redirect> redirect;\n\n    private TestClient(boolean expectClose, boolean connected, Flux<Window> windows, Optional<Redirect> redirect, TransactionStatus transactionStatus) {\n\n        this.expectClose = expectClose;\n        this.connected = connected;\n        this.redirect = redirect;\n        this.transactionStatus = transactionStatus;\n\n\n        Assert.requireNonNull(windows, \"Windows must not be null\")\n            .map(window -> window.exchanges)\n            .map(exchanges -> exchanges\n                .concatMap(exchange ->\n\n                    this.requestProcessor.asFlux().zipWith(exchange.requests)\n                        .handle((tuple, sink) -> {\n                            Message actual = tuple.getT1();\n                            Consumer<Message> expected = (Consumer) tuple.getT2();\n\n                            try {\n                                expected.accept(actual);\n                            } catch (Throwable t) {\n                                sink.error(t);\n                            }\n                        })\n                        .thenMany(exchange.responses)))\n            .subscribe(this.responseProcessor::tryEmitNext, this.responseProcessor::tryEmitError, this.responseProcessor::tryEmitComplete);\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    @Override\n    public Mono<Void> attention() {\n        return Mono.empty();\n    }\n\n    @Override\n    public Mono<Void> close() {\n        return this.expectClose ? Mono.fromRunnable(() -> {\n            this.closed = true;\n        }) : Mono.error(new AssertionError(\"close called unexpectedly\"));\n    }\n\n    public boolean isClosed() {\n        return this.closed;\n    }\n\n    public Flux<Message> exchange(Publisher<? extends ClientMessage> requests, Predicate<Message> takeUntil) {\n\n        Assert.requireNonNull(requests, \"requests must not be null\");\n\n        return this.responseProcessor.asFlux()\n            .doOnSubscribe(s ->\n                Flux.from(requests)\n                    .subscribe(this.requestProcessor::tryEmitNext, this.requestProcessor::tryEmitError))\n            .next()\n            .flatMapMany(Function.identity());\n    }\n\n    @Override\n    public ByteBufAllocator getByteBufAllocator() {\n        return TestByteBufAllocator.TEST;\n    }\n\n    @Override\n    public ConnectionContext getContext() {\n        return new ConnectionContext();\n    }\n\n    @Override\n    public Optional<Collation> getDatabaseCollation() {\n\n        // windows-1252\n        return Optional.of(Collation.from(13632521, 52));\n    }\n\n    @Override\n    public Optional<String> getDatabaseVersion() {\n        return Optional.of(\"1.2.3\");\n    }\n\n    @Override\n    public Optional<Redirect> getRedirect() {\n        return this.redirect;\n    }\n\n    @Override\n    public TransactionDescriptor getTransactionDescriptor() {\n        return TransactionDescriptor.empty();\n    }\n\n    @Override\n    public TransactionStatus getTransactionStatus() {\n        return this.transactionStatus;\n    }\n\n    @Override\n    public boolean isColumnEncryptionSupported() {\n        return true;\n    }\n\n    @Override\n    public boolean isConnected() {\n        return this.connected;\n    }\n\n    public static final class Builder {\n\n        private final List<Window.Builder<?>> windows = new ArrayList<>();\n\n        private boolean expectClose = false;\n\n        private boolean connected = true;\n\n        private Optional<Redirect> redirect = Optional.empty();\n\n        private TransactionStatus transactionStatus = TransactionStatus.AUTO_COMMIT;\n\n        private Builder() {\n        }\n\n        public TestClient build() {\n            return new TestClient(this.expectClose, this.connected, Flux.fromIterable(this.windows).map(Window.Builder::build), this.redirect, this.transactionStatus);\n        }\n\n        public Builder expectClose() {\n            this.expectClose = true;\n            return this;\n        }\n\n        public Builder withConnected(boolean connected) {\n            this.connected = connected;\n            return this;\n        }\n\n        public Builder withRedirect(Redirect redirect) {\n            this.redirect = Optional.of(redirect);\n            return this;\n        }\n\n        public Builder withTransactionStatus(TransactionStatus transactionStatus) {\n            this.transactionStatus = Assert.requireNonNull(transactionStatus, \"TransactionStatus must not be nuln\");\n            return this;\n        }\n\n        public Exchange.Builder<Builder> expectRequest(ClientMessage... requests) {\n            Assert.requireNonNull(requests, \"ClientMessage requests must not be null\");\n\n            Consumer[] consumers = Arrays.stream(requests).map(request -> {\n\n                Consumer<ClientMessage> messageConsumer = actual -> Assertions.assertThat(actual).isEqualTo(request);\n                return messageConsumer;\n            }).toArray(i -> new Consumer[i]);\n\n            return assertNextRequestWith(consumers);\n        }\n\n        public Exchange.Builder<Builder> assertNextRequestWith(Consumer<ClientMessage> request) {\n            Assert.requireNonNull(request, \"Client Consumer must not be null\");\n\n            return assertNextRequestWith(new Consumer[]{request});\n        }\n\n        public Exchange.Builder<Builder> assertNextRequestWith(Consumer<ClientMessage>... requests) {\n            Assert.requireNonNull(requests, \"Client Consumer must not be null\");\n\n            Window.Builder<Builder> window = new Window.Builder<>(this);\n            this.windows.add(window);\n\n            Exchange.Builder<Builder> exchange = new Exchange.Builder<>(this, requests);\n            window.exchanges.add(exchange);\n\n            return exchange;\n        }\n\n        public Window.Builder<Builder> window() {\n            Window.Builder<Builder> window = new Window.Builder<>(this);\n            this.windows.add(window);\n            return window;\n        }\n    }\n\n    private static final class Exchange {\n\n        private final Flux<Consumer<? extends Message>> requests;\n\n        private final Publisher<Message> responses;\n\n        private Exchange(Flux<Consumer<? extends Message>> requests, Publisher<Message> responses) {\n            this.requests = Assert.requireNonNull(requests, \"Requests must not be null\");\n            this.responses = Assert.requireNonNull(responses, \"Responses must not be null\");\n        }\n\n        public static final class Builder<T> {\n\n            private final T chain;\n\n            private final Flux<Consumer<? extends Message>> requests;\n\n            private Publisher<Message> responses;\n\n            private Builder(T chain, Consumer<? extends Message>... requests) {\n                this.chain = Assert.requireNonNull(chain, \"Request chain must not be null\");\n                this.requests = Flux.just(Assert.requireNonNull(requests, \"Requests must not be null\"));\n            }\n\n            public T thenRespond(Message... responses) {\n                Assert.requireNonNull(responses, \"Responses must not be null\");\n\n                return thenRespond(Flux.just(responses));\n            }\n\n            T thenRespond(Publisher<Message> responses) {\n                Assert.requireNonNull(responses, \"Responses must not be null\");\n\n                this.responses = responses;\n                return this.chain;\n            }\n\n            private Exchange build() {\n                return new Exchange(this.requests, this.responses);\n            }\n        }\n    }\n\n    private static final class Window {\n\n        private final Flux<Exchange> exchanges;\n\n        private Window(Flux<Exchange> exchanges) {\n            this.exchanges = Assert.requireNonNull(exchanges, \"Exchanges must not be null\");\n        }\n\n        public static final class Builder<T> {\n\n            private final T chain;\n\n            private final List<Exchange.Builder<?>> exchanges = new ArrayList<>();\n\n            private Builder(T chain) {\n                this.chain = Assert.requireNonNull(chain, \"Chain must not be null\");\n            }\n\n            public T done() {\n                return this.chain;\n            }\n\n            public Exchange.Builder<Builder<T>> expectRequest(ClientMessage request) {\n                return assertNextRequestWith(actual -> Assertions.assertThat(actual).isEqualTo(request));\n            }\n\n            public Exchange.Builder<Builder<T>> assertNextRequestWith(Consumer<ClientMessage> request) {\n\n                Assert.requireNonNull(request, \"Request must not be null\");\n\n                Exchange.Builder<Builder<T>> exchange = new Exchange.Builder<>(this, request);\n                this.exchanges.add(exchange);\n                return exchange;\n            }\n\n            private Window build() {\n                return new Window(Flux.fromIterable(this.exchanges).map(Exchange.Builder::build));\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/client/ssl/HostNamePredicateUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link HostNamePredicate}.\n *\n * @author Mark Paluch\n */\nclass HostNamePredicateUnitTests {\n\n    @Test\n    void shouldRejectAll() {\n        assertThat(HostNamePredicate.of(\"\")).rejects(\"bar\");\n    }\n\n    @Test\n    void shouldMatchSimpleName() {\n        assertThat(HostNamePredicate.of(\"foo\")).accepts(\"foo\").rejects(\"bar\");\n    }\n\n    @Test\n    void shouldMatchNameWithDots() {\n        assertThat(HostNamePredicate.of(\"foo.bar.baz\")).accepts(\"foo.bar.baz\").rejects(\"bar\").rejects(\"baz.bar.foo\");\n    }\n\n    @Test\n    void shouldMatchWildcard() {\n        assertThat(HostNamePredicate.of(\"foo.*.baz\")).accepts(\"foo.bar.baz\").accepts(\"foo..baz\").accepts(\"foo.bar.baz\").rejects(\"foo.baz\").rejects(\"baz.bar.foo\");\n    }\n\n    @Test\n    void shouldMatchWildcardInWord() {\n        assertThat(HostNamePredicate.of(\"foo.a*z.baz\")).accepts(\"foo.agz.baz\").accepts(\"foo.az.baz\").rejects(\"foo.bar.baz\");\n    }\n\n    @Test\n    void shouldMatchWildcardInWildcardCertificate() {\n        assertThat(HostNamePredicate.of(\"*.foo.bar.net\")).accepts(\"*.foo.bar.net\").rejects(\"*.foo.bar.baz\").rejects(\"*.subdomain.foo.bar.net\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/client/ssl/TdsSslHandlerUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslHandler;\nimport io.r2dbc.mssql.client.ConnectionContext;\nimport io.r2dbc.mssql.message.header.Header;\nimport io.r2dbc.mssql.message.header.PacketIdProvider;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\nimport java.util.stream.IntStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\n/**\n * Unit tests for {@link TdsSslHandler}.\n *\n * @author Mark Paluch\n */\nclass TdsSslHandlerUnitTests {\n\n    TdsSslHandler handler = new TdsSslHandler(PacketIdProvider.just(0), new SslConfiguration() {\n\n        @Override\n        public boolean isSslEnabled() {\n            return false;\n        }\n\n        @Override\n        public SslContext getSslContext() {\n            return null;\n        }\n    }, new ConnectionContext());\n\n    SslHandler sslHandler = mock(SslHandler.class);\n\n    ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);\n\n    ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);\n\n    @BeforeEach\n    void setUp() {\n        this.handler.setSslHandler(this.sslHandler);\n        this.handler.setState(SslState.CONNECTION);\n    }\n\n    @Test\n    void entireMessage() throws Exception {\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n\n        Header header = new Header(Type.PRE_LOGIN, Status.of(Status.StatusBit.EOM), 100, 1);\n        header.encode(buffer);\n\n        IntStream.range(0, 92).forEach(buffer::writeByte);\n\n        this.handler.channelRead(this.ctx, buffer);\n\n        verify(this.sslHandler).channelRead(any(), this.captor.capture());\n\n        ByteBuf value = this.captor.getValue();\n        assertThat(value.readableBytes()).isEqualTo(92);\n    }\n\n    @Test\n    void singleChunkedMessage() throws Exception {\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n        ByteBuf expected = TestByteBufAllocator.TEST.buffer();\n\n        Header header = new Header(Type.PRE_LOGIN, Status.of(Status.StatusBit.EOM), 100, 1);\n        header.encode(buffer);\n\n        IntStream.range(0, 92).forEach(buffer::writeByte);\n        IntStream.range(0, 92).forEach(expected::writeByte);\n\n        while (buffer.isReadable()) {\n            this.handler.channelRead(this.ctx, buffer.readRetainedSlice(Math.min(10, buffer.readableBytes())));\n        }\n        buffer.release();\n\n        verify(this.sslHandler).channelRead(any(), this.captor.capture());\n\n        ByteBuf value = this.captor.getValue();\n        assertThat(value.readableBytes()).isEqualTo(92);\n        assertThat(value).isEqualTo(expected);\n    }\n\n    @Test\n    void multipleChunkedMessages() throws Exception {\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n        ByteBuf expected = TestByteBufAllocator.TEST.buffer();\n\n        Header chunk1 = new Header(Type.PRE_LOGIN, Status.empty(), 100, 1);\n        chunk1.encode(buffer);\n        IntStream.range(0, 92).forEach(buffer::writeByte);\n\n        Header chunk2 = new Header(Type.PRE_LOGIN, Status.of(Status.StatusBit.EOM), 50, 1);\n        chunk2.encode(buffer);\n        IntStream.range(0, 42).forEach(buffer::writeByte);\n\n        IntStream.range(0, 92).forEach(expected::writeByte);\n        IntStream.range(0, 42).forEach(expected::writeByte);\n\n        this.handler.channelRead(this.ctx, buffer.readRetainedSlice(Math.min(80, buffer.readableBytes())));\n        this.handler.channelRead(this.ctx, buffer.readRetainedSlice(Math.min(50, buffer.readableBytes())));\n        this.handler.channelRead(this.ctx, buffer.readRetainedSlice(Math.min(20, buffer.readableBytes())));\n        buffer.release();\n\n        verify(this.sslHandler).channelRead(any(), this.captor.capture());\n\n        ByteBuf value = this.captor.getValue();\n        assertThat(value.readableBytes()).isEqualTo(92 + 42);\n    }\n\n    @Test\n    void channelInactiveReleasesChunk() throws Exception {\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n        Header header = new Header(Type.PRE_LOGIN, Status.of(Status.StatusBit.EOM), 100, 1);\n        header.encode(buffer);\n\n        IntStream.range(0, 20).forEach(buffer::writeByte);\n\n        this.handler.channelRead(this.ctx, buffer.readRetainedSlice(Math.min(10, buffer.readableBytes())));\n        this.handler.channelRead(this.ctx, buffer.readRetainedSlice(Math.min(10, buffer.readableBytes())));\n\n        buffer.release();\n        assertThat(buffer.refCnt()).isNotZero();\n\n        this.handler.channelInactive(this.ctx);\n        assertThat(buffer.refCnt()).isZero();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/client/ssl/X509CertificateUtilUnitTests.java",
    "content": "/*\n * Copyright 2020-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.client.ssl;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.security.GeneralSecurityException;\nimport java.security.cert.X509Certificate;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * Unit tests for {@link X509CertificateUtil}.\n *\n * @author Mark Paluch\n */\nclass X509CertificateUtilUnitTests {\n\n    @Test\n    void shouldCorrectlyExtractSan() throws GeneralSecurityException {\n\n        X509Certificate mockCert = mock(X509Certificate.class);\n        when(mockCert.getSubjectAlternativeNames()).thenReturn(Arrays.asList(Arrays.asList(1, \"invalid\"), Arrays.asList(2, \"valid\"), Collections.singletonList(3)));\n\n        List<String> names = X509CertificateUtil.getSubjectAlternativeNames(mockCert);\n\n        assertThat(names).containsOnly(\"valid\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/BigIntegerCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.math.BigInteger;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link BigIntegerCodec}.\n *\n * @author Mark Paluch\n */\nclass BigIntegerCodecUnitTests {\n\n    @Test\n    void shouldEncodeInteger() {\n\n        Encoded encoded = BigIntegerCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), new BigInteger(\"12345\"));\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"11 26 00 03 01 39 30\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"decimal(38,0)\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = BigIntegerCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"08 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"bigint\");\n    }\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation tinyint =\n            builder().withServerType(SqlServerType.TINYINT).build();\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).build();\n\n        TypeInformation numeric =\n            builder().withServerType(SqlServerType.NUMERIC).build();\n\n        assertThat(BigIntegerCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), BigInteger.class)).isTrue();\n        assertThat(BigIntegerCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varchar), BigInteger.class)).isFalse();\n        assertThat(BigIntegerCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), BigInteger.class)).isTrue();\n        assertThat(BigIntegerCodec.INSTANCE.canDecode(ColumnUtil.createColumn(numeric), BigInteger.class)).isTrue();\n    }\n\n    @Test\n    void shouldDecodeFromLong() {\n\n        TypeInformation type = createType(8, SqlServerType.BIGINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0100000100000000\");\n\n        assertThat(BigIntegerCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), BigInteger.class)).isEqualTo(BigInteger.valueOf(16777217));\n    }\n\n    @Test\n    void shouldDecodeFromNumeric() {\n\n        TypeInformation type = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.NUMERIC).withScale(0).withPrecision(5).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"05 01 39 30 00 00\");\n\n        assertThat(BigIntegerCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), BigInteger.class)).isEqualTo(BigInteger.valueOf(12345));\n    }\n\n    private TypeInformation createType(int length, SqlServerType serverType) {\n        return builder().withMaxLength(length).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(length).withServerType(serverType).build();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/BinaryCodecUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.ByteBuffer;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link BinaryCodec}.\n *\n * @author Mark Paluch\n */\nclass BinaryCodecUnitTests {\n\n    @Test\n    void shouldEncodeBinaryArray() {\n\n        Encoded encoded = BinaryCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), \"bar\".getBytes());\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"40 1F 03 00 62 61 72\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"varbinary(8000)\");\n    }\n\n    @Test\n    void shouldEncodeBinaryByteBuffer() {\n\n        Encoded encoded = BinaryCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), ByteBuffer.wrap(\"bar\".getBytes()));\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"40 1F 03 00 62 61 72\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"varbinary(8000)\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = BinaryCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"40 1F FF FF\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"varbinary(8000)\");\n    }\n\n    @Test\n    void shouldBeAbleToDecodeByteArray() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.BINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        assertThat(BinaryCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), byte[].class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeByteBuffer() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.BINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        assertThat(BinaryCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), ByteBuffer.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeVarbinaryToByteArray() {\n\n        TypeInformation varbinary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        assertThat(BinaryCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varbinary), byte[].class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeVarbinaryToByteBuffer() {\n\n        TypeInformation varbinary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        assertThat(BinaryCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varbinary), ByteBuffer.class)).isTrue();\n    }\n\n    @Test\n    void shouldDecodeBinaryToByteArray() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.BINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"0A 00 66 6F 6F 00 00 00 00 00 00 00\");\n        byte[] expected = new byte[10];\n        expected[0] = 'f';\n        expected[1] = 'o';\n        expected[2] = 'o';\n\n        assertThat(BinaryCodec.INSTANCE.decode(data, ColumnUtil.createColumn(binary), byte[].class)).isEqualTo(expected);\n    }\n\n    @Test\n    void shouldDecodeBinaryToByteBuffer() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.BINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"0A 00 66 6F 6F 00 00 00 00 00 00 00\");\n        ByteBuffer expected = ByteBuffer.allocate(10);\n        expected.put(\"foo\".getBytes()).put(new byte[7]).flip();\n\n        assertThat(BinaryCodec.INSTANCE.decode(data, ColumnUtil.createColumn(binary), ByteBuffer.class)).isEqualTo(expected);\n    }\n\n    @Test\n    void shouldDecodeBinaryNullToByteArray() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.BINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"FF FF\");\n\n        assertThat(BinaryCodec.INSTANCE.decode(data, ColumnUtil.createColumn(binary), byte[].class)).isNull();\n    }\n\n    @Test\n    void shouldDecodeBinaryNullToByteBuffer() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.BINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"FF FF\");\n\n        assertThat(BinaryCodec.INSTANCE.decode(data, ColumnUtil.createColumn(binary), ByteBuffer.class)).isNull();\n    }\n\n    @Test\n    void shouldDecodeVarBinaryToByteArray() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"03 00 62 61 72\");\n        byte[] expected = \"bar\".getBytes();\n\n        assertThat(BinaryCodec.INSTANCE.decode(data, ColumnUtil.createColumn(binary), byte[].class)).isEqualTo(expected);\n    }\n\n    @Test\n    void shouldDecodeVarBinaryToByteBuffer() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"03 00 62 61 72\");\n        ByteBuffer expected = ByteBuffer.allocate(3);\n        expected.put(\"bar\".getBytes()).flip();\n\n        assertThat(BinaryCodec.INSTANCE.decode(data, ColumnUtil.createColumn(binary), ByteBuffer.class)).isEqualTo(expected);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/BlobCodecUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.PooledByteBufAllocator;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.PlpLength;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport io.r2dbc.spi.Blob;\nimport org.junit.jupiter.api.Test;\nimport reactor.test.StepVerifier;\n\nimport java.nio.ByteBuffer;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link BlobCodec}.\n *\n * @author Mark Paluch\n */\nclass BlobCodecUnitTests {\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = BlobCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"40 1F FF FF\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"varbinary(8000)\");\n    }\n\n    @Test\n    void shouldBeAbleToEncodeNull() {\n\n        assertThat(BlobCodec.INSTANCE.canEncodeNull(Blob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeBinary() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.BINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        assertThat(BlobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), Blob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeVarBinary() {\n\n        TypeInformation varbinary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        assertThat(BlobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varbinary), Blob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeVarBinaryMax() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.VARBINARYMAX).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        assertThat(BlobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), Blob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeImage() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.IMAGE).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        assertThat(BlobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), Blob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeVarbinary() {\n\n        TypeInformation varbinary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"03 00 62 61 72\");\n\n        Blob blob = BlobCodec.INSTANCE.decode(data, ColumnUtil.createColumn(varbinary), Blob.class);\n\n        StepVerifier.create(blob.stream()).expectNext(ByteBuffer.wrap(\"bar\".getBytes())).verifyComplete();\n    }\n\n    @Test\n    void shouldBeAbleToDecodePlpStream() {\n\n        TypeInformation varbinary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer(12 + 24);\n        PlpLength.of(24).encode(buffer);\n\n        Length.of(8).encode(buffer, varbinary);\n        buffer.writeBytes(\"C1xxxxxx\".getBytes());\n\n        Length.of(8).encode(buffer, varbinary);\n        buffer.writeBytes(\"C2yyyyyy\".getBytes());\n\n        Length.of(8).encode(buffer, varbinary);\n        buffer.writeBytes(\"C3zzzzzz\".getBytes());\n\n        Blob blob = BlobCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(varbinary), Blob.class);\n\n        StepVerifier.create(blob.stream())\n            .expectNext(ByteBuffer.wrap(\"C1xxxxxx\".getBytes()))\n            .expectNext(ByteBuffer.wrap(\"C2yyyyyy\".getBytes()))\n            .expectNext(ByteBuffer.wrap(\"C3zzzzzz\".getBytes()))\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldReleaseConsumedBuffers() {\n\n        TypeInformation varbinary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;\n\n        ByteBuf buffer = alloc.buffer();\n        PlpLength.of(8).encode(buffer);\n\n        Length.of(8).encode(buffer, varbinary);\n        buffer.writeBytes(\"C1xxxxxx\".getBytes());\n\n        Blob blob = BlobCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(varbinary), Blob.class);\n        buffer.release();\n\n        StepVerifier.create(blob.stream())\n            .expectNextCount(1)\n            .verifyComplete();\n\n        assertThat(buffer.refCnt()).isZero();\n    }\n\n    @Test\n    void shouldReleaseRemainingBuffersOnCancel() {\n\n        TypeInformation varbinary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer(12 + 24);\n        PlpLength.of(24).encode(buffer);\n\n        Length.of(8).encode(buffer, varbinary);\n        buffer.writeBytes(\"C1xxxxxx\".getBytes());\n\n        Length.of(8).encode(buffer, varbinary);\n        buffer.writeBytes(\"C2yyyyyy\".getBytes());\n\n        Length.of(8).encode(buffer, varbinary);\n        buffer.writeBytes(\"C3zzzzzz\".getBytes());\n\n        Blob blob = BlobCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(varbinary), Blob.class);\n        buffer.release();\n\n        assertThat(buffer.refCnt()).isEqualTo(3);\n\n        StepVerifier.create(blob.stream(), 0)\n            .thenRequest(1)\n            .expectNextCount(1)\n            .thenCancel()\n            .verify();\n\n        assertThat(buffer.refCnt()).isZero();\n    }\n\n    @Test\n    void shouldReleaseOnDiscard() {\n\n        TypeInformation varbinary =\n            builder().withServerType(SqlServerType.VARBINARY).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;\n\n        ByteBuf buffer = alloc.buffer();\n        PlpLength.of(8).encode(buffer);\n\n        Length.of(8).encode(buffer, varbinary);\n        buffer.writeBytes(\"C1xxxxxx\".getBytes());\n\n        Blob blob = BlobCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(varbinary), Blob.class);\n        buffer.release();\n\n        StepVerifier.create(blob.discard())\n            .verifyComplete();\n\n        assertThat(buffer.refCnt()).isZero();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/BooleanCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link BooleanCodec}.\n *\n * @author Mark Paluch\n */\nclass BooleanCodecUnitTests {\n\n    @Test\n    void shouldEncodeBoolean() {\n\n        Encoded encoded = BooleanCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), true);\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"01 01 01\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"tinyint\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = BooleanCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"01 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"tinyint\");\n    }\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation tinyint =\n            builder().withServerType(SqlServerType.TINYINT).build();\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).build();\n\n        assertThat(BooleanCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), Boolean.class)).isTrue();\n        assertThat(BooleanCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varchar), Boolean.class)).isFalse();\n        assertThat(BooleanCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), String.class)).isFalse();\n    }\n\n    @Test\n    void shouldDecodeFromLong() {\n\n        TypeInformation type = createType(8, SqlServerType.BIGINT);\n\n        assertThat(BooleanCodec.INSTANCE.decode(HexUtils.decodeToByteBuf(\"0000000000000001\"), ColumnUtil.createColumn(type), Boolean.class)).isTrue();\n        assertThat(BooleanCodec.INSTANCE.decode(HexUtils.decodeToByteBuf(\"0000000000000000\"), ColumnUtil.createColumn(type), Boolean.class)).isFalse();\n    }\n\n    @Test\n    void shouldDecodeFromInteger() {\n\n        TypeInformation type = createType(4, SqlServerType.INTEGER);\n\n        assertThat(BooleanCodec.INSTANCE.decode(HexUtils.decodeToByteBuf(\"01000000\"), ColumnUtil.createColumn(type), Boolean.class)).isTrue();\n        assertThat(BooleanCodec.INSTANCE.decode(HexUtils.decodeToByteBuf(\"00000000\"), ColumnUtil.createColumn(type), Boolean.class)).isFalse();\n    }\n\n    @Test\n    void shouldDecodeFromSmallInt() {\n\n        TypeInformation type = createType(2, SqlServerType.SMALLINT);\n\n        assertThat(BooleanCodec.INSTANCE.decode(HexUtils.decodeToByteBuf(\"0100\"), ColumnUtil.createColumn(type), Boolean.class)).isTrue();\n    }\n\n    @Test\n    void shouldDecodeFromTinyInt() {\n\n        TypeInformation type = createType(1, SqlServerType.TINYINT);\n\n        assertThat(BooleanCodec.INSTANCE.decode(HexUtils.decodeToByteBuf(\"01\"), ColumnUtil.createColumn(type), Boolean.class)).isTrue();\n        assertThat(BooleanCodec.INSTANCE.decode(HexUtils.decodeToByteBuf(\"00\"), ColumnUtil.createColumn(type), Boolean.class)).isFalse();\n    }\n\n    private TypeInformation createType(int length, SqlServerType serverType) {\n        return builder().withMaxLength(length).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(length).withServerType(serverType).build();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/ByteCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ByteCodec}.\n *\n * @author Mark Paluch\n */\nclass ByteCodecUnitTests {\n\n    @Test\n    void shouldEncodeByte() {\n\n        Encoded encoded = ByteCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), (byte) 2);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"01 01 02\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"tinyint\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = ByteCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"01 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"tinyint\");\n    }\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation tinyint =\n            builder().withServerType(SqlServerType.TINYINT).build();\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).build();\n\n        assertThat(ByteCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), Byte.class)).isTrue();\n        assertThat(ByteCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varchar), Byte.class)).isFalse();\n        assertThat(ByteCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), String.class)).isFalse();\n    }\n\n    @Test\n    void shouldDecodeFromLong() {\n\n        TypeInformation type = createType(8, SqlServerType.BIGINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"FF00000000000000\");\n\n        assertThat(ByteCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Byte.class)).isEqualTo((byte) 255);\n    }\n\n    @Test\n    void shouldDecodeFromInteger() {\n\n        TypeInformation type = createType(4, SqlServerType.INTEGER);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01000000\");\n\n        assertThat(ByteCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Byte.class)).isEqualTo((byte) 1);\n    }\n\n    @Test\n    void shouldDecodeFromSmallInt() {\n\n        TypeInformation type = createType(2, SqlServerType.SMALLINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0100\");\n\n        assertThat(ByteCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Byte.class)).isEqualTo((byte) 1);\n    }\n\n    @Test\n    void shouldDecodeTinyInt() {\n\n        TypeInformation type = createType(1, SqlServerType.TINYINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01\");\n\n        assertThat(ByteCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Byte.class)).isEqualTo((byte) 1);\n    }\n\n    private TypeInformation createType(int length, SqlServerType serverType) {\n        return builder().withMaxLength(length).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(length).withServerType(serverType).build();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/ClobCodecUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.PooledByteBufAllocator;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.type.Length;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.PlpLength;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport io.r2dbc.spi.Clob;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.test.StepVerifier;\n\nimport java.nio.charset.StandardCharsets;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ClobCodec}.\n *\n * @author Mark Paluch\n */\nclass ClobCodecUnitTests {\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = ClobCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"40 1f 00 00 00 00 00 ff ff\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"varchar(8000)\");\n    }\n\n    @Test\n    void shouldBeAbleToEncodeNull() {\n\n        assertThat(ClobCodec.INSTANCE.canEncodeNull(Clob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeChar() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.CHAR).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        assertThat(ClobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), Clob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeVarVarChar() {\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).withLengthStrategy(LengthStrategy.USHORTLENTYPE).build();\n\n        assertThat(ClobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varchar), Clob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeVarCharMax() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.VARCHARMAX).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        assertThat(ClobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), Clob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeText() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.TEXT).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        assertThat(ClobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), Clob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeNtext() {\n\n        TypeInformation binary =\n            builder().withServerType(SqlServerType.NTEXT).withLengthStrategy(LengthStrategy.PARTLENTYPE).build();\n\n        assertThat(ClobCodec.INSTANCE.canDecode(ColumnUtil.createColumn(binary), Clob.class)).isTrue();\n    }\n\n    @Test\n    void shouldBeAbleToDecodeVarchar() {\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).withLengthStrategy(LengthStrategy.USHORTLENTYPE).withCharset(ServerCharset.CP1252.charset()).build();\n\n        ByteBuf data = TestByteBufAllocator.TEST.buffer();\n        Encode.uShort(data, 6);\n        data.writeCharSequence(\"foobar\", ServerCharset.CP1252.charset());\n\n        Clob clob = ClobCodec.INSTANCE.decode(data, ColumnUtil.createColumn(varchar), Clob.class);\n\n        StepVerifier.create(clob.stream()).expectNext(\"foobar\").verifyComplete();\n    }\n\n    @Test\n    void shouldBeAbleToDecodePlpStream() {\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(StandardCharsets.US_ASCII).build();\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.heapBuffer(12 + 24);\n        PlpLength.of(24).encode(buffer);\n\n        Length.of(8).encode(buffer, varchar);\n        buffer.writeBytes(\"C1xxxxxx\".getBytes());\n\n        Length.of(8).encode(buffer, varchar);\n        buffer.writeBytes(\"C2yyyyyy\".getBytes());\n\n        Length.of(8).encode(buffer, varchar);\n        buffer.writeBytes(\"C3zzzzzz\".getBytes());\n\n        Clob clob = ClobCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(varchar), Clob.class);\n\n        StepVerifier.create(clob.stream())\n            .expectNext(\"C1xxxxxx\")\n            .expectNext(\"C2yyyyyy\")\n            .expectNext(\"C3zzzzzz\")\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldReleaseConsumedBuffers() {\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(StandardCharsets.US_ASCII).build();\n\n        PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;\n\n        ByteBuf buffer = alloc.buffer();\n        PlpLength.of(8).encode(buffer);\n\n        Length.of(8).encode(buffer, varchar);\n        buffer.writeBytes(\"C1xxxxxx\".getBytes());\n\n        Clob clob = ClobCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(varchar), Clob.class);\n        buffer.release();\n\n        StepVerifier.create(clob.stream())\n            .expectNextCount(1)\n            .verifyComplete();\n\n        assertThat(buffer.refCnt()).isZero();\n    }\n\n    @Test\n    void shouldReleaseEmptyBuffer() {\n\n        TypeInformation type =\n            builder().withMaxLength(50).withLengthStrategy(LengthStrategy.PARTLENTYPE).withPrecision(50).withServerType(SqlServerType.NVARCHARMAX).withCharset(StandardCharsets.UTF_16LE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"00 00 00 00 00 00 00 00\");\n\n        Clob clob = ClobCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), Clob.class);\n        data.release();\n\n        Flux.from(clob.stream())\n            .as(StepVerifier::create)\n            .verifyComplete();\n\n        assertThat(data.refCnt()).isZero();\n    }\n\n    @Test\n    void shouldDecodeVarcharMaxSplitCharacter() {\n\n        TypeInformation type =\n            builder().withMaxLength(50).withLengthStrategy(LengthStrategy.PARTLENTYPE).withPrecision(50).withServerType(SqlServerType.NVARCHARMAX).withCharset(StandardCharsets.UTF_16LE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"62 00 00 00 00 00 00 00 \" +\n            \"2d 00 00 00 \" +\n            \"6c 00 65 00 61 00 6e 00 6e 00 65 00 2e 00 61 00 73 00 68 00 74 00 6f 00 6e 00 40 00 64 00 64 00 2d 00 70 00 75 00 62 00 2e 00 63 00 6f \" +\n            \"35 00 00 00 \" +\n            \"00 6d 00 2c 00 64 00 61 00 76 00 69 00 64 00 2e 00 6d 00 61 00 61 00 73 00 73 00 65 00 6e 00 40 00 64 00 64 00 2d 00 70 00 75 00 62 00 2e 00 63 00 6f 00 6d 00 \" +\n            \"00 00 00 00\");\n\n        Clob clob = ClobCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), Clob.class);\n        data.release();\n\n        Flux.from(clob.stream())\n            .as(StepVerifier::create)\n            .expectNext(\"leanne.ashton@dd-pub.c\")\n            .expectNext(\"om,david.maassen@dd-pub.com\")\n            .verifyComplete();\n\n        assertThat(data.refCnt()).isZero();\n    }\n\n    @Test\n    void shouldReportRemainderInDecodeBuffer() {\n\n        TypeInformation type =\n            builder().withMaxLength(50).withLengthStrategy(LengthStrategy.PARTLENTYPE).withPrecision(50).withServerType(SqlServerType.NVARCHARMAX).withCharset(StandardCharsets.UTF_16LE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"2d 00 00 00 00 00 00 00 \" +\n            \"2d 00 00 00 \" +\n            \"6c 00 65 00 61 00 6e 00 6e 00 65 00 2e 00 61 00 73 00 68 00 74 00 6f 00 6e 00 40 00 64 00 64 00 2d 00 70 00 75 00 62 00 2e 00 63 00 6f\");\n\n        Clob clob = ClobCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), Clob.class);\n        data.release();\n\n        Flux.from(clob.stream())\n            .as(StepVerifier::create)\n            .expectNext(\"leanne.ashton@dd-pub.c\")\n            .verifyError(ClobCodec.ClobDecodeException.class);\n\n        assertThat(data.refCnt()).isZero();\n    }\n\n    @Test\n    void shouldReleaseRemainingBuffersOnCancel() {\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(StandardCharsets.US_ASCII).build();\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer(12 + 24);\n        PlpLength.of(24).encode(buffer);\n\n        Length.of(8).encode(buffer, varchar);\n        buffer.writeBytes(\"C1xxxxxx\".getBytes());\n\n        Length.of(8).encode(buffer, varchar);\n        buffer.writeBytes(\"C2yyyyyy\".getBytes());\n\n        Length.of(8).encode(buffer, varchar);\n        buffer.writeBytes(\"C3zzzzzz\".getBytes());\n\n        Clob clob = ClobCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(varchar), Clob.class);\n        buffer.release();\n\n        assertThat(buffer.refCnt()).isEqualTo(1);\n\n        StepVerifier.create(clob.stream(), 0)\n            .thenRequest(1)\n            .expectNextCount(1)\n            .thenCancel()\n            .verify();\n\n        assertThat(buffer.refCnt()).isZero();\n    }\n\n    @Test\n    void shouldReleaseOnDiscard() {\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(StandardCharsets.US_ASCII).build();\n\n        PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;\n\n        ByteBuf buffer = alloc.buffer();\n        PlpLength.of(8).encode(buffer);\n\n        Length.of(8).encode(buffer, varchar);\n        buffer.writeBytes(\"C1xxxxxx\".getBytes());\n\n        Clob clob = ClobCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(varchar), Clob.class);\n        buffer.release();\n\n        StepVerifier.create(clob.discard())\n            .verifyComplete();\n\n        assertThat(buffer.refCnt()).isZero();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/ColumnUtil.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\n/**\n * Utility to create columns.\n *\n * @author Mark Paluch\n */\nclass ColumnUtil {\n\n    /**\n     * Create a mock column.\n     *\n     * @param typeInformation\n     * @return\n     */\n    static Column createColumn(TypeInformation typeInformation) {\n        return new Column(0, \"foo\", typeInformation, null);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/DecimalCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.math.BigDecimal;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link DecimalCodec}.\n *\n * @author Mark Paluch\n */\nclass DecimalCodecUnitTests {\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation tinyint =\n            builder().withServerType(SqlServerType.TINYINT).build();\n\n        TypeInformation numeric =\n            builder().withServerType(SqlServerType.NUMERIC).build();\n\n        assertThat(DecimalCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), BigDecimal.class)).isTrue();\n        assertThat(DecimalCodec.INSTANCE.canDecode(ColumnUtil.createColumn(numeric), BigDecimal.class)).isTrue();\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = DecimalCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"11 26 00 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"decimal(38,0)\");\n    }\n\n    @Test\n    void shouldEncodeNumeric5x2() {\n\n        Encoded encoded = DecimalCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), new BigDecimal(\"36.89\"));\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"11 26 02 03 01 69 0e\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"decimal(38,2)\");\n    }\n\n    @Test\n    void shouldEncodeNumeric10x3() {\n\n        Encoded encoded = DecimalCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), new BigDecimal(\"9.5E+2\"));\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"11 26 00 03 01 B6 03\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"decimal(38,0)\");\n    }\n\n    @Test\n    void shouldDecodeNumeric5x2() {\n\n        TypeInformation type = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.NUMERIC).withScale(2).withPrecision(5).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"05 01 69 0E 00 00\");\n\n        BigDecimal decoded = DecimalCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), BigDecimal.class);\n\n        assertThat(decoded).isEqualTo(\"36.89\");\n    }\n\n    @Test\n    void shouldDecodeNumeric5x0() {\n\n        TypeInformation type = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.NUMERIC).withScale(0).withPrecision(5).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"05 01 39 30 00 00\");\n\n        BigDecimal decoded = DecimalCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), BigDecimal.class);\n\n        assertThat(decoded).isEqualTo(\"12345\");\n    }\n\n    @Test\n    void shouldDecodeInteger() {\n\n        TypeInformation type = builder().withMaxLength(4).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(4).withServerType(SqlServerType.INTEGER).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01000000\");\n\n        assertThat(DecimalCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), BigDecimal.class)).isEqualTo(new BigDecimal(\"1\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/DoubleCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.offset;\n\n/**\n * Unit tests for {@link DoubleCodec}.\n *\n * @author Mark Paluch\n */\nclass DoubleCodecUnitTests {\n\n    @Test\n    void shouldEncodeDouble() {\n\n        Encoded encoded = DoubleCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), 11344.554);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"08 08 FE D4 78 E9 46 28 C6 40\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"float\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = DoubleCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"08 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"float\");\n    }\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation floatType =\n            builder().withServerType(SqlServerType.FLOAT).build();\n\n        TypeInformation intType =\n            builder().withServerType(SqlServerType.INTEGER).build();\n\n        assertThat(DoubleCodec.INSTANCE.canDecode(ColumnUtil.createColumn(floatType), Double.class)).isTrue();\n        assertThat(DoubleCodec.INSTANCE.canDecode(ColumnUtil.createColumn(intType), Double.class)).isFalse();\n    }\n\n    @Test\n    void shouldDecodeFloat() {\n\n        TypeInformation type = builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.FLOAT).withMaxLength(8).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"08FED478E94628C640\");\n\n        assertThat(DoubleCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Double.class)).isCloseTo(11344.554, offset(0.01));\n    }\n\n    @Test\n    void shouldDecodeReal() {\n\n        TypeInformation type = builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.REAL).withMaxLength(4).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0437423146\");\n\n        assertThat(DoubleCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Double.class)).isCloseTo(11344.554, offset(0.01));\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/EncodedUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.type.TdsDataType;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link Encoded}.\n *\n * @author Mark Paluch\n */\nclass EncodedUnitTests {\n\n    @Test\n    void shouldReportSqlServerType() {\n\n        Encoded encoded = Encoded.of(TdsDataType.INT1, Unpooled.EMPTY_BUFFER);\n\n        assertThat(encoded.getFormalType()).isEqualTo(\"tinyint\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/FloatCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link FloatCodec}.\n *\n * @author Mark Paluch\n */\nclass FloatCodecUnitTests {\n\n    @Test\n    void shouldEncodeFloat() {\n\n        Encoded encoded = FloatCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), 11344.554f);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"04 04 37 42 31 46\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"real\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = FloatCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"04 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"real\");\n    }\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation floatType =\n            builder().withServerType(SqlServerType.FLOAT).build();\n\n        TypeInformation intType =\n            builder().withServerType(SqlServerType.INTEGER).build();\n\n        assertThat(FloatCodec.INSTANCE.canDecode(ColumnUtil.createColumn(floatType), Float.class)).isTrue();\n        assertThat(FloatCodec.INSTANCE.canDecode(ColumnUtil.createColumn(intType), Float.class)).isFalse();\n    }\n\n    @Test\n    void shouldDecodeFloat() {\n\n        TypeInformation type = builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.FLOAT).withMaxLength(8).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"08FED478E94628C640\");\n\n        assertThat(FloatCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Float.class)).isEqualTo((float) 11344.554);\n    }\n\n    @Test\n    void shouldDecodeReal() {\n\n        TypeInformation type = builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.REAL).withMaxLength(4).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0437423146\");\n\n        assertThat(FloatCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Float.class)).isEqualTo((float) 11344.554);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/IntegerCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link IntegerCodec}.\n *\n * @author Mark Paluch\n */\nclass IntegerCodecUnitTests {\n\n    @Test\n    void shouldEncodeInteger() {\n\n        Encoded encoded = IntegerCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), 16777217);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"04 04 01 00 00 01\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"int\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = IntegerCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"04 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"int\");\n    }\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation tinyint =\n            builder().withServerType(SqlServerType.TINYINT).build();\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).build();\n\n        TypeInformation numeric =\n            builder().withServerType(SqlServerType.NUMERIC).build();\n\n        assertThat(IntegerCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), Integer.class)).isTrue();\n        assertThat(IntegerCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varchar), Integer.class)).isFalse();\n        assertThat(IntegerCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), String.class)).isFalse();\n        assertThat(IntegerCodec.INSTANCE.canDecode(ColumnUtil.createColumn(numeric), Integer.class)).isTrue();\n    }\n\n    @Test\n    void shouldDecodeFromLong() {\n\n        TypeInformation type = createType(8, SqlServerType.BIGINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0100000100000000\");\n\n        assertThat(IntegerCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Integer.class)).isEqualTo(16777217);\n    }\n\n    @Test\n    void shouldDecodeFromInteger() {\n\n        TypeInformation type = createType(4, SqlServerType.INTEGER);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01000000\");\n\n        assertThat(IntegerCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Integer.class)).isEqualTo(1);\n    }\n\n    @Test\n    void shouldDecodeFromNumeric() {\n\n        TypeInformation type = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.NUMERIC).withScale(0).withPrecision(5).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"05 01 39 30 00 00\");\n\n        assertThat(IntegerCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Integer.class)).isEqualTo(12345);\n    }\n\n    @Test\n    void shouldDecodeFromSmallInt() {\n\n        TypeInformation type = createType(2, SqlServerType.SMALLINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0100\");\n\n        assertThat(IntegerCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Integer.class)).isEqualTo(1);\n    }\n\n    @Test\n    void shouldDecodeTinyInt() {\n\n        TypeInformation type = createType(1, SqlServerType.TINYINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01\");\n\n        assertThat(IntegerCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Integer.class)).isEqualTo(1);\n    }\n\n    private TypeInformation createType(int length, SqlServerType serverType) {\n        return builder().withMaxLength(length).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(length).withServerType(serverType).build();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/LocalDateCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.LocalDate;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link LocalDateCodec}.\n *\n * @author Mark Paluch\n */\nclass LocalDateCodecUnitTests {\n\n    static final TypeInformation DATE = builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.DATE).build();\n\n    @Test\n    void shouldEncodeDate() {\n\n        LocalDate value = LocalDate.parse(\"2018-10-23\");\n\n        Encoded encoded = LocalDateCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), value);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"03 DD 3E 0B\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"date\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = LocalDateCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"date\");\n    }\n\n    @Test\n    void shouldDecodeNull() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"00\");\n\n        LocalDate decoded = LocalDateCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(DATE), LocalDate.class);\n\n        assertThat(decoded).isNull();\n    }\n\n    @Test\n    void shouldDecodeDate() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"03DD3E0B\");\n\n        LocalDate decoded = LocalDateCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(DATE), LocalDate.class);\n\n        assertThat(decoded).isEqualTo(\"2018-10-23\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/LocalDateTimeCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.LocalDateTime;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link LocalDateTimeCodec}.\n *\n * @author Mark Paluch\n */\nclass LocalDateTimeCodecUnitTests {\n\n    static final TypeInformation SMALLDATETIME = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.SMALLDATETIME).build();\n\n    static final TypeInformation DATETIME = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.DATETIME).build();\n\n    static final TypeInformation DATETIME2 = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withScale(7).withServerType(SqlServerType.DATETIME2).build();\n\n    @Test\n    void shouldDecodeSmallDateTime() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"04F5A83E00\");\n\n        LocalDateTime decoded = LocalDateTimeCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(SMALLDATETIME), LocalDateTime.class);\n\n        assertThat(decoded).isEqualTo(\"2018-06-04T01:02\");\n    }\n\n    @Test\n    void shouldEncodeSmallDateTime() {\n\n        LocalDateTime value = LocalDateTime.parse(\"2018-06-04T01:02\");\n\n        ByteBuf encoded = TestByteBufAllocator.TEST.buffer();\n\n        LocalDateTimeCodec.encode(encoded, SqlServerType.SMALLDATETIME, 0, value);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"F5A83E00\");\n    }\n\n    @Test\n    void shouldDecodeDateTime() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0886A90000AA700201\");\n\n        LocalDateTime decoded = LocalDateTimeCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(DATETIME), LocalDateTime.class);\n\n        assertThat(decoded).isEqualTo(\"2018-10-27T15:40:57.1\");\n    }\n\n    @Test\n    void shouldEncodeDateTime() {\n\n        LocalDateTime value = LocalDateTime.parse(\"2018-10-27T15:40:57.1\");\n\n        ByteBuf encoded = TestByteBufAllocator.TEST.buffer();\n        LocalDateTimeCodec.encode(encoded, SqlServerType.DATETIME, 0, value);\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"86A90000AA700201\");\n    }\n\n    @Test\n    void shouldDecodeDateTime2() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"082006E17483E13E0B\");\n\n        LocalDateTime decoded = LocalDateTimeCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(DATETIME2), LocalDateTime.class);\n\n        assertThat(decoded).isEqualTo(\"2018-10-27T15:41:00.162\");\n    }\n\n    @Test\n    void shouldEncodeDateTime2() {\n\n        LocalDateTime value = LocalDateTime.parse(\"2018-10-27T15:41:00.162\");\n\n        Encoded encoded = LocalDateTimeCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), value);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 08 20 06 E1 74 83 E1 3E 0B\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"datetime2\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = LocalDateTimeCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"datetime2\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/LocalTimeCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.LocalTime;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link LocalTimeCodec}.\n *\n * @author Mark Paluch\n */\nclass LocalTimeCodecUnitTests {\n\n    static final TypeInformation TIME = builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withScale(7).withServerType(SqlServerType.TIME).build();\n\n    @Test\n    void shouldEncodeTime() {\n\n        LocalTime value = LocalTime.parse(\"18:13:14\");\n\n        Encoded encoded = LocalTimeCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), value);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 05 00 19 12 B9 98\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"time\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = LocalTimeCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"time\");\n    }\n\n    @Test\n    void shouldDecodeTime() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"05 c0 c9 b1 61 5d\");\n\n        LocalTime decoded = LocalTimeCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(TIME), LocalTime.class);\n\n        assertThat(decoded).isEqualTo(\"11:08:27.100\");\n    }\n\n    @Test\n    void shouldDecodeNull() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"00\");\n\n        LocalTime decoded = LocalTimeCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(TIME), LocalTime.class);\n\n        assertThat(decoded).isNull();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/LongCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link LongCodec}.\n *\n * @author Mark Paluch\n */\nclass LongCodecUnitTests {\n\n    @Test\n    void shouldEncodeLong() {\n\n        Encoded encoded = LongCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), 72057594037927937L);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"08 08 01 00 00 00 00 0 00 0 01\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"bigint\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = LongCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"08 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"bigint\");\n    }\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation tinyint =\n            builder().withServerType(SqlServerType.TINYINT).build();\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).build();\n\n        assertThat(LongCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), Long.class)).isTrue();\n        assertThat(LongCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varchar), Long.class)).isFalse();\n        assertThat(LongCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), String.class)).isFalse();\n    }\n\n    @Test\n    void shouldDecodeFromLong() {\n\n        TypeInformation type = createType(8, SqlServerType.BIGINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0100000000000001\");\n\n        assertThat(LongCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Long.class)).isEqualTo(72057594037927937L);\n    }\n\n    @Test\n    void shouldDecodeFromInteger() {\n\n        TypeInformation type = createType(4, SqlServerType.INTEGER);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01000000\");\n\n        assertThat(LongCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Long.class)).isEqualTo(1);\n    }\n\n    @Test\n    void shouldDecodeFromSmallInt() {\n\n        TypeInformation type = createType(2, SqlServerType.SMALLINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0100\");\n\n        assertThat(LongCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Long.class)).isEqualTo(1);\n    }\n\n    @Test\n    void shouldDecodeTinyInt() {\n\n        TypeInformation type = createType(1, SqlServerType.TINYINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01\");\n\n        assertThat(LongCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Long.class)).isEqualTo(1);\n    }\n\n    private TypeInformation createType(int length, SqlServerType serverType) {\n        return builder().withMaxLength(length).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(length).withServerType(serverType).build();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/MoneyCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.token.Column;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.math.BigDecimal;\n\nimport static io.r2dbc.mssql.codec.ColumnUtil.createColumn;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link MoneyCodec}.\n *\n * @author Mark Paluch\n */\nclass MoneyCodecUnitTests {\n\n    @Test\n    void shouldEncodeMoney() {\n\n        Encoded encoded = MoneyCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), new BigDecimal(\"7301494.4032\"));\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"08 08 11 00 00 00 20 a1 07 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"money\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = MoneyCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"08 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"money\");\n    }\n\n    @Test\n    void shouldDecodeBigMoney() {\n\n        Column column =\n            createColumn(TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.MONEY).withMaxLength(8).build());\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer(9);\n        buffer.writeByte(8);\n        Encode.money(buffer, new BigDecimal(\"7301494.4032\").unscaledValue());\n\n        BigDecimal decoded = MoneyCodec.INSTANCE.decode(buffer, column, BigDecimal.class);\n\n        assertThat(decoded).isEqualTo(\"7301494.4032\");\n    }\n\n    @Test\n    void shouldDecodeSmallMoney() {\n\n        Column column =\n            createColumn(TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.SMALLMONEY).withMaxLength(4).build());\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0420A10500\");\n\n        BigDecimal decoded = MoneyCodec.INSTANCE.decode(buffer, column, BigDecimal.class);\n\n        assertThat(decoded).isEqualTo(\"36.8928\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/OffsetDateTimeCodecUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.OffsetDateTime;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link OffsetDateTimeCodec}.\n *\n * @author Mark Paluch\n */\nclass OffsetDateTimeCodecUnitTests {\n\n    static final TypeInformation DATETIMEOFFSET = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withScale(7).withServerType(SqlServerType.DATETIMEOFFSET).build();\n\n    @Test\n    void shouldEncodeDatetimeoffset() {\n\n        OffsetDateTime value = OffsetDateTime.parse(\"2018-08-27T17:41:14.890+00:45\");\n\n        Encoded encoded = OffsetDateTimeCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), value);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 0a a0 d8 dd f7 8d a4 3e 0b 2d 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"datetimeoffset\");\n    }\n\n    @Test\n    void shouldEncodeDatetimeOffsetNegativeTz() {\n\n        OffsetDateTime value = OffsetDateTime.parse(\"2018-08-27T17:41:14.890-00:45\");\n\n        Encoded encoded = OffsetDateTimeCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), value);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 0a a0 74 84 8a 9a a4 3e 0b d3 ff\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"datetimeoffset\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = OffsetDateTimeCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"datetimeoffset\");\n    }\n\n    @Test\n    void shouldDecodeDateTimeOffset() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0a a0 d8 dd f7 8d a4 3e 0b 2d 00\");\n\n        OffsetDateTime decoded = OffsetDateTimeCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(DATETIMEOFFSET), OffsetDateTime.class);\n\n        assertThat(decoded).isEqualTo(\"2018-08-27T17:41:14.890+00:45\");\n    }\n\n    @Test\n    void shouldDecodeDateTimeOffsetNegativeTz() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0a a0 74 84 8a 9a a4 3e 0b d3 ff\");\n\n        OffsetDateTime decoded = OffsetDateTimeCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(DATETIMEOFFSET), OffsetDateTime.class);\n\n        assertThat(decoded).isEqualTo(\"2018-08-27T17:41:14.890-00:45\");\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/PlpEncodedUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.CompositeByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.*;\n\n/**\n * Unit tests for {@link PlpEncoded}.\n *\n * @author Mark Paluch\n */\nclass PlpEncodedUnitTests {\n\n    @Test\n    void shouldSplitBigByteArray() {\n\n        byte[] bytes = new byte[255];\n\n        for (int i = Byte.MIN_VALUE; i < Byte.MAX_VALUE; i++) {\n            bytes[i - Byte.MIN_VALUE] = (byte) i;\n        }\n\n        PlpEncoded encoded = new PlpEncoded(SqlServerType.VARBINARYMAX, TestByteBufAllocator.TEST, Mono.just(Unpooled.wrappedBuffer(bytes)), () -> {\n\n        });\n\n        encoded.chunked(() -> 100, false).as(StepVerifier::create)\n            .assertNext(actual -> assertThat(actual.readableBytes()).isEqualTo(100))\n            .assertNext(actual -> assertThat(actual.readableBytes()).isEqualTo(100))\n            .assertNext(actual -> assertThat(actual.readableBytes()).isEqualTo(55))\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldSplitBigByteArrayWithPlpHeaders() {\n\n        byte[] bytes = new byte[255];\n\n        for (int i = Byte.MIN_VALUE; i < Byte.MAX_VALUE; i++) {\n            bytes[i - Byte.MIN_VALUE] = (byte) i;\n        }\n\n        PlpEncoded encoded = new PlpEncoded(SqlServerType.VARBINARYMAX, TestByteBufAllocator.TEST, Mono.just(Unpooled.wrappedBuffer(bytes)), () -> {\n\n        });\n\n        encoded.chunked(() -> 100, true).as(StepVerifier::create)\n            .assertNext(actual -> assertThat(actual.readableBytes()).isEqualTo(100 + 8 + 4))\n            .assertNext(actual -> assertThat(actual.readableBytes()).isEqualTo(100 + 4))\n            .assertNext(actual -> assertThat(actual.readableBytes()).isEqualTo(55 + 4))\n            .verifyComplete();\n    }\n\n    @Test\n    void shouldRearrangeChunks() {\n\n        byte[] bytes1 = new byte[255];\n        byte[] bytes2 = new byte[255];\n\n        for (int i = Byte.MIN_VALUE; i < Byte.MAX_VALUE; i++) {\n            bytes1[i - Byte.MIN_VALUE] = (byte) i;\n            bytes2[i - Byte.MIN_VALUE] = (byte) i;\n        }\n\n        PlpEncoded encoded = new PlpEncoded(SqlServerType.VARBINARYMAX, TestByteBufAllocator.TEST, Flux.just(Unpooled.wrappedBuffer(bytes1), Unpooled.wrappedBuffer(bytes2)), () -> {\n        });\n\n        AtomicInteger size = new AtomicInteger(100);\n\n        encoded.chunked(size::get).as(StepVerifier::create)\n            .assertNext(actual -> {\n                size.incrementAndGet();\n                assertThat(actual.readableBytes()).isEqualTo(100);\n            }).assertNext(actual -> {\n            size.incrementAndGet();\n            assertThat(actual.readableBytes()).isEqualTo(101);\n        }).assertNext(actual -> {\n            size.incrementAndGet();\n            assertThat(actual.readableBytes()).isEqualTo(102);\n        }).assertNext(actual -> {\n            size.incrementAndGet();\n            assertThat(actual.readableBytes()).isEqualTo(103);\n        }).assertNext(actual -> {\n            assertThat(actual.readableBytes()).isEqualTo(104);\n        }).verifyComplete();\n    }\n\n    @Test\n    void shouldDisposeUnusedBuffers() {\n\n        AtomicBoolean dispose = new AtomicBoolean();\n\n        PlpEncoded encoded = new PlpEncoded(SqlServerType.VARBINARYMAX, TestByteBufAllocator.TEST, Flux.empty(),\n            () -> dispose.set(true));\n\n        encoded.dispose();\n\n        assertThat(dispose).isTrue();\n    }\n\n    @Test\n    void emptySequenceShouldNeverAllocateCompositeBuffer() {\n\n        AtomicBoolean dispose = new AtomicBoolean();\n        ByteBufAllocator alloc = mock(ByteBufAllocator.class);\n        CompositeByteBuf composite = spy(TestByteBufAllocator.TEST.compositeBuffer());\n        doReturn(composite).when(alloc).compositeBuffer();\n\n        PlpEncoded encoded = new PlpEncoded(SqlServerType.VARBINARYMAX, alloc, Flux.empty(),\n            () -> dispose.set(true));\n\n        StepVerifier.create(encoded.chunked(() -> 1), 0).thenRequest(1).verifyComplete();\n\n        verifyNoInteractions(alloc);\n    }\n\n    @Test\n    void shouldDisposeCompositeBufferOnComplete() {\n\n        AtomicBoolean dispose = new AtomicBoolean();\n        ByteBufAllocator alloc = mock(ByteBufAllocator.class);\n        CompositeByteBuf composite = spy(TestByteBufAllocator.TEST.compositeBuffer());\n        doReturn(composite).when(alloc).compositeBuffer();\n\n        PlpEncoded encoded = new PlpEncoded(SqlServerType.VARBINARYMAX, alloc, Flux.just(Unpooled.wrappedBuffer(new byte[]{1, 2, 3})),\n            () -> dispose.set(true));\n\n        encoded.chunked(() -> 1)\n            .as(StepVerifier::create)\n            .expectNextCount(3)\n            .verifyComplete();\n\n        verify(composite).release();\n    }\n\n    @Test\n    void shouldDisposeCompositeBufferOnCancel() {\n\n        AtomicBoolean dispose = new AtomicBoolean();\n        ByteBufAllocator alloc = mock(ByteBufAllocator.class);\n        CompositeByteBuf composite = spy(TestByteBufAllocator.TEST.compositeBuffer());\n        doReturn(composite).when(alloc).compositeBuffer();\n\n        PlpEncoded encoded = new PlpEncoded(SqlServerType.VARBINARYMAX, alloc, Flux.just(Unpooled.wrappedBuffer(new byte[]{1, 2, 3})),\n            () -> dispose.set(true));\n\n        StepVerifier.create(encoded.chunked(() -> 1), 0)\n            .thenRequest(1)\n            .expectNextCount(1)\n            .thenCancel()\n            .verify();\n\n        verify(composite).release();\n    }\n\n    @Test\n    void shouldDisposeCompositeBufferOnError() {\n\n        AtomicBoolean dispose = new AtomicBoolean();\n        ByteBufAllocator alloc = mock(ByteBufAllocator.class);\n        CompositeByteBuf composite = spy(TestByteBufAllocator.TEST.compositeBuffer());\n        doReturn(composite).when(alloc).compositeBuffer();\n\n        PlpEncoded encoded = new PlpEncoded(SqlServerType.VARBINARYMAX, alloc, Flux.concat(Mono.just(Unpooled.wrappedBuffer(new byte[]{1, 2, 3})), Mono.error(new IllegalStateException())),\n            () -> dispose.set(true));\n\n        encoded.chunked(() -> 1).as(StepVerifier::create)\n            .expectNextCount(3)\n            .verifyError(IllegalStateException.class);\n\n        verify(composite).release();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/RpcEncodingUnitTests.java",
    "content": "/*\n * Copyright 2020-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Unit tests for {@link RpcEncoding}.\n *\n * @author Mark Paluch\n */\nclass RpcEncodingUnitTests {\n\n    @Test\n    void shouldEncodeStringAsNVarchar() {\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n        RpcEncoding.encodeString(buffer, null, RpcDirection.IN, Collation.RAW, \"hello world\");\n\n        byte[] unicode = \"hello world\".getBytes(ServerCharset.UNICODE.charset());\n\n        String len = \"16 00\";\n        EncodedAssert.assertThat(buffer).isEqualToHex(\"00 00 e7 40 1f 00 00 00 00 00 \" + len + ByteBufUtil.hexDump(unicode));\n    }\n\n    @Test\n    void shouldEncodeStringAsNVarcharMax() {\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n        RpcEncoding.encodeString(buffer, null, RpcDirection.OUT, Collation.RAW, \"hello world\");\n\n        byte[] unicode = \"hello world\".getBytes(ServerCharset.UNICODE.charset());\n\n        String uLongLen = \"16 00 00 00 00 00 00 00\";\n        String len = \"16 00 00 00\";\n\n        EncodedAssert.assertThat(buffer).isEqualToHex(\"00 01 e7 ff ff 00 00 00 00 00 \" + uLongLen + len + ByteBufUtil.hexDump(unicode) + \" 00 00 00 00\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/ShortCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ShortCodec}.\n *\n * @author Mark Paluch\n */\nclass ShortCodecUnitTests {\n\n    @Test\n    void shouldEncodeShort() {\n\n        Encoded encoded = ShortCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), (short) 258);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"02 02 02 01\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"smallint\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = ShortCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"02 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"smallint\");\n    }\n\n    @Test\n    void shouldBeAbleToDecode() {\n\n        TypeInformation tinyint =\n            builder().withServerType(SqlServerType.TINYINT).build();\n\n        TypeInformation varchar =\n            builder().withServerType(SqlServerType.VARCHAR).build();\n\n        assertThat(ShortCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), Short.class)).isTrue();\n        assertThat(ShortCodec.INSTANCE.canDecode(ColumnUtil.createColumn(varchar), Short.class)).isFalse();\n        assertThat(ShortCodec.INSTANCE.canDecode(ColumnUtil.createColumn(tinyint), String.class)).isFalse();\n    }\n\n    @Test\n    void shouldDecodeFromLong() {\n\n        TypeInformation type = createType(8, SqlServerType.BIGINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0101000000000000\");\n\n        assertThat(ShortCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Short.class)).isEqualTo((short) 257);\n    }\n\n    @Test\n    void shouldDecodeFromInteger() {\n\n        TypeInformation type = createType(4, SqlServerType.INTEGER);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01000000\");\n\n        assertThat(ShortCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Short.class)).isEqualTo((short) 1);\n    }\n\n    @Test\n    void shouldDecodeFromSmallInt() {\n\n        TypeInformation type = createType(2, SqlServerType.SMALLINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0100\");\n\n        assertThat(ShortCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Short.class)).isEqualTo((short) 1);\n    }\n\n    @Test\n    void shouldDecodeTinyInt() {\n\n        TypeInformation type = createType(1, SqlServerType.TINYINT);\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"01\");\n\n        assertThat(ShortCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), Short.class)).isEqualTo((short) 1);\n    }\n\n    private TypeInformation createType(int length, SqlServerType serverType) {\n        return builder().withMaxLength(length).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(length).withServerType(serverType).build();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/StringCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.codec.RpcParameterContext.ValueContext;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.charset.StandardCharsets;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link StringCodec}.\n *\n * @author Mark Paluch\n */\nclass StringCodecUnitTests {\n\n    @Test\n    void shouldEncodeNvarchar() {\n\n        Collation collation = Collation.from(13632521, 52);\n\n        ByteBuf data = TestByteBufAllocator.TEST.buffer();\n        Encode.uShort(data, 12);\n        data.writeCharSequence(\"foobar\", ServerCharset.UNICODE.charset());\n\n        Encoded encoded = StringCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(new RpcParameterContext.CharacterValueContext(collation, true)), \"foobar\");\n\n        EncodedAssert.assertThat(encoded).isEncodedAs(expected ->\n        {\n            expected.writeShortLE(8000); // max size\n\n            // collation windows-1252\n            expected.writeByte(0x09);\n            expected.writeByte(0x04);\n            expected.writeByte(0xD0);\n            expected.writeByte(0x00);\n            expected.writeByte(0x34);\n\n            expected.writeShortLE(12); // actual size\n\n            expected.writeCharSequence(\"foobar\", ServerCharset.UNICODE.charset());\n        });\n        assertThat(encoded.getFormalType()).isEqualTo(\"nvarchar(4000)\");\n    }\n\n    @Test\n    void shouldEncodeVarchar() {\n\n        Collation collation = Collation.from(13632521, 52);\n\n        Encoded encoded = StringCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(ValueContext.character(collation, false)), \"foobar\");\n\n        EncodedAssert.assertThat(encoded).isEncodedAs(expected ->\n        {\n            expected.writeShortLE(8000); // max size\n\n            // collation windows-1252\n            expected.writeByte(0x09);\n            expected.writeByte(0x04);\n            expected.writeByte(0xD0);\n            expected.writeByte(0x00);\n            expected.writeByte(0x34);\n\n            expected.writeShortLE(6); // actual size\n\n            expected.writeCharSequence(\"foobar\", ServerCharset.CP1252.charset());\n        });\n        assertThat(encoded.getFormalType()).isEqualTo(\"varchar(8000)\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = StringCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"40 1f 00 00 00 00 00 ff ff\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"varchar(8000)\");\n    }\n\n    @Test\n    void shouldBeAbleToDecodeUuid() {\n\n        TypeInformation type = builder().withMaxLength(16).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(16).withServerType(SqlServerType.GUID).build();\n\n        assertThat(StringCodec.INSTANCE.canDecode(ColumnUtil.createColumn(type), String.class)).isTrue();\n    }\n\n    @Test\n    void shouldDecodeUuid() {\n\n        TypeInformation type = builder().withMaxLength(16).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(16).withServerType(SqlServerType.GUID).build();\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"F17B0DC7C7E5C54098C7A12F7E686724FD\");\n\n        String value = StringCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), String.class);\n\n        assertThat(value).isEqualTo(\"C70D7BF1-E5C7-40C5-98C7-A12F7E686724\");\n    }\n\n    @Test\n    void shouldDecodeVarchar() {\n\n        TypeInformation type =\n            builder().withMaxLength(50).withLengthStrategy(LengthStrategy.USHORTLENTYPE).withPrecision(50).withServerType(SqlServerType.VARCHAR).withCharset(ServerCharset.CP1252.charset()).build();\n\n        ByteBuf data = TestByteBufAllocator.TEST.buffer();\n        Encode.uShort(data, 6);\n        data.writeCharSequence(\"foobar\", ServerCharset.CP1252.charset());\n\n        String value = StringCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), String.class);\n\n        assertThat(value).isEqualTo(\"foobar\");\n    }\n\n    @Test\n    void shouldDecodeVarcharMax() {\n\n        TypeInformation type =\n            builder().withMaxLength(50).withLengthStrategy(LengthStrategy.PARTLENTYPE).withPrecision(50).withServerType(SqlServerType.VARCHAR).withCharset(ServerCharset.CP1252.charset()).build();\n\n        ByteBuf data = TestByteBufAllocator.TEST.buffer();\n        Encode.uLongLong(data, 6);\n        Encode.asInt(data, 6);\n        data.writeCharSequence(\"foobar\", ServerCharset.CP1252.charset());\n\n        String value = StringCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), String.class);\n\n        assertThat(value).isEqualTo(\"foobar\");\n    }\n\n    @Test\n    void shouldDecodeVarcharMaxSplitCharacter() {\n\n        TypeInformation type =\n            builder().withMaxLength(50).withLengthStrategy(LengthStrategy.PARTLENTYPE).withPrecision(50).withServerType(SqlServerType.NVARCHARMAX).withCharset(StandardCharsets.UTF_16LE).build();\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"62 00 00 00 00 00 00 00 \" +\n            \"2d 00 00 00 \" +\n            \"6c 00 65 00 61 00 6e 00 6e 00 65 00 2e 00 61 00 73 00 68 00 74 00 6f 00 6e 00 40 00 64 00 64 00 2d 00 70 00 75 00 62 00 2e 00 63 00 6f \" +\n            \"35 00 00 00 \" +\n            \"00 6d 00 2c 00 64 00 61 00 76 00 69 00 64 00 2e 00 6d 00 61 00 61 00 73 00 73 00 65 00 6e 00 40 00 64 00 64 00 2d 00 70 00 75 00 62 00 2e 00 63 00 6f 00 6d 00 \" +\n            \"00 00 00 00\");\n\n        String value = StringCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), String.class);\n\n        assertThat(value).isEqualTo(\"leanne.ashton@dd-pub.com,david.maassen@dd-pub.com\");\n    }\n\n    @Test\n    void shouldDecodeNvarchar() {\n\n        TypeInformation type =\n            builder().withMaxLength(100).withLengthStrategy(LengthStrategy.USHORTLENTYPE).withPrecision(50).withServerType(SqlServerType.VARCHAR).withCharset(ServerCharset.UNICODE.charset()).build();\n\n        ByteBuf data = TestByteBufAllocator.TEST.buffer();\n        Encode.uShort(data, 12);\n        data.writeCharSequence(\"foobar\", ServerCharset.UNICODE.charset());\n\n        String value = StringCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), String.class);\n\n        assertThat(value).isEqualTo(\"foobar\");\n    }\n\n    @Test\n    void shouldDecodeChar() {\n\n        TypeInformation type =\n            builder().withMaxLength(20).withLengthStrategy(LengthStrategy.USHORTLENTYPE).withServerType(SqlServerType.CHAR).withCharset(ServerCharset.CP1252.charset()).build();\n\n        ByteBuf data = TestByteBufAllocator.TEST.buffer();\n        Encode.uShort(data, 20);\n        data.writeCharSequence(\"foobar              \", ServerCharset.CP1252.charset());\n\n        String value = StringCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), String.class);\n\n        assertThat(value).isEqualTo(\"foobar              \");\n    }\n\n    @Test\n    void shouldDecodeText() {\n\n        TypeInformation type =\n            builder().withMaxLength(2147483647).withLengthStrategy(LengthStrategy.LONGLENTYPE).withServerType(SqlServerType.TEXT).withCharset(ServerCharset.CP1252.charset()).build();\n\n        // Text value\n        ByteBuf data = HexUtils.decodeToByteBuf(\"10 64\" +\n            \"75 6D 6D 79 20 74 65 78 74 70 74 72 00 00 00 64\" +\n            \"75 6D 6D 79 54 53 00 0B 00 00 00 6D 79 74 65 78\" +\n            \"74 76 61 6C 75 65\");\n\n        String value = StringCodec.INSTANCE.decode(data, ColumnUtil.createColumn(type), String.class);\n\n        assertThat(value).isEqualTo(\"mytextvalue\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/TimestampCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Unit tests for {@link TimestampCodec}.\n *\n * @author Mark Paluch\n */\nclass TimestampCodecUnitTests {\n\n    @Test\n    void shouldEncodeTimestamp() {\n\n        byte[] value = new byte[]{0, 0, 0, 0, 0, 0, 7, -47};\n\n        assertThatThrownBy(() -> TimestampCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), value)).isInstanceOf(UnsupportedOperationException.class);\n    }\n\n    @Test\n    void shouldDecodeTimestamp() {\n\n        TypeInformation type = TypeInformation.builder().withLengthStrategy(LengthStrategy.USHORTLENTYPE).withServerType(SqlServerType.TIMESTAMP).build();\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"080000000000000007D1\");\n\n        byte[] decoded = TimestampCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), byte[].class);\n\n        assertThat(decoded).containsSequence(0, 0, 0, 0, 0, 0, 7, -47);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/UuidCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.UUID;\n\nimport static io.r2dbc.mssql.message.type.TypeInformation.builder;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link UuidCodec}.\n *\n * @author Mark Paluch\n */\nclass UuidCodecUnitTests {\n\n    @Test\n    void shouldEncodeUuid() {\n\n        UUID uuid = UUID.fromString(\"C70D7BF1-E5C7-40C5-98C7-A12F7E686724\");\n        Encoded encoded = UuidCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), uuid);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"10 10 F17B0DC7C7E5C54098C7A12F7E686724\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"uniqueidentifier\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = UuidCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"10 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"uniqueidentifier\");\n    }\n\n    @Test\n    void shouldDecodeUuid() {\n\n        TypeInformation type = builder().withMaxLength(16).withLengthStrategy(LengthStrategy.FIXEDLENTYPE).withPrecision(16).withServerType(SqlServerType.GUID).build();\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"F17B0DC7C7E5C54098C7A12F7E686724\");\n\n        UUID decoded = UuidCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(type), UUID.class);\n        assertThat(decoded).isEqualTo(UUID.fromString(\"C70D7BF1-E5C7-40C5-98C7-A12F7E686724\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/codec/ZonedDateTimeCodecUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.codec;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.ZonedDateTime;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ZonedDateTimeCodec}.\n *\n * @author Mark Paluch\n */\nclass ZonedDateTimeCodecUnitTests {\n\n    static final TypeInformation DATETIMEOFFSET = TypeInformation.builder().withLengthStrategy(LengthStrategy.BYTELENTYPE).withScale(7).withServerType(SqlServerType.DATETIMEOFFSET).build();\n\n    @Test\n    void shouldEncodeDatetimeoffset() {\n\n        ZonedDateTime value = ZonedDateTime.parse(\"2018-08-27T17:41:14.890+00:45[UT+00:45]\");\n\n        Encoded encoded = ZonedDateTimeCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.out(), value);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 0a a0 d8 dd f7 8d a4 3e 0b 2d 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"datetimeoffset\");\n    }\n\n    @Test\n    void shouldEncodeNull() {\n\n        Encoded encoded = ZonedDateTimeCodec.INSTANCE.encodeNull(TestByteBufAllocator.TEST);\n\n        EncodedAssert.assertThat(encoded).isEqualToHex(\"07 00\");\n        assertThat(encoded.getFormalType()).isEqualTo(\"datetimeoffset\");\n    }\n\n    @Test\n    void shouldDecodeDateTimeOffset() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0a a0 d8 dd f7 8d a4 3e 0b 2d 00\");\n\n        ZonedDateTime decoded = ZonedDateTimeCodec.INSTANCE.decode(buffer, ColumnUtil.createColumn(DATETIMEOFFSET), ZonedDateTime.class);\n\n        assertThat(decoded).isEqualTo(\"2018-08-27T17:41:14.890+00:45[UT+00:45]\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/TDSVersionUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link TDSVersion}.\n *\n * @author Mark Paluch\n */\nfinal class TDSVersionUnitTests {\n\n    @Test\n    void shouldCompareGreaterVersion() {\n\n        assertThat(TDSVersion.VER_KATMAI.isGreateOrEqualsTo(TDSVersion.VER_YUKON)).isTrue();\n        assertThat(TDSVersion.VER_KATMAI.isGreateOrEqualsTo(TDSVersion.VER_KATMAI)).isTrue();\n        assertThat(TDSVersion.VER_KATMAI.isGreateOrEqualsTo(TDSVersion.VER_DENALI)).isFalse();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/header/HeaderUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Mark Paluch\n */\nfinal class HeaderUnitTests {\n\n    @Test\n    void shouldEncodePreLoginHeader() {\n\n        Header header = new Header(Type.PRE_LOGIN, Status.of(Status.StatusBit.EOM), (short) 0x002F, (short) 0, (byte) 1, (byte) 0);\n\n        ByteBuf buffer = Unpooled.buffer(8);\n\n        header.encode(buffer);\n\n        assertThat(buffer.writerIndex()).isEqualTo(8);\n        assertThat(ByteBufUtil.prettyHexDump(buffer)).containsIgnoringCase(\"12 01 00 2F 00 00 01 00\");\n    }\n\n    @Test\n    void shouldDecodeLoginHeader() {\n\n        Header header = new Header(Type.PRE_LOGIN, Status.of(Status.StatusBit.EOM), (short) 0x002F, (short) 0, (byte) 1, (byte) 0);\n\n        ByteBuf buffer = Unpooled.buffer(8);\n\n        header.encode(buffer);\n\n        assertThat(Header.canDecode(buffer)).isTrue();\n\n        Header decoded = Header.decode(buffer);\n\n        assertThat(decoded.getType()).isEqualTo(Type.PRE_LOGIN);\n        assertThat(decoded.getStatus().is(Status.StatusBit.EOM)).isTrue();\n        assertThat(decoded.getLength()).isEqualTo((short) 0x002F);\n        assertThat(decoded.getSpid()).isEqualTo((short) 0);\n        assertThat(decoded.getPacketId()).isEqualTo((byte) 1);\n        assertThat(decoded.getWindow()).isEqualTo((byte) 0);\n    }\n\n    @Test\n    void shouldNotDecodeHeader() {\n\n        ByteBuf buffer = Unpooled.buffer(7);\n\n        assertThat(Header.canDecode(buffer)).isFalse();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/header/StatusUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link Status}.\n *\n * @author Mark Paluch\n */\nclass StatusUnitTests {\n\n    @ParameterizedTest\n    @EnumSource(Status.StatusBit.class)\n    void shouldReturnCorrectStatus(Status.StatusBit bit) {\n\n        if (bit == Status.StatusBit.NORMAL) {\n            return;\n        }\n\n        Status status = Status.of(bit);\n\n        assertThat(status.is(bit)).isTrue();\n    }\n\n    @Test\n    void shouldCreateCombinedStatus() {\n\n        Status status = Status.of(Status.StatusBit.EOM, Status.StatusBit.RESET_CONNECTION);\n\n        assertThat(status.is(Status.StatusBit.EOM)).isTrue();\n        assertThat(status.is(Status.StatusBit.RESET_CONNECTION)).isTrue();\n        assertThat(status.is(Status.StatusBit.RESET_CONNECTION_SKIP_TRAN)).isFalse();\n        assertThat(status.is(Status.StatusBit.IGNORE)).isFalse();\n    }\n\n    @Test\n    void shouldAddStatus() {\n\n        Status status = Status.of(Status.StatusBit.EOM).and(Status.StatusBit.RESET_CONNECTION);\n\n        assertThat(status.is(Status.StatusBit.EOM)).isTrue();\n        assertThat(status.is(Status.StatusBit.RESET_CONNECTION)).isTrue();\n\n        assertThat(status.and(Status.StatusBit.EOM)).isSameAs(status);\n\n        Status ignore = status.and(Status.StatusBit.IGNORE);\n        assertThat(ignore.is(Status.StatusBit.IGNORE)).isTrue();\n        assertThat(ignore).isNotSameAs(status);\n    }\n\n    @Test\n    void shouldRemoveStatus() {\n\n        Status status = Status.of(Status.StatusBit.EOM).and(Status.StatusBit.RESET_CONNECTION);\n\n        assertThat(status.not(Status.StatusBit.IGNORE)).isSameAs(status);\n\n        Status notEom = status.not(Status.StatusBit.EOM);\n        assertThat(notEom.is(Status.StatusBit.EOM)).isFalse();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/header/TypeUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.header;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * @author Mark Paluch\n */\nfinal class TypeUnitTests {\n\n    @Test\n    void shouldResolveType() {\n        assertThat(Type.valueOf((byte) 0x1)).isEqualTo(Type.SQL_BATCH);\n    }\n\n    @Test\n    void typeResolutionShouldFail() {\n        assertThatThrownBy(() -> Type.valueOf((byte) 0xFF)).hasMessageContaining(\"Invalid header type: 0xFF\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/AllHeadersUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link AllHeaders}.\n *\n * @author Mark Paluch\n */\nclass AllHeadersUnitTests {\n\n    @Test\n    void shouldEncodeProperly() {\n\n        byte[] tx = new byte[]{0, 0, 0, 0, 0, 0, 0, 0};\n\n        AllHeaders header = AllHeaders.transactional(tx, 1);\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n\n        header.encode(buffer);\n\n        assertThat(buffer.readableBytes()).isEqualTo(header.getLength());\n\n        EncodedAssert.assertThat(buffer).isEncodedAs(expected -> {\n\n            Encode.asInt(expected, 22); // all length inclusive\n            Encode.asInt(expected, 18); // header length inclusive\n            Encode.uShort(expected, 2); // Tx header\n            expected.writeBytes(tx); // Tx descriptor\n            Encode.dword(expected, 1); // outstanding requests\n        });\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/CanDecodeTestSupport.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\n\nimport java.util.function.Predicate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Support class to test {@literal canDecode} methods.\n *\n * @author Mark Paluch\n */\nfinal class CanDecodeTestSupport {\n\n    /**\n     * Test {@literal canDecode} method with a message without the type byte. This test method tests positive and negative decodability scenarios by truncating the data buffer until it does not\n     * contain any readable bytes.\n     *\n     * @param buffer    data buffer containing the message without the type byte.\n     * @param canDecode the {@literal canDecode} method to test.\n     */\n    public static void testCanDecode(ByteBuf buffer, Predicate<ByteBuf> canDecode) {\n\n        int expectedReadIndex = buffer.readerIndex();\n        int writerIndex = buffer.writerIndex();\n        assertThat(canDecode).accepts(buffer);\n        assertThat(buffer.readerIndex()).describedAs(\"readIndex after first canDecode()\").isEqualTo(expectedReadIndex);\n\n        for (int i = 1; i < writerIndex; i++) {\n\n            buffer.writerIndex(writerIndex - i);\n            assertThat(canDecode).describedAs(\"canDecode() with missing \" + i + \" bytes\").rejects(buffer);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/ColInfoTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ColInfoToken}.\n *\n * @author Mark Paluch\n */\nclass ColInfoTokenUnitTests {\n\n    @Test\n    void shouldDecodeToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"a50900010100020100030014\");\n\n        assertThat(buffer.readByte()).isEqualTo(ColInfoToken.TYPE);\n\n        ColInfoToken token = ColInfoToken.decode(buffer);\n\n        assertThat(token.getColumns()).hasSize(3);\n\n        ColInfoToken.ColInfo column = token.getColumns().get(0);\n\n        assertThat(column.getTable()).isEqualTo((byte) 1);\n        assertThat(column.getColumn()).isEqualTo((byte) 1);\n        assertThat(column.getStatus()).isEqualTo((byte) 0);\n        assertThat(column.getName()).isNull();\n        assertThat(buffer.readableBytes()).isZero();\n    }\n\n    @Test\n    void shouldSkipToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"a50900010100020100030014\");\n\n        assertThat(buffer.readByte()).isEqualTo(ColInfoToken.TYPE);\n\n        ColInfoToken token = ColInfoToken.skip(buffer);\n\n        assertThat(token.getColumns()).isEmpty();\n        assertThat(buffer.readableBytes()).isZero();\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"0900010100020100030014\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), ColInfoToken::canDecode);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/ColumnMetadataTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ColumnMetadataToken}.\n *\n * @author Mark Paluch\n */\nclass ColumnMetadataTokenUnitTests {\n\n    @Test\n    void shouldDecodeColumns() {\n\n        String encoded = \"04000000000000\" +\n            \"000800380B65006D0070006C006F0079\" +\n            \"00650065005F00690064000000000008\" +\n            \"00A732000904D00034096C0061007300\" +\n            \"74005F006E0061006D00650000000000\" +\n            \"0900A732000904D000340A6600690072\" +\n            \"00730074005F006E0061006D00650000\" +\n            \"00000009006E0806730061006C006100\" +\n            \"72007900\";\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(encoded);\n\n        ColumnMetadataToken metadata = ColumnMetadataToken.decode(buffer, true);\n\n        assertThat(metadata.getColumns()).hasSize(4).extracting(Column::getName).containsSequence(\"employee_id\", \"last_name\", \"first_name\", \"salary\");\n    }\n\n    @Test\n    void shouldDecodeIntAndVarcharMaxColumns() {\n\n        // columns: id INT, content VARCHAR(MAX)\n        String encoded = \"02 00 00 00 00 00 00\" +\n            \"00 09 00 26 04 02 69 00 64 00 00 00 00 00 09 00\" +\n            \"A7 FF FF 09 04 D0 00 34 07 63 00 6F 00 6E 00 74\" +\n            \"00 65 00 6E 00 74 00\";\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(encoded);\n\n        ColumnMetadataToken metadata = ColumnMetadataToken.decode(buffer, true);\n\n        assertThat(metadata.getColumns()).hasSize(2);\n\n        Column id = metadata.getColumns()[0];\n        assertThat(id.getName()).isEqualTo(\"id\");\n        assertThat(id.getType().getLengthStrategy()).isEqualTo(LengthStrategy.BYTELENTYPE);\n\n        Column content = metadata.getColumns()[1];\n        assertThat(content.getName()).isEqualTo(\"content\");\n        assertThat(content.getType().getLengthStrategy()).isEqualTo(LengthStrategy.PARTLENTYPE);\n    }\n\n    @Test\n    void shouldDecodeIntBinaryAndVarbinaryColumns() {\n\n        // columns: id INT, binfix BINARY(10), binvar VARBINARY(255)\n        String encoded = \"03 00 00 00 00 00 00\" +\n            \"00 08 00 38 02 69 00 64 00 00 00 00 00 09 00 AD\" +\n            \"0A 00 06 62 00 69 00 6E 00 66 00 69 00 78 00 00\" +\n            \"00 00 00 09 00 A5 FF 00 06 62 00 69 00 6E 00 76\" +\n            \"00 61 00 72 00\";\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(encoded);\n\n        ColumnMetadataToken metadata = ColumnMetadataToken.decode(buffer, true);\n\n        assertThat(metadata.getColumns()).hasSize(3);\n\n        Column id = metadata.getColumns()[0];\n        assertThat(id.getName()).isEqualTo(\"id\");\n        assertThat(id.getType().getLengthStrategy()).isEqualTo(LengthStrategy.FIXEDLENTYPE);\n\n        Column binfix = metadata.getColumns()[1];\n        assertThat(binfix.getName()).isEqualTo(\"binfix\");\n        assertThat(binfix.getType().getLengthStrategy()).isEqualTo(LengthStrategy.USHORTLENTYPE);\n        assertThat(binfix.getType().getServerType()).isEqualTo(SqlServerType.BINARY);\n\n        Column binvar = metadata.getColumns()[2];\n        assertThat(binvar.getName()).isEqualTo(\"binvar\");\n        assertThat(binvar.getType().getLengthStrategy()).isEqualTo(LengthStrategy.USHORTLENTYPE);\n        assertThat(binvar.getType().getServerType()).isEqualTo(SqlServerType.VARBINARY);\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"04000000000000\" +\n            \"000800380B65006D0070006C006F0079\" +\n            \"00650065005F00690064000000000008\" +\n            \"00A732000904D00034096C0061007300\" +\n            \"74005F006E0061006D00650000000000\" +\n            \"0900A732000904D000340A6600690072\" +\n            \"00730074005F006E0061006D00650000\" +\n            \"00000009006E0806730061006C006100\" +\n            \"72007900\";\n        ColumnMetadataToken metadata = ColumnMetadataToken.decode(HexUtils.decodeToByteBuf(data), true);\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), buffer -> ColumnMetadataToken.canDecode(buffer, true));\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/DoneInProcUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link DoneInProcToken}.\n *\n * @author Mark Paluch\n */\nclass DoneInProcUnitTests {\n\n    @Test\n    void shouldDecodeToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"FF1000C1000100000000000000\");\n\n        assertThat(buffer.readByte()).isEqualTo(DoneInProcToken.TYPE);\n\n        DoneInProcToken token = DoneInProcToken.decode(buffer);\n        assertThat(token.isDone()).isTrue();\n        assertThat(token.hasMore()).isFalse();\n        assertThat(token.hasCount()).isTrue();\n        assertThat(token.getRowCount()).isEqualTo(1);\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"1000C1000100000000000000\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), DoneInProcToken::canDecode);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/DoneProcUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link DoneProcToken}.\n *\n * @author Mark Paluch\n */\nclass DoneProcUnitTests {\n\n    @Test\n    void shouldDecodeToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"FE1000C1000100000000000000\");\n\n        assertThat(buffer.readByte()).isEqualTo(DoneProcToken.TYPE);\n\n        DoneProcToken token = DoneProcToken.decode(buffer);\n        assertThat(token.isDone()).isTrue();\n        assertThat(token.hasMore()).isFalse();\n        assertThat(token.hasCount()).isTrue();\n        assertThat(token.getRowCount()).isEqualTo(1);\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"1000C1000100000000000000\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), DoneProcToken::canDecode);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/DoneTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link DoneToken}.\n *\n * @author Mark Paluch\n */\nclass DoneTokenUnitTests {\n\n    @Test\n    void shouldDecodeToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"FD1000C1000100000000000000\");\n\n        assertThat(buffer.readByte()).isEqualTo(DoneToken.TYPE);\n\n        DoneToken token = DoneToken.decode(buffer);\n        assertThat(token.isDone()).isTrue();\n        assertThat(token.isError()).isFalse();\n        assertThat(token.hasMore()).isFalse();\n        assertThat(token.hasCount()).isTrue();\n        assertThat(token.getRowCount()).isEqualTo(1);\n    }\n\n    @Test\n    void shouldErrorDecodeToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"FD1200C1000100000000000000\");\n\n        assertThat(buffer.readByte()).isEqualTo(DoneToken.TYPE);\n\n        DoneToken token = DoneToken.decode(buffer);\n        assertThat(token.isDone()).isTrue();\n        assertThat(token.isError()).isTrue();\n        assertThat(token.hasMore()).isFalse();\n        assertThat(token.hasCount()).isTrue();\n        assertThat(token.getRowCount()).isEqualTo(1);\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"1000C1000100000000000000\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), DoneToken::canDecode);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/EnvChangeTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.tds.Redirect;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link EnvChangeToken}.\n *\n * @author Mark Paluch\n */\nfinal class EnvChangeTokenUnitTests {\n\n    @Test\n    void shouldDecodeDatabaseChange() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"E31B0001066D0061007300740065007200066D0061007300740065007200\");\n\n        assertThat(buffer.readByte()).isEqualTo(EnvChangeToken.TYPE);\n\n        EnvChangeToken token = EnvChangeToken.decode(buffer);\n\n        assertThat(token.getChangeType()).isEqualTo(EnvChangeToken.EnvChangeType.Database);\n        assertThat(token.getNewValueString()).isEqualTo(\"master\");\n        assertThat(token.getOldValueString()).isEqualTo(\"master\");\n    }\n\n    @Test\n    void shouldDecodeLanguageChange() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"e31700020a750073005f0065\" + \"006e0067006c0069007300680000\");\n\n        assertThat(buffer.readByte()).isEqualTo(EnvChangeToken.TYPE);\n\n        EnvChangeToken token = EnvChangeToken.decode(buffer);\n\n        assertThat(token.getChangeType()).isEqualTo(EnvChangeToken.EnvChangeType.Language);\n        assertThat(token.getNewValueString()).isEqualTo(\"us_english\");\n        assertThat(token.getOldValueString()).isEqualTo(\"\");\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"1B0001066D0061007300740065007200066D0061007300740065007200\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), EnvChangeToken::canDecode);\n    }\n\n    @Test\n    void shouldDecodeRoute() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"e316001413000039300700740065007300740069006e006700\");\n\n        assertThat(buffer.readByte()).isEqualTo(EnvChangeToken.TYPE);\n\n        EnvChangeToken token = EnvChangeToken.decode(buffer);\n        Redirect redirect = Redirect.decode(Unpooled.wrappedBuffer(token.getNewValue()));\n\n        assertThat(redirect.getPort()).isEqualTo(12345);\n        assertThat(redirect.getServerName()).isEqualTo(\"testing\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/ErrorTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ErrorToken}.\n *\n * @author Mark Paluch\n */\nclass ErrorTokenUnitTests {\n\n    @Test\n    void shouldDecodeErrorToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"AA5C00CF00000001\" +\n            \"101B0049006E00760061006C00690064\" +\n            \"00200063006F006C0075006D006E0020\" +\n            \"006E0061006D00650020002700660073\" +\n            \"006400660027002E000C610036003800\" +\n            \"38003000390032006100370039006600\" +\n            \"35000001000000\");\n\n        assertThat(buffer.readByte()).isEqualTo(ErrorToken.TYPE);\n\n        ErrorToken errorToken = ErrorToken.decode(buffer);\n\n        assertThat(errorToken.getNumber()).isEqualTo(207);\n        assertThat(errorToken.getState()).isEqualTo((byte) 1);\n        assertThat(errorToken.getMessage()).isEqualTo(\"Invalid column name 'fsdf'.\");\n        assertThat(errorToken.getServerName()).isEqualTo(\"a688092a79f5\");\n        assertThat(errorToken.getClassification()).isEqualTo(AbstractInfoToken.Classification.GENERAL_ERROR);\n        assertThat(errorToken.getProcName()).isEqualTo(\"\");\n        assertThat(errorToken.getLineNumber()).isEqualTo(16777216);\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"5C00CF00000001\" +\n            \"101B0049006E00760061006C00690064\" +\n            \"00200063006F006C0075006D006E0020\" +\n            \"006E0061006D00650020002700660073\" +\n            \"006400660027002E000C610036003800\" +\n            \"38003000390032006100370039006600\" +\n            \"35000001000000\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), ErrorToken::canDecode);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/FeatureExtAckTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.token.FeatureExtAckToken.ColumnEncryption;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link FeatureExtAckToken}.\n *\n * @author Mark Paluch\n */\nclass FeatureExtAckTokenUnitTests {\n\n    @Test\n    void shouldDecode() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"ae040100000001ff\");\n\n        assertThat(buffer.readByte()).isEqualTo(FeatureExtAckToken.TYPE);\n\n        FeatureExtAckToken token = FeatureExtAckToken.decode(buffer);\n\n        List<FeatureExtAckToken.FeatureToken> tokens = token.getFeatureTokens();\n        assertThat(tokens).hasSize(1);\n\n        ColumnEncryption encryption = (ColumnEncryption) tokens.get(0);\n\n        assertThat(encryption.getTceVersion()).isEqualTo((byte) 1);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/IdentifierUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Unit tests for {@link Identifier}.\n *\n * @author Mark Paluch\n */\nclass IdentifierUnitTests {\n\n    @Test\n    void shouldCreateObjectIdentifier() {\n\n        Identifier identifier = Identifier.objectName(\"foo\");\n\n        assertThat(identifier.asEscapedString()).isEqualTo(\"[foo]\");\n    }\n\n    @Test\n    void shouldCreateFullQualifier() {\n\n        Identifier identifier = Identifier.builder().objectName(\"obj\").schemaName(\"schema\").databaseName(\"db\").serverName(\"server\").build();\n\n        assertThat(identifier.asEscapedString()).isEqualTo(\"[server].[db].[schema].[obj]\");\n    }\n\n    @Test\n    void shouldRejectServernameWithoutDatabase() {\n\n        assertThatThrownBy(() -> Identifier.builder().objectName(\"obj\").serverName(\"server\").build()).isInstanceOf(IllegalStateException.class);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/InfoTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link InfoToken}.\n *\n * @author Mark Paluch\n */\nfinal class InfoTokenUnitTests {\n\n    @Test\n    void shouldDecode() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"AB700045160000020025\" + \"004300680061006E0067006500640020\"\n            + \"00640061007400610062006100730065\" + \"00200063006F006E0074006500780074\" + \"00200074006F00200027006D00610073\"\n            + \"0074006500720027002E000C61003600\" + \"38003800300039003200610037003900\" + \"660035000001000000\");\n\n        assertThat(buffer.readByte()).isEqualTo(InfoToken.TYPE);\n\n        InfoToken infoToken = InfoToken.decode(buffer);\n\n        assertThat(infoToken.getNumber()).isEqualTo(5701);\n        assertThat(infoToken.getState()).isEqualTo((byte) 2);\n        assertThat(infoToken.getMessage()).isEqualTo(\"Changed database context to 'master'.\");\n        assertThat(infoToken.getServerName()).isEqualTo(\"a688092a79f5\");\n        assertThat(infoToken.getProcName()).isEqualTo(\"\");\n        assertThat(infoToken.getLineNumber()).isEqualTo(16777216);\n    }\n\n    @Test\n    void shouldDecodeLanguageChange() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"ab74\" +\n            \"0047160000010027004300680061006e\" +\n            \"0067006500640020006c0061006e0067\" +\n            \"00750061006700650020007300650074\" +\n            \"00740069006e006700200074006f0020\" +\n            \"00750073005f0065006e0067006c0069\" +\n            \"00730068002e000c6100360038003800\" +\n            \"30003900320061003700390066003500\" +\n            \"0001000000\");\n\n        assertThat(buffer.readByte()).isEqualTo(InfoToken.TYPE);\n\n        InfoToken infoToken = InfoToken.decode(buffer);\n\n        assertThat(infoToken.getNumber()).isEqualTo(5703);\n        assertThat(infoToken.getState()).isEqualTo((byte) 1);\n        assertThat(infoToken.getMessage()).isEqualTo(\"Changed language setting to us_english.\");\n        assertThat(infoToken.getServerName()).isEqualTo(\"a688092a79f5\");\n        assertThat(infoToken.getProcName()).isEqualTo(\"\");\n        assertThat(infoToken.getLineNumber()).isEqualTo(16777216);\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"74\" +\n            \"0047160000010027004300680061006e\" +\n            \"0067006500640020006c0061006e0067\" +\n            \"00750061006700650020007300650074\" +\n            \"00740069006e006700200074006f0020\" +\n            \"00750073005f0065006e0067006c0069\" +\n            \"00730068002e000c6100360038003800\" +\n            \"30003900320061003700390066003500\" +\n            \"0001000000\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), InfoToken::canDecode);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/Login7UnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.TDSVersion;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.ContextualTdsFragment;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport io.r2dbc.mssql.util.Version;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link Login7}.\n *\n * @author Mark Paluch\n */\nfinal class Login7UnitTests {\n\n    @Test\n    void shouldRenderSimpleLoginPacket() {\n\n        Login7 login7 = Login7.builder()\n            .serverName(\"localhost\")\n            .hostName(\"some-fancy-hostname\")\n            .username(\"sa\")\n            .password(\"super-secret\")\n            .database(\"master\")\n            .clientLibraryName(\"MyDriver\")\n            .applicationName(\"MyApp\")\n            .clientLibraryVersion(Version.parse(\"6.4\"))\n            .tdsVersion(TDSVersion.VER_DENALI).build();\n\n        ByteBuf buffer = Unpooled.buffer(400);\n        login7.encode(buffer);\n\n        // Header: 100100eb00000100\n        byte[] expected = ByteBufUtil.decodeHexDump(\"e300000004000074\" + \"401f0000000004060000000000000000\"\n            + \"e003001800000000000000005e001300\" + \"8400020088000c00a0000500aa000900\" + \"bc000400c000080000000000d0000600\"\n            + \"00000000000000000000000000000000\" + \"00000000000073006f006d0065002d00\" + \"660061006e00630079002d0068006f00\"\n            + \"730074006e0061006d00650073006100\" + \"92a5f2a5a2a5f3a582a577a592a5f3a5\" + \"93a582a5f3a5e2a54d00790041007000\"\n            + \"70006c006f00630061006c0068006f00\" + \"73007400dc0000004d00790044007200\" + \"69007600650072006d00610073007400\"\n            + \"65007200040100000001ff\");\n\n        assertThat(ByteBufUtil.prettyHexDump(buffer))\n            .isEqualTo(ByteBufUtil.prettyHexDump(Unpooled.wrappedBuffer(expected)));\n    }\n\n    @Test\n    void shouldEncodePacket() {\n\n        Login7 login7 = Login7.builder()\n            .serverName(\"localhost\")\n            .hostName(\"some-fancy-hostname\")\n            .username(\"sa\")\n            .password(\"super-secret\")\n            .database(\"master\")\n            .clientLibraryName(\"MyDriver\")\n            .applicationName(\"MyApp\")\n            .clientLibraryVersion(Version.parse(\"6.4\"))\n            .tdsVersion(TDSVersion.VER_DENALI).build();\n\n        Mono.just(login7.encode(TestByteBufAllocator.TEST, 0))\n            .cast(ContextualTdsFragment.class)\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> {\n                assertThat(actual.getHeaderOptions().getType()).isEqualTo(Type.TDS7_LOGIN);\n                assertThat(actual.getHeaderOptions().getStatus().getValue()).isEqualTo((byte) 0);\n            }).verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/LoginAckTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.TDSVersion;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.Version;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Mark Paluch\n */\nfinal class LoginAckTokenUnitTests {\n\n    @Test\n    void shouldDecode() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"ad36000174000004164d00\" + \"6900630072006f0073006f0066007400\"\n            + \"2000530051004c002000530065007200\" + \"760065007200000000000e000bde\");\n\n        assertThat(buffer.readByte()).isEqualTo(LoginAckToken.TYPE);\n\n        LoginAckToken token = LoginAckToken.decode(buffer);\n        assertThat(token.getTdsVersion()).isEqualTo(TDSVersion.VER_DENALI.getVersion());\n        assertThat(token.getClientInterface()).isEqualTo(LoginAckToken.CLIENT_INTEFACE_TSQL);\n        assertThat(token.getProgrName()).startsWith(\"Microsoft SQL Server\");\n        assertThat(token.getVersion()).isEqualTo(Version.parse(\"14.0.3038\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/NbcRowTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.Types;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link NbcRowToken}.\n *\n * @author Mark Paluch\n */\nclass NbcRowTokenUnitTests {\n\n    TypeInformation integerType = Types.integer();\n\n    TypeInformation stringType = Types.varchar(255);\n\n    Column[] columns = Arrays.asList(new Column(0, \"id\", this.integerType),\n        new Column(1, \"first_name\", this.stringType),\n        new Column(2, \"last_name\", this.stringType),\n        new Column(3, \"other\", this.stringType),\n        new Column(4, \"other2\", this.stringType),\n        new Column(5, \"other3\", this.stringType),\n        new Column(6, \"rowstat\", this.integerType)).toArray(new Column[0]);\n\n    @Test\n    void shouldDecodeNbcRow() {\n\n        ByteBuf data = HexUtils.decodeToByteBuf(\"D2 1C 04 01 00 00 00 01 00 61 02 00 78 61 04 01 00 00 00\");\n\n        assertThat(data.readByte()).isEqualTo(NbcRowToken.TYPE);\n\n        NbcRowToken rowToken = NbcRowToken.decode(data, this.columns);\n\n        assertThat(rowToken.getColumnData(0)).isNotNull();\n        assertThat(rowToken.getColumnData(1)).isNotNull();\n        assertThat(rowToken.getColumnData(2)).isNull();\n        assertThat(rowToken.getColumnData(3)).isNull();\n        assertThat(rowToken.getColumnData(4)).isNull();\n        assertThat(rowToken.getColumnData(5)).isNotNull();\n        assertThat(rowToken.getColumnData(6)).isNotNull();\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"1C 04 01 00 00 00 01 00 61 02 00 78 61 04 01 00 00 00\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), buffer -> NbcRowToken.canDecode(buffer, this.columns));\n    }\n\n    @Test\n    void shouldDecodePlpNull() {\n\n        TypeInformation integerType = TypeInformation.builder().withServerType(SqlServerType.INTEGER).withLengthStrategy(LengthStrategy.BYTELENTYPE).build();\n        TypeInformation plpType = TypeInformation.builder().withServerType(SqlServerType.VARCHARMAX).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(ServerCharset.CP1252.charset()).build();\n\n        Column id = new Column(0, \"id\", integerType);\n        Column content = new Column(1, \"content\", plpType);\n        ColumnMetadataToken columns = ColumnMetadataToken.create(new Column[]{id, content});\n\n        ByteBuf rowData = HexUtils.decodeToByteBuf(\"02 04 01 00 00 00\");\n\n        NbcRowToken row = NbcRowToken.decode(rowData, columns.getColumns());\n\n        assertThat(row.getColumnData(0).readableBytes()).isEqualTo(5);\n        assertThat(row.getColumnData(1)).isNull();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/OrderTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link OrderToken}.\n *\n * @author Mark Paluch\n */\nclass OrderTokenUnitTests {\n\n    @Test\n    void shouldDecodeOrderToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"a9 02 00 02 00\");\n\n        assertThat(buffer.readByte()).isEqualTo(OrderToken.TYPE);\n\n        OrderToken orderToken = OrderToken.decode(buffer);\n\n        assertThat(orderToken.getOrderByColumns()).containsOnly(2);\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"02 00 02 00\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), OrderToken::canDecode);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/PreloginUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.ContextualTdsFragment;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\nimport reactor.test.StepVerifier;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link Prelogin}.\n *\n * @author Mark Paluch\n */\nfinal class PreloginUnitTests {\n\n    @Test\n    void shouldUsePreloginHeader() {\n\n        Prelogin prelogin = Prelogin.builder().build();\n\n        Mono.just(prelogin.encode(TestByteBufAllocator.TEST, 0))\n            .cast(ContextualTdsFragment.class)\n            .as(StepVerifier::create)\n            .consumeNextWith(actual -> {\n                assertThat(actual.getHeaderOptions().getType()).isEqualTo(Type.PRE_LOGIN);\n            }).verifyComplete();\n    }\n\n    @Test\n    void shouldEncodePrelogin() {\n\n        Prelogin prelogin = Prelogin.builder().build();\n        ByteBuf buffer = Unpooled.buffer(32);\n\n        prelogin.encode(buffer);\n\n        String result = ByteBufUtil.prettyHexDump(buffer);\n\n        // Version header at offset 10, length 6\n        assertThat(result).containsSequence(\"00 00 10 00 06\");\n    }\n\n    @Test\n    void shouldEncodePreloginVersion() {\n\n        Prelogin.Version token = new Prelogin.Version(0, 0);\n\n        assertThat(token.getTokenHeaderLength()).isEqualTo(5);\n        assertThat(token.getDataLength()).isEqualTo(6);\n\n        ByteBuf buffer = Unpooled.buffer(11);\n\n        token.encodeToken(buffer, 0);\n        token.encodeStream(buffer);\n\n        // Token header sequence\n        assertThat(buffer.array()).startsWith(0, 0, 0, 0, 6);\n\n        // Token data sequence\n        assertThat(buffer.array()).endsWith(0, 0, 0, 0, 0, 0);\n    }\n\n    @Test\n    void shouldEncodeEncryption() {\n\n        Prelogin.Encryption token = new Prelogin.Encryption(Prelogin.Encryption.ENCRYPT_NOT_SUP);\n\n        assertThat(token.getTokenHeaderLength()).isEqualTo(5);\n        assertThat(token.getDataLength()).isEqualTo(1);\n\n        ByteBuf buffer = Unpooled.buffer(6);\n\n        token.encodeToken(buffer, 0);\n        token.encodeStream(buffer);\n\n        // Token header sequence\n        assertThat(buffer.array()).startsWith(1, 0, 0, 0, 1);\n\n        // Token data sequence\n        assertThat(buffer.array()).endsWith(Prelogin.Encryption.ENCRYPT_NOT_SUP);\n    }\n\n    @Test\n    void shouldDecodePreloginResponse() {\n\n        // Server version 14.0.3038.14\n        // Encryption disabled\n        // Instance validation 0x00\n        String response = \"00 00 10 00 06 01 00 16 00 01 02 00 17 00 01 ff 0e 00 0b de 00 00 02 00\";\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(response);\n        Prelogin prelogin = Prelogin.decode(buffer);\n\n        assertThat(prelogin.getTokens()).hasSize(4);\n        assertThat(prelogin.getToken(Prelogin.Version.class)).isNotEmpty().hasValueSatisfying(actual -> {\n\n            assertThat(actual.getVersion()).isEqualTo(14);\n            assertThat(actual.getSubbuild()).isEqualTo((short) 3038);\n        });\n\n        assertThat(prelogin.getToken(Prelogin.Encryption.class)).isNotEmpty().hasValueSatisfying(actual -> {\n\n            assertThat(actual.getEncryption()).isEqualTo(Prelogin.Encryption.ENCRYPT_NOT_SUP);\n        });\n\n        assertThat(prelogin.getToken(Prelogin.Terminator.class)).hasValue(Prelogin.Terminator.INSTANCE);\n    }\n\n    @Test\n    void decodeShouldConsumeRemainingBytes() {\n\n        String response = \"00 00 10 00 06 01 00 16 00 01 02 00 17 00 01 ff 0e 00 0b de 00 00 02 00 01 02 03 04\";\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(response);\n        Prelogin.decode(buffer);\n\n        assertThat(buffer.readerIndex()).isEqualTo(buffer.writerIndex());\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/ReturnValueUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.util.EncodedAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ReturnValue}.\n *\n * @author Mark Paluch\n */\nclass ReturnValueUnitTests {\n\n    @Test\n    void shouldDecode() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"AC0000000100000000000026\" +\n            \"0404F3DEBC0A\");\n\n        assertThat(buffer.readByte()).isEqualTo(ReturnValue.TYPE);\n\n        ReturnValue returnValue = ReturnValue.decode(buffer, false);\n\n        assertThat(returnValue.getOrdinal()).isEqualTo(0);\n        assertThat(returnValue.getParameterName()).isEqualTo(\"\");\n        assertThat(returnValue.getStatus()).isEqualTo((byte) 1);\n        assertThat(returnValue.getValueType().getServerType()).isEqualTo(SqlServerType.INTEGER);\n        assertThat(returnValue.getValueType().getLengthStrategy()).isEqualTo(LengthStrategy.BYTELENTYPE);\n        assertThat(returnValue.getValueType().getPrecision()).isEqualTo(10);\n        assertThat(buffer.readerIndex()).isEqualTo(buffer.writerIndex());\n\n        EncodedAssert.assertThat(returnValue.getValue()).isEncodedAs(expected -> {\n            expected.writeByte(4); // length\n            expected.writeIntLE(180150003);\n        });\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"00000001000000000000260404F3DEBC0A\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), buffer -> ReturnValue.canDecode(buffer, true));\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/RowTokenFactory.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\n\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Utility to create a {@link RowToken}.\n *\n * @author Mark Paluch\n */\npublic final class RowTokenFactory {\n\n    /**\n     * Creates a {@link RowToken} using {@link ColumnMetadataToken} and an encoded data buffer.\n     *\n     * @param columnMetadata\n     * @param bufferWriter\n     * @return\n     */\n    public static RowToken create(ColumnMetadataToken columnMetadata, Consumer<ByteBuf> bufferWriter) {\n        return create(columnMetadata.getColumns(), bufferWriter);\n    }\n\n    /**\n     * Creates a {@link RowToken} using {@link Column}s and an encoded data buffer.\n     *\n     * @param columns\n     * @param bufferWriter\n     * @return\n     */\n    public static RowToken create(List<Column> columns, Consumer<ByteBuf> bufferWriter) {\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n\n        bufferWriter.accept(buffer);\n\n        return RowToken.decode(buffer, columns.toArray(new Column[0]));\n    }\n\n    /**\n     * Creates a {@link RowToken} using {@link Column}s and an encoded data buffer.\n     *\n     * @param columns\n     * @param bufferWriter\n     * @return\n     */\n    public static RowToken create(Column[] columns, Consumer<ByteBuf> bufferWriter) {\n\n        ByteBuf buffer = TestByteBufAllocator.TEST.buffer();\n\n        bufferWriter.accept(buffer);\n\n        return RowToken.decode(buffer, columns);\n    }\n\n    private RowTokenFactory() {\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/RowTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.message.tds.ServerCharset;\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.BufferedReader;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link RowToken}.\n *\n * @author Mark Paluch\n */\nclass RowTokenUnitTests {\n\n    @Test\n    void shouldDecodeRow() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"8107000000000000\" +\n            \"000800300B65006D0070006C006F0079\" +\n            \"00650065005F00690064000000000008\" +\n            \"00E764000904D00034096C0061007300\" +\n            \"74005F006E0061006D00650000000000\" +\n            \"0900A732000904D000340A6600690072\" +\n            \"00730074005F006E0061006D00650000\" +\n            \"00000009006E0806730061006C006100\" +\n            \"7200790000000000090024100366006F\" +\n            \"006F000000000009006D080366006C00\" +\n            \"74000000000009006D04036200610072\" +\n            \"00D1010C00700061006C007500630068\" +\n            \"0004006D61726B080000000020A10700\" +\n            \"10F17B0DC7C7E5C54098C7A12F7E6867\" +\n            \"2408FED478E94628C6400437423146\");\n\n        Tabular tabular = Tabular.decode(buffer, true);\n\n        assertThat(tabular.getTokens()).hasSize(2);\n\n        RowToken rowToken = tabular.getRequiredToken(RowToken.class);\n        assertThat(rowToken.getColumnData(0)).isNotNull();\n        assertThat(rowToken.getColumnData(1)).isNotNull();\n        assertThat(rowToken.getColumnData(2)).isNotNull();\n        assertThat(rowToken.getColumnData(3)).isNotNull();\n        buffer.release();\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        ByteBuf rowMetadata = HexUtils.decodeToByteBuf(\"8107000000000000\" +\n            \"000800300B65006D0070006C006F0079\" +\n            \"00650065005F00690064000000000008\" +\n            \"00E764000904D00034096C0061007300\" +\n            \"74005F006E0061006D00650000000000\" +\n            \"0900A732000904D000340A6600690072\" +\n            \"00730074005F006E0061006D00650000\" +\n            \"00000009006E0806730061006C006100\" +\n            \"7200790000000000090024100366006F\" +\n            \"006F000000000009006D080366006C00\" +\n            \"74000000000009006D04036200610072\" +\n            \"00\");\n\n        ColumnMetadataToken columns = ColumnMetadataToken.decode(rowMetadata.skipBytes(1), true);\n\n        String row = \"010C00700061006C007500630068\" +\n            \"0004006D61726B080000000020A10700\" +\n            \"10F17B0DC7C7E5C54098C7A12F7E6867\" +\n            \"2408FED478E94628C6400437423146\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(row), buffer -> RowToken.canDecode(buffer, columns.getColumns()));\n    }\n\n    @Test\n    void shouldDecodeIntAndVarcharMax() throws IOException {\n\n        TypeInformation integerType = TypeInformation.builder().withServerType(SqlServerType.INTEGER).withLengthStrategy(LengthStrategy.BYTELENTYPE).build();\n        TypeInformation plpType = TypeInformation.builder().withServerType(SqlServerType.VARCHARMAX).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(ServerCharset.CP1252.charset()).build();\n\n        Column id = new Column(0, \"id\", integerType);\n        Column content = new Column(1, \"content\", plpType);\n        ColumnMetadataToken columns = ColumnMetadataToken.create(new Column[]{id, content});\n\n        ByteBuf rowData = loadRowData(\"int-varcharmax-data.txt\");\n\n        RowToken row = RowToken.decode(rowData, columns.getColumns());\n\n        assertThat(row.getColumnData(0).readableBytes()).isEqualTo(5);\n        assertThat(row.getColumnData(1).readableBytes()).isEqualTo(10016);\n\n        rowData.release();\n        row.release();\n    }\n\n    @Test\n    void shouldDecodeIntAndVarcharMaxNull() {\n\n        TypeInformation integerType = TypeInformation.builder().withServerType(SqlServerType.INTEGER).withLengthStrategy(LengthStrategy.BYTELENTYPE).build();\n        TypeInformation plpType = TypeInformation.builder().withServerType(SqlServerType.VARCHARMAX).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(ServerCharset.CP1252.charset()).build();\n\n        Column id = new Column(0, \"id\", integerType);\n        Column content = new Column(1, \"content\", plpType);\n        ColumnMetadataToken columns = ColumnMetadataToken.create(new Column[]{id, content});\n\n        ByteBuf rowData = HexUtils.decodeToByteBuf(\"04 01 00 00 00 FF FF FF FF FF FF FF FF\");\n\n        RowToken row = RowToken.decode(rowData, columns.getColumns());\n\n        assertThat(row.getColumnData(0).readableBytes()).isEqualTo(5);\n        assertThat(row.getColumnData(1)).isNull();\n\n        rowData.release();\n        row.release();\n    }\n\n    @Test\n    void canDecodeShouldReportPlpDecodability() throws IOException {\n\n        TypeInformation integerType = TypeInformation.builder().withServerType(SqlServerType.INTEGER).withLengthStrategy(LengthStrategy.BYTELENTYPE).build();\n        TypeInformation plpType = TypeInformation.builder().withServerType(SqlServerType.VARCHARMAX).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(ServerCharset.CP1252.charset()).build();\n\n        Column id = new Column(0, \"id\", integerType);\n        Column content = new Column(1, \"content\", plpType);\n        ColumnMetadataToken columns = ColumnMetadataToken.create(new Column[]{id, content});\n\n        ByteBuf rowData = loadRowData(\"int-varcharmax-data.txt\");\n        CanDecodeTestSupport.testCanDecode(rowData, buffer -> RowToken.canDecode(buffer, columns.getColumns()));\n        rowData.release();\n    }\n\n    @Test\n    void shouldReleaseBuffersProperly() throws IOException {\n\n        TypeInformation integerType = TypeInformation.builder().withServerType(SqlServerType.INTEGER).withLengthStrategy(LengthStrategy.BYTELENTYPE).build();\n        TypeInformation plpType = TypeInformation.builder().withServerType(SqlServerType.VARCHARMAX).withLengthStrategy(LengthStrategy.PARTLENTYPE).withCharset(ServerCharset.CP1252.charset()).build();\n\n        Column id = new Column(0, \"id\", integerType);\n        Column content = new Column(1, \"content\", plpType);\n        ColumnMetadataToken columns = ColumnMetadataToken.create(new Column[]{id, content});\n\n        ByteBuf rowData = loadRowData(\"int-varcharmax-data.txt\");\n\n        RowToken row = RowToken.decode(rowData, columns.getColumns());\n\n        ByteBuf idData = row.getColumnData(0);\n        ByteBuf contentData = row.getColumnData(1);\n\n        assertThat(idData.refCnt()).isNotZero();\n        assertThat(contentData.refCnt()).isNotZero();\n\n        rowData.release();\n        row.release();\n\n        assertThat(idData.refCnt()).isZero();\n        assertThat(contentData.refCnt()).isZero();\n    }\n\n    private static ByteBuf loadRowData(String resource) throws IOException {\n\n        StringBuffer buffer = new StringBuffer();\n\n        try (InputStream in = RowTokenUnitTests.class.getClassLoader().getResourceAsStream(resource)) {\n\n            if (in == null) {\n                throw new FileNotFoundException(resource);\n            }\n\n            BufferedReader reader = new BufferedReader(new InputStreamReader(in));\n            String line;\n            while ((line = reader.readLine()) != null) {\n                buffer.append(line);\n            }\n        }\n\n        return HexUtils.decodeToByteBuf(buffer.toString());\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/RpcRequestUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.r2dbc.mssql.codec.BlobCodec;\nimport io.r2dbc.mssql.codec.Encoded;\nimport io.r2dbc.mssql.codec.RpcDirection;\nimport io.r2dbc.mssql.codec.RpcParameterContext;\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport io.r2dbc.mssql.message.type.Collation;\nimport io.r2dbc.mssql.util.ClientMessageAssert;\nimport io.r2dbc.mssql.util.HexUtils;\nimport io.r2dbc.mssql.util.TestByteBufAllocator;\nimport io.r2dbc.spi.Blob;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.test.StepVerifier;\n\nimport java.nio.ByteBuffer;\n\n/**\n * Unit tests for {@link RpcRequest}.\n *\n * @author Mark Paluch\n */\nclass RpcRequestUnitTests {\n\n    @Test\n    void shouldEncodeSpCursorOpen() {\n\n        int SCROLLOPT_FAST_FORWARD = 16;\n\n        int CCOPT_READ_ONLY = 1;\n        int CCOPT_ALLOW_DIRECT = 8192;\n\n        int resultSetScrollOpt = SCROLLOPT_FAST_FORWARD;\n        int resultSetCCOpt = CCOPT_READ_ONLY | CCOPT_ALLOW_DIRECT;\n\n        Collation collation = Collation.from(13632521, 52);\n\n        RpcRequest rpcRequest = RpcRequest.builder() //\n            .withProcId(RpcRequest.Sp_CursorOpen) //\n            .withTransactionDescriptor(TransactionDescriptor.empty())\n            .withParameter(RpcDirection.OUT, 0) // cursor\n            .withParameter(RpcDirection.IN, collation, \"SELECT * FROM my_table\")\n            .withParameter(RpcDirection.IN, resultSetScrollOpt)  // scrollopt\n            .withParameter(RpcDirection.IN, resultSetCCOpt) // ccopt\n            .withParameter(RpcDirection.OUT, 0) // rowcount\n            .build();\n\n        String hex = \"00 01 26 04 04 00 00 00 00 00 00 E7\" +\n            \"40 1f 09 04 D0 00 34 2C 00 53 00 45 00 4C 00 45\" +\n            \"00 43 00 54 00 20 00 2A 00 20 00 46 00 52 00 4F\" +\n            \"00 4D 00 20 00 6D 00 79 00 5F 00 74 00 61 00 62\" +\n            \"00 6C 00 65 00 00 00 26 04 04 10 00 00 00 00 00\" +\n            \"26 04 04 01 20 00 00 00 01 26 04 04 00 00 00 00\";\n\n        ClientMessageAssert.assertThat(rpcRequest).encoded()\n            .hasHeader(HeaderOptions.create(Type.RPC, Status.empty()))\n            .isEncodedAs(expected -> {\n\n                AllHeaders.transactional(TransactionDescriptor.empty(), 1).encode(expected);\n\n                Encode.uShort(expected, 0xFFFF); // proc Id switch\n                Encode.uShort(expected, 0x02); // proc Id\n                Encode.asByte(expected, 0); // option flag\n                Encode.asByte(expected, 0); // status flag\n\n                expected.writeBytes(HexUtils.decodeToByteBuf(hex)); // encoded parameters\n            });\n    }\n\n    @Test\n    void shouldEncodeStream() {\n\n        int SCROLLOPT_FAST_FORWARD = 16;\n\n        int CCOPT_READ_ONLY = 1;\n        int CCOPT_ALLOW_DIRECT = 8192;\n\n        int resultSetScrollOpt = SCROLLOPT_FAST_FORWARD;\n        int resultSetCCOpt = CCOPT_READ_ONLY | CCOPT_ALLOW_DIRECT;\n\n        Collation collation = Collation.from(13632521, 52);\n\n        Blob blob = Blob.from(Flux.range(0, 10)\n            .map(it -> String.format(\"X%05d    0123456789\", it))\n            .map(String::getBytes)\n            .map(ByteBuffer::wrap));\n\n        Encoded encoded = BlobCodec.INSTANCE.encode(TestByteBufAllocator.TEST, RpcParameterContext.in(), blob);\n\n        RpcRequest rpcRequest = RpcRequest.builder() //\n            .withProcId(RpcRequest.Sp_CursorOpen) //\n            .withTransactionDescriptor(TransactionDescriptor.empty())\n            .withParameter(RpcDirection.OUT, 0) // cursor\n            .withParameter(RpcDirection.IN, collation, \"SELECT * FROM my_table\")\n            .withParameter(RpcDirection.IN, resultSetScrollOpt)  // scrollopt\n            .withParameter(RpcDirection.IN, resultSetCCOpt) // ccopt\n            .withParameter(RpcDirection.OUT, 0) // rowcount\n            .withParameter(RpcDirection.IN, encoded)\n            .build();\n\n        Flux.from(rpcRequest.encode(TestByteBufAllocator.TEST, 10))\n            .doOnNext(it -> it.getByteBuf().release())\n            .as(StepVerifier::create)\n            .expectNextCount(12)\n            .verifyComplete();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/SqlBatchUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.r2dbc.mssql.message.TransactionDescriptor;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.header.Status;\nimport io.r2dbc.mssql.message.header.Type;\nimport io.r2dbc.mssql.message.tds.Encode;\nimport org.junit.jupiter.api.Test;\n\nimport static io.r2dbc.mssql.util.ClientMessageAssert.assertThat;\n\n/**\n * Unit tests for {@link SqlBatch}.\n *\n * @author Mark Paluch\n */\nclass SqlBatchUnitTests {\n\n    @Test\n    void shouldEncodeProperly() {\n\n        SqlBatch batch = SqlBatch.create(1, TransactionDescriptor.empty(), \"SELECT *  FROM employees;\");\n\n        assertThat(batch).encoded() //\n            .hasHeader(HeaderOptions.create(Type.SQL_BATCH, Status.empty())) //\n            .isEncodedAs(it -> {\n\n                Encode.dword(it, 22); // Total header length\n                Encode.dword(it, 18); // MARS header length\n                Encode.uShort(it, 2); // Tx Descriptor header\n                it.writeBytes(new byte[8]); // Tx descriptor\n                Encode.dword(it, 1); // outstanding requests\n\n                Encode.unicodeStream(it, batch.getSql());\n            });\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/TabnameTokenUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link TabnameToken}.\n *\n * @author Mark Paluch\n */\nclass TabnameTokenUnitTests {\n\n    @Test\n    void shouldDecodeToken() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"a413000108006d0079005f007400\" +\n            \"610062006c006500\");\n\n        assertThat(buffer.readByte()).isEqualTo(TabnameToken.TYPE);\n\n        TabnameToken token = TabnameToken.decode(buffer);\n        assertThat(token.getTableNames()).hasSize(1);\n\n        Identifier name = token.getTableNames().get(0);\n        assertThat(name.getObjectName()).isEqualTo(\"my_table\");\n    }\n\n    @Test\n    void canDecodeShouldReportDecodability() {\n\n        String data = \"13000108006d0079005f007400\" +\n            \"610062006c006500\";\n\n        CanDecodeTestSupport.testCanDecode(HexUtils.decodeToByteBuf(data), TabnameToken::canDecode);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/token/TabularUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.token;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link LoginAckToken}.\n *\n * @author Mark Paluch\n */\nfinal class TabularUnitTests {\n\n    @Test\n    void shouldDecodeLoginAck() {\n\n        String hex = \"e31b0001066d0061\" + \"007300740065007200066d0061007300\"\n            + \"740065007200ab700045160000020025\" + \"004300680061006e0067006500640020\" + \"00640061007400610062006100730065\"\n            + \"00200063006f006e0074006500780074\" + \"00200074006f00200027006d00610073\" + \"0074006500720027002e000c61003600\"\n            + \"38003800300039003200610037003900\" + \"660035000001000000e3080007050904\" + \"d0003400e31700020a750073005f0065\"\n            + \"006e0067006c0069007300680000ab74\" + \"0047160000010027004300680061006e\" + \"0067006500640020006c0061006e0067\"\n            + \"00750061006700650020007300650074\" + \"00740069006e006700200074006f0020\" + \"00750073005f0065006e0067006c0069\"\n            + \"00730068002e000c6100360038003800\" + \"30003900320061003700390066003500\" + \"0001000000ad36000174000004164d00\"\n            + \"6900630072006f0073006f0066007400\" + \"2000530051004c002000530065007200\" + \"760065007200000000000e000bdee313\"\n            + \"00040438003000300030000434003000\" + \"39003600ae040100000001fffd000000\" + \"000000000000000000\";\n\n        ByteBuf byteBuf = Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump(hex));\n\n        Tabular tabular = Tabular.decode(byteBuf, false);\n\n        assertThat(tabular.getTokens()).hasSize(9);\n        assertThat(tabular.getRequiredToken(LoginAckToken.class)).isNotNull();\n        assertThat(tabular\n            .getRequiredToken(EnvChangeToken.class, it -> it.getChangeType() == EnvChangeToken.EnvChangeType.Database)\n            .getNewValueString()).isEqualTo(\"master\");\n    }\n\n    @Test\n    void colMetadataShouldReplacePreviousMetadata() {\n\n        ByteBuf firstMetadata = HexUtils.decodeToByteBuf(\"8107000000000000\" +\n            \"000800300B65006D0070006C006F0079\" +\n            \"00650065005F00690064000000000008\" +\n            \"00E764000904D00034096C0061007300\" +\n            \"74005F006E0061006D00650000000000\" +\n            \"0900A732000904D000340A6600690072\" +\n            \"00730074005F006E0061006D00650000\" +\n            \"00000009006E0806730061006C006100\" +\n            \"7200790000000000090024100366006F\" +\n            \"006F000000000009006D080366006C00\" +\n            \"74000000000009006D04036200610072\" +\n            \"00\");\n\n        ByteBuf nextMetadata = HexUtils.decodeToByteBuf(\"8102000000000000\" +\n            \"00090026010c6e0075006c006c006100\" +\n            \"62006c0065005f0063006f006c000000\" +\n            \"00000000380752004f00570053005400\" +\n            \"41005400\");\n\n        ByteBuf rowData = HexUtils.decodeToByteBuf(\"d1014201000000\");\n\n        Tabular.TabularDecoder decoder = Tabular.createDecoder(true);\n\n        // initialize\n        decoder.decode(firstMetadata);\n        decoder.decode(nextMetadata);\n\n        List<DataToken> rows = decoder.decode(rowData);\n        assertThat(rows).hasSize(1);\n        assertThat(rowData.readableBytes()).isEqualTo(0);\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/type/CollationUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.charset.Charset;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link Collation}.\n *\n * @author Mark Paluch\n */\nclass CollationUnitTests {\n\n    @Test\n    void shouldDecodeCp1252CollationFromSortId() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0904D00034\");\n\n        Collation collation = Collation.decode(buffer);\n\n        assertThat(collation.getSortId()).isEqualTo(52);\n        assertThat(collation.getCharset()).isEqualTo(Charset.forName(\"windows-1252\"));\n    }\n\n    @Test\n    void shouldDecodeEnglishCollationFromSortId() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"0904D00000\");\n\n        Collation collation = Collation.decode(buffer);\n\n        assertThat(collation.getSortId()).isEqualTo(0);\n        assertThat(collation.getCharset()).isEqualTo(Charset.forName(\"windows-1252\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/message/type/TypeBuilderUnitTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.message.type;\n\nimport io.netty.buffer.ByteBuf;\nimport io.r2dbc.mssql.util.HexUtils;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Mark Paluch\n */\nclass TypeBuilderUnitTests {\n\n    @Test\n    void shouldDecodeInt() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"000000000800380B\");\n        TypeInformation typeInformation = TypeBuilder.decode(buffer, true);\n\n        assertThat(typeInformation.getMaxLength()).isEqualTo(4);\n        assertThat(typeInformation.getServerType()).isEqualTo(SqlServerType.INTEGER);\n        assertThat(typeInformation.getLengthStrategy()).isEqualTo(LengthStrategy.FIXEDLENTYPE);\n        assertThat(typeInformation.getDisplaySize()).isEqualTo(11);\n    }\n\n    @Test\n    void canDecodeShouldCheckDecodingAbility() {\n\n        ByteBuf buffer = HexUtils.decodeToByteBuf(\"000000000800380B\");\n\n        assertThat(TypeBuilder.canDecode(buffer, true)).isTrue();\n        assertThat(buffer.readerIndex()).isEqualTo(0);\n\n        assertThat(TypeBuilder.canDecode(HexUtils.decodeToByteBuf(\"000000000800\"), true)).isFalse();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/ClientMessageAssert.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.r2dbc.mssql.message.ClientMessage;\nimport io.r2dbc.mssql.message.header.HeaderOptions;\nimport io.r2dbc.mssql.message.tds.ContextualTdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsFragment;\nimport io.r2dbc.mssql.message.tds.TdsPacket;\nimport org.assertj.core.api.AbstractObjectAssert;\nimport org.assertj.core.api.Assertions;\nimport org.reactivestreams.Publisher;\nimport reactor.core.publisher.Mono;\n\nimport java.nio.charset.Charset;\nimport java.util.function.Consumer;\n\n/**\n * Assertion utility for {@link ClientMessage}.\n *\n * @author Mark Paluch\n */\npublic final class ClientMessageAssert extends AbstractObjectAssert<ClientMessageAssert, ClientMessage> {\n\n    private ClientMessageAssert(ClientMessage actual) {\n        super(actual, ClientMessageAssert.class);\n    }\n\n    /**\n     * Create an assertion for a {@link ClientMessage}.\n     *\n     * @param actual\n     */\n    public static ClientMessageAssert assertThat(ClientMessage actual) {\n        return new ClientMessageAssert(actual);\n    }\n\n    /**\n     * Assert the encoded form of {@link ClientMessage}.\n     *\n     * @see TdsFragment\n     */\n    @SuppressWarnings(\"unchecked\")\n    public TdsFragmentAssert encoded() {\n\n        Object encoded = this.actual.encode(TestByteBufAllocator.TEST, 0);\n\n        if (encoded instanceof TdsFragment) {\n            return new TdsFragmentAssert((TdsFragment) encoded);\n        }\n\n        return new TdsFragmentAssert(Mono.from((Publisher<TdsFragment>) encoded).block());\n    }\n\n    /**\n     * Assertions for {@link TdsFragment}.\n     */\n    public static class TdsFragmentAssert extends AbstractObjectAssert<TdsFragmentAssert, TdsFragment> {\n\n        private TdsFragmentAssert(TdsFragment actual) {\n            super(actual, TdsFragmentAssert.class);\n        }\n\n        /**\n         * Assert that the actual {@link TdsFragment message} contains the expected string by converting the message using the default {@link Charset}.\n         *\n         * @param expected the expected string\n         */\n        public TdsFragmentAssert contains(String expected) {\n            return contains(expected, Charset.defaultCharset());\n        }\n\n        /**\n         * Assert that the actual {@link TdsFragment message} contains the expected string by converting the message using the given {@link Charset}.\n         *\n         * @param expected the expected string\n         */\n        public TdsFragmentAssert contains(String expected, Charset charset) {\n\n            isNotNull();\n\n            new EncodedAssert(this.actual.getByteBuf()).contains(expected, charset);\n\n            return this;\n        }\n\n        /**\n         * Assert that the actual {@link TdsFragment message} is empty.\n         */\n        public TdsFragmentAssert isEmpty() {\n\n            Assertions.assertThat(this.actual.getByteBuf().readableBytes()).isEqualTo(0);\n\n            return this;\n        }\n\n        /**\n         * Assert that the actual {@link TdsFragment message} declares the expected {@link HeaderOptions}\n         *\n         * @param expected the expected {@link HeaderOptions}.\n         */\n        public TdsFragmentAssert hasHeader(HeaderOptions expected) {\n\n            isNotNull();\n\n            Assertions.assertThat(this.actual).isInstanceOfAny(ContextualTdsFragment.class, TdsPacket.class);\n\n            if (this.actual instanceof ContextualTdsFragment) {\n\n                ContextualTdsFragment contextual = (ContextualTdsFragment) this.actual;\n\n                Assertions.assertThat(contextual.getHeaderOptions().getType()).isEqualTo(expected.getType());\n                Assertions.assertThat(contextual.getHeaderOptions().getStatus()).isEqualTo(expected.getStatus());\n            }\n\n            return this;\n        }\n\n        /**\n         * Assert that the actual {@link TdsFragment message} is encoded as described by the expected encoded buffer. The expected value is passed to a {@link Consumer} that writes the expectation\n         * to the {@link ByteBuf data\n         * buffer}.\n         *\n         * @param encoded expectation writer.\n         */\n        public TdsFragmentAssert isEncodedAs(Consumer<ByteBuf> encoded) {\n\n            isNotNull();\n\n            ByteBuf expected = TestByteBufAllocator.TEST.buffer();\n            encoded.accept(expected);\n\n            Assertions.assertThat(ByteBufUtil.prettyHexDump(this.actual.getByteBuf())).describedAs(\"ByteBuf\")\n                .isEqualTo(ByteBufUtil.prettyHexDump(expected));\n\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/EmbeddedChannelAssert.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport org.assertj.core.api.AbstractObjectAssert;\nimport org.assertj.core.api.Assertions;\n\nimport java.util.Queue;\n\n/**\n * Assertion utility for {@link EmbeddedChannel} to verify channel interaction.\n *\n * @author Mark Paluch\n */\npublic final class EmbeddedChannelAssert extends AbstractObjectAssert<EmbeddedChannelAssert, EmbeddedChannel> {\n\n    private EmbeddedChannelAssert(EmbeddedChannel actual) {\n        super(actual, EmbeddedChannelAssert.class);\n    }\n\n    /**\n     * Create an assertion for an {@link EmbeddedChannel}.\n     *\n     * @param actual the channel.\n     */\n    public static EmbeddedChannelAssert assertThat(EmbeddedChannel actual) {\n        return new EmbeddedChannelAssert(actual);\n    }\n\n    /**\n     * Assert inbound (received) messages.\n     */\n    public MessagesAssert inbound() {\n        return new MessagesAssert(\"inbound\", this.actual.inboundMessages());\n    }\n\n    /**\n     * Assert outbound (sent) messages.\n     */\n    public MessagesAssert outbound() {\n        return new MessagesAssert(\"outbound\", this.actual.outboundMessages());\n    }\n\n    /**\n     * Assertions for messages of the embedded channel.\n     */\n    public static final class MessagesAssert extends AbstractObjectAssert<MessagesAssert, Queue<Object>> {\n\n        private final String direction;\n\n        private MessagesAssert(String direction, Queue<Object> actual) {\n            super(actual, MessagesAssert.class);\n            this.direction = direction;\n        }\n\n        /**\n         * Assert that the message contains {@link ByteBuf} messages.\n         */\n        public EncodedAssert hasByteBufMessage() {\n\n            isNotNull();\n            Object poll = this.actual.poll();\n\n            Assertions.assertThat(poll).describedAs(this.direction + \" message\").isNotNull().isInstanceOf(ByteBuf.class);\n\n            return new EncodedAssert((ByteBuf) poll);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/EncodedAssert.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.r2dbc.mssql.codec.Encoded;\nimport org.assertj.core.api.AbstractObjectAssert;\nimport org.assertj.core.api.Assertions;\n\nimport java.nio.charset.Charset;\nimport java.util.function.Consumer;\n\n/**\n * Assertions for {@link ByteBuf}.\n *\n * @author Mark Paluch\n */\npublic final class EncodedAssert extends AbstractObjectAssert<EncodedAssert, ByteBuf> {\n\n    EncodedAssert(ByteBuf actual) {\n        super(actual, EncodedAssert.class);\n    }\n\n    /**\n     * Create an assertion for a {@link ByteBuf}.\n     *\n     * @param actual\n     */\n    public static EncodedAssert assertThat(ByteBuf actual) {\n        return new EncodedAssert(actual);\n    }\n\n    /**\n     * Create an assertion for a {@link ByteBuf}.\n     *\n     * @param actual\n     */\n    public static EncodedAssert assertThat(Encoded actual) {\n        return new EncodedAssert(actual.getValue());\n    }\n\n    /**\n     * Assert that the actual {@link ByteBuf data buffer} contains the expected string by converting the buffer using the default {@link Charset}.\n     *\n     * @param expected the expected string.\n     */\n    public EncodedAssert contains(String expected) {\n        return contains(expected, Charset.defaultCharset());\n    }\n\n    /**\n     * Assert that the actual {@link ByteBuf data buffer} contains the expected string by converting the buffer using the given {@link Charset}.\n     *\n     * @param expected the expected string.\n     * @param charset  the charset to use.\n     */\n    public EncodedAssert contains(String expected, Charset charset) {\n\n        isNotNull();\n\n        String actual = this.actual.toString(charset);\n\n        Assertions.assertThat(actual).contains(expected);\n\n        return this;\n    }\n\n    /**\n     * Assert that the actual {@link ByteBuf data buffer} is equal to the expected hexadecimal representation.\n     *\n     * @param expected the expected value represented as hexadecimal characters.\n     */\n    public EncodedAssert isEqualToHex(String expected) {\n\n        isNotNull();\n\n        Assertions.assertThat(ByteBufUtil.prettyHexDump(this.actual)).describedAs(\"ByteBuf\")\n            .isEqualTo(ByteBufUtil.prettyHexDump(HexUtils.decodeToByteBuf(expected)));\n\n        return this;\n    }\n\n    /**\n     * Assert that the actual {@link ByteBuf data buffer} is empty (i.e. contains no readable bytes).\n     */\n    public void isEmpty() {\n        Assertions.assertThat(this.actual.readableBytes()).isEqualTo(0);\n    }\n\n    /**\n     * Assert that the actual {@link ByteBuf data buffer} is encoded as described by the expected encoded buffer. The expected value is passed to a {@link Consumer} that writes the expectation to\n     * the {@link ByteBuf data\n     * buffer}.\n     *\n     * @param encoded expectation writer.\n     */\n    public EncodedAssert isEncodedAs(Consumer<ByteBuf> encoded) {\n\n        isNotNull();\n\n        ByteBuf expected = TestByteBufAllocator.TEST.buffer();\n        encoded.accept(expected);\n\n        Assertions.assertThat(ByteBufUtil.prettyHexDump(this.actual)).describedAs(\"ByteBuf\")\n            .isEqualTo(ByteBufUtil.prettyHexDump(expected));\n\n        ReferenceCountUtil.maybeSafeRelease(this.actual);\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/FluxDiscardOnCancelUnitTests.java",
    "content": "/*\n * Copyright 2019-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Hooks;\nimport reactor.test.StepVerifier;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.IntStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link FluxDiscardOnCancel}.\n *\n * @author Mark Paluch\n */\nclass FluxDiscardOnCancelUnitTests {\n\n    @Test\n    void shouldEmitAllItemsOnSubscription() {\n\n        Iterator<Integer> items = createItems(4);\n\n        Flux.fromIterable(() -> items)\n            .as(Operators::discardOnCancel)\n            .as(StepVerifier::create)\n            .expectNext(0, 1, 2, 3)\n            .verifyComplete();\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    void considersAssemblyHook() {\n\n        List<Object> publishers = new ArrayList<>();\n        Hooks.onEachOperator(objectPublisher -> {\n            publishers.add(objectPublisher);\n\n            return objectPublisher;\n        });\n\n        Iterator<Integer> items = createItems(4);\n\n        Flux.fromIterable(() -> items)\n            .transform(Operators::discardOnCancel)\n            .as(StepVerifier::create)\n            .expectNextCount(4)\n            .verifyComplete();\n\n        assertThat(publishers).hasSize(2).extracting(Object::getClass).contains((Class) FluxDiscardOnCancel.class);\n    }\n\n    @Test\n    void considersOnDropHook() {\n\n        List<Object> discard = new ArrayList<>();\n\n        Iterator<Integer> items = createItems(4);\n\n        Flux.fromIterable(() -> items)\n            .as(Operators::discardOnCancel)\n            .doOnDiscard(Object.class, discard::add)\n            .as(it -> StepVerifier.create(it, 0))\n            .thenRequest(2)\n            .expectNext(0, 1)\n            .thenCancel()\n            .verify();\n\n        assertThat(discard).containsOnly(2, 3);\n    }\n\n    @Test\n    void considersCancelSignalPropagation() {\n\n        AtomicBoolean cancelled = new AtomicBoolean();\n\n        Iterator<Integer> items = createItems(4);\n\n        Flux.fromIterable(() -> items)\n            .as(it -> Operators.discardOnCancel(it, () -> cancelled.set(true)))\n            .as(it -> StepVerifier.create(it, 0))\n            .thenRequest(2)\n            .expectNext(0, 1)\n            .thenCancel()\n            .verify();\n\n        assertThat(cancelled).isTrue();\n    }\n\n    @Test\n    void shouldNotConsumeItemsOnCancel() {\n\n        Iterator<Integer> items = createItems(4);\n\n        Flux.fromIterable(() -> items)\n            .as(it -> StepVerifier.create(it, 0))\n            .thenRequest(2)\n            .expectNext(0, 1)\n            .thenCancel()\n            .verify();\n\n        assertThat(items).toIterable().containsOnly(3);\n    }\n\n    @Test\n    void shouldConsumeAndDiscardItemsOnCancel() {\n\n        Iterator<Integer> items = createItems(4);\n\n        Flux.fromIterable(() -> items)\n            .as(Operators::discardOnCancel)\n            .as(it -> StepVerifier.create(it, 0))\n            .thenRequest(2)\n            .expectNext(0, 1)\n            .thenCancel()\n            .verify();\n\n        assertThat(items).toIterable().isEmpty();\n    }\n\n    static Iterator<Integer> createItems(int count) {\n        return IntStream.range(0, count).boxed().iterator();\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/HexUtils.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\n\n/**\n * Utilities for working with {@link ByteBuf}s.\n */\npublic final class HexUtils {\n\n    private HexUtils() {\n    }\n\n    /**\n     * Decode a {@link String} containing Hex-encoded bytes into a {@link ByteBuf}.\n     *\n     * @param chars the {@link String} to decode\n     * @return the {@link ByteBuf} decoded from the {@link String}\n     */\n    public static ByteBuf decodeToByteBuf(String chars) {\n\n        Assert.requireNonNull(chars, \"String must not be null\");\n\n        return Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump(chars.replaceAll(\" \", \"\")));\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/IntegrationTestSupport.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport io.r2dbc.mssql.MssqlConnection;\nimport io.r2dbc.mssql.MssqlConnectionFactory;\nimport io.r2dbc.mssql.MssqlConnectionFactoryProvider;\nimport io.r2dbc.spi.ConnectionFactories;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport reactor.test.StepVerifier;\n\nimport java.util.function.Predicate;\n\nimport static io.r2dbc.spi.ConnectionFactoryOptions.*;\n\n/**\n * Support class for integration tests.\n *\n * @author Mark Paluch\n */\npublic abstract class IntegrationTestSupport {\n\n    @RegisterExtension\n    protected static final MsSqlServerExtension SERVER = new MsSqlServerExtension();\n\n    protected static MssqlConnectionFactory connectionFactory;\n\n    protected static MssqlConnection connection;\n\n    @BeforeAll\n    static void beforeAll() {\n\n        ConnectionFactoryOptions options = builder().build();\n\n        connectionFactory = (MssqlConnectionFactory) ConnectionFactories.get(options);\n        connection = connectionFactory.create().block();\n    }\n\n    public static ConnectionFactoryOptions.Builder builder() {\n\n        Predicate<String> preferCursoredExecution = sql -> sql.contains(\"cursored\");\n\n        return ConnectionFactoryOptions.builder()\n            .option(DRIVER, MssqlConnectionFactoryProvider.MSSQL_DRIVER)\n            .option(HOST, SERVER.getHost())\n            .option(PORT, SERVER.getPort())\n            .option(PASSWORD, SERVER.getPassword())\n            .option(USER, SERVER.getUsername())\n            .option(MssqlConnectionFactoryProvider.PREFER_CURSORED_EXECUTION, preferCursoredExecution);\n    }\n\n    @BeforeEach\n    void setUp() {\n        connection.setAutoCommit(true).as(StepVerifier::create).verifyComplete();\n    }\n\n    @AfterAll\n    static void afterAll() {\n\n        System.out.println(\"close\");\n        if (connection != null) {\n            connection.close().subscribe();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/MsSqlServerExtension.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport com.zaxxer.hikari.HikariDataSource;\nimport io.r2dbc.mssql.MssqlConnectionConfiguration;\nimport org.junit.jupiter.api.extension.AfterAllCallback;\nimport org.junit.jupiter.api.extension.BeforeAllCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.springframework.jdbc.core.JdbcOperations;\nimport org.springframework.jdbc.core.JdbcTemplate;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.MSSQLServerContainer;\nimport reactor.util.annotation.Nullable;\n\nimport java.io.IOException;\nimport java.net.Socket;\nimport java.util.function.Supplier;\n\n/**\n * Test container extension for Microsoft SQL Server.\n */\n@SuppressWarnings({\"rawtypes\"})\npublic final class MsSqlServerExtension implements BeforeAllCallback, AfterAllCallback {\n\n    private volatile MSSQLServerContainer<?> containerInstance = null;\n\n    private final Supplier<MSSQLServerContainer<?>> container = () -> {\n\n        if (this.containerInstance != null) {\n            return this.containerInstance;\n        }\n        return this.containerInstance = new MSSQLServerContainer(\"mcr.microsoft.com/mssql/server:2022-latest\") {\n\n            protected void configure() {\n                this.addExposedPort(MS_SQL_SERVER_PORT);\n                this.addEnv(\"ACCEPT_EULA\", \"Y\");\n                this.addEnv(\"SA_PASSWORD\", getPassword());\n                this.withReuse(true);\n            }\n        };\n    };\n\n    private HikariDataSource dataSource;\n\n    private JdbcOperations jdbcOperations;\n\n    private final DatabaseContainer sqlServer = External.INSTANCE.isAvailable() ? External.INSTANCE : new TestContainer(this.container.get());\n\n    private final boolean useTestContainer = this.sqlServer instanceof TestContainer;\n\n    @Override\n    public void beforeAll(ExtensionContext context) {\n        initialize();\n    }\n\n    public void initialize() {\n\n        if (this.useTestContainer) {\n            this.container.get().start();\n        }\n\n        HikariDataSource hikariDataSource = new HikariDataSource();\n        hikariDataSource.setJdbcUrl(\"jdbc:sqlserver://\" + getHost() + \":\" + getPort() + \";database=master;sendStringParametersAsUnicode=true;encrypt=false\");\n        hikariDataSource.setUsername(getUsername());\n        hikariDataSource.setPassword(getPassword());\n\n        this.dataSource = hikariDataSource;\n        this.dataSource.setMaximumPoolSize(1);\n\n        this.jdbcOperations = new JdbcTemplate(this.dataSource);\n    }\n\n    @Override\n    public void afterAll(ExtensionContext context) {\n    }\n\n    public MssqlConnectionConfiguration.Builder configBuilder() {\n        return MssqlConnectionConfiguration.builder().host(getHost()).port(getPort()).username(getUsername()).password(getPassword());\n    }\n\n    public MssqlConnectionConfiguration getConnectionConfiguration() {\n        return configBuilder().build();\n    }\n\n    public HikariDataSource getDataSource() {\n        return this.dataSource;\n    }\n\n    @Nullable\n    public JdbcOperations getJdbcOperations() {\n        return this.jdbcOperations;\n    }\n\n    public String getHost() {\n        return this.sqlServer.getHost();\n    }\n\n    public int getPort() {\n        return this.sqlServer.getPort();\n    }\n\n    public String getUsername() {\n        return this.sqlServer.getUsername();\n    }\n\n    public String getPassword() {\n        return this.sqlServer.getPassword();\n    }\n\n    /**\n     * Interface to be implemented by database providers (provided database, test container).\n     */\n    interface DatabaseContainer {\n\n        String getHost();\n\n        int getPort();\n\n        String getUsername();\n\n        String getPassword();\n    }\n\n    /**\n     * Externally provided SQL Server instance.\n     */\n    static class External implements DatabaseContainer {\n\n        public static final External INSTANCE = new External();\n\n        @Override\n        public String getHost() {\n            return \"localhost\";\n        }\n\n        @Override\n        public int getPort() {\n            return 1433;\n        }\n\n        @Override\n        public String getUsername() {\n            return \"sa\";\n        }\n\n        @Override\n        public String getPassword() {\n            return \"A_Str0ng_Required_Password\";\n        }\n\n        /**\n         * Returns whether this container is available.\n         */\n        @SuppressWarnings(\"try\")\n        boolean isAvailable() {\n\n            try (Socket ignored = new Socket(getHost(), getPort())) {\n\n                return true;\n            } catch (IOException e) {\n                return false;\n            }\n        }\n    }\n\n    /**\n     * {@link DatabaseContainer} provided by {@link JdbcDatabaseContainer}.\n     */\n    static class TestContainer implements DatabaseContainer {\n\n        private final JdbcDatabaseContainer<?> container;\n\n        TestContainer(JdbcDatabaseContainer<?> container) {\n            this.container = container;\n        }\n\n        @Override\n        public String getHost() {\n            return this.container.getContainerIpAddress();\n        }\n\n        @Override\n        public int getPort() {\n            return this.container.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT);\n        }\n\n        @Override\n        public String getUsername() {\n            return this.container.getUsername();\n        }\n\n        @Override\n        public String getPassword() {\n            return this.container.getPassword();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/TestByteBufAllocator.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.UnpooledByteBufAllocator;\nimport io.netty.util.internal.PlatformDependent;\n\n/**\n * {@link ByteBufAllocator} used for tests.\n */\npublic final class TestByteBufAllocator {\n\n    public static final ByteBufAllocator TEST = new UnpooledByteBufAllocator(PlatformDependent.directBufferPreferred(),\n        true);\n\n    private TestByteBufAllocator() {\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/io/r2dbc/mssql/util/Types.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage io.r2dbc.mssql.util;\n\nimport io.r2dbc.mssql.message.type.LengthStrategy;\nimport io.r2dbc.mssql.message.type.SqlServerType;\nimport io.r2dbc.mssql.message.type.TypeInformation;\n\n/**\n * Type collection for testing.\n *\n * @author Mark Paluch\n */\npublic class Types {\n\n    private static final TypeInformation integer =\n        TypeInformation.builder().withScale(4).withMaxLength(4).withLengthStrategy(LengthStrategy.BYTELENTYPE).withServerType(SqlServerType.INTEGER).build();\n\n    public static TypeInformation integer() {\n        return integer;\n    }\n\n    public static TypeInformation varchar(int length) {\n        return TypeInformation.builder().withServerType(SqlServerType.VARCHAR).withLengthStrategy(LengthStrategy.USHORTLENTYPE).withMaxLength(length).build();\n    }\n\n    private Types() {\n\n    }\n}\n"
  },
  {
    "path": "src/test/resources/int-varcharmax-data.txt",
    "content": "04 01 00 00 00 10 27 00\n00 00 00 00 00 F7 1E 00 00 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n\n19 08 00 00 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41\n41 41 41 41 41 00 00 00 00\n"
  },
  {
    "path": "src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n  ~ Copyright 2018-2022 the original author or authors.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      https://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{yyyy-MM-dd} | %d{HH:mm:ss.SSS} | %-20.20thread | %5p | %logger{25} | %m%n</pattern>\n        </encoder>\n    </appender>\n\n    <logger name=\"io.r2dbc.mssql\" level=\"INFO\"/>\n    <logger name=\"tc\" level=\"OFF\"/> <!-- Test Container/Docker Logging -->\n    <logger name=\"org.testcontainers\" level=\"WARN\"/>\n    <logger name=\"io.netty.resolver.dns\" level=\"OFF\"/> <!-- Silence native MacOS Resolver Warnings -->\n    <logger name=\"reactor.ipc.netty\" level=\"WARN\"/>\n    <logger name=\"stream\" level=\"INFO\"/>\n    <logger name=\"test\" level=\"INFO\"/>\n\n    <root level=\"WARN\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n</configuration>\n"
  }
]